mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-15 06:42:25 +02:00
Grid Bounds (#800)
* Added centered unit box static field to Box2. * MapGrid is more testable. * Added some unit tests for MapGrid. Fixed bug in MapChunk.GridTileToLocal(). MapGrid.UpdateAABB() actually expands properly now. * Moved IMapChunk to Robust.Shared.Map. Moved Chunk class out of MapManager class. * Added unit tests for MapChunk. * Bounds reduce by 1, so almost working. * Now bound shrinking works :D * Moved MapGrid out of MapManager. Moved IMapGrid into the Shared/Map folder. Replaced all calls to IMapGrid.ParentMap.Index with IMapGrid.ParentMapId. * Added more MapGrid unit tests. Fixed a bug in TryGetTileRef.
This commit is contained in:
committed by
Pieter-Jan Briers
parent
d74151d242
commit
1b3cc8aba6
@@ -23,7 +23,7 @@ namespace Robust.Client.GameObjects
|
||||
public IEnumerable<IEntity> GetEntitiesInRange(GridCoordinates position, float Range)
|
||||
{
|
||||
var AABB = new Box2(position.Position - new Vector2(Range / 2, Range / 2), position.Position + new Vector2(Range / 2, Range / 2));
|
||||
return GetEntitiesIntersecting(_mapManager.GetGrid(position.GridID).ParentMap.Index, AABB);
|
||||
return GetEntitiesIntersecting(_mapManager.GetGrid(position.GridID).ParentMapId, AABB);
|
||||
}
|
||||
|
||||
public IEnumerable<IEntity> GetEntitiesIntersecting(MapId mapId, Box2 position)
|
||||
|
||||
@@ -307,7 +307,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
foreach (var effect in _owner._Effects)
|
||||
{
|
||||
if (_mapManager.GetGrid(effect.Coordinates.GridID).ParentMap.Index != map)
|
||||
if (_mapManager.GetGrid(effect.Coordinates.GridID).ParentMapId != map)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -46,8 +46,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
gridProgram.SetUniform(UniModUV, new Vector4(0, 0, 1, 1));
|
||||
gridProgram.SetUniform(UniModulate, Color.White);
|
||||
|
||||
foreach (var grid in _mapManager.GetMap(map).GetAllGrids())
|
||||
foreach (var mapGrid in _mapManager.GetMap(map).GetAllGrids())
|
||||
{
|
||||
var grid = (IMapGridInternal) mapGrid;
|
||||
|
||||
if (!_mapChunkData.ContainsKey(grid.Index))
|
||||
{
|
||||
continue;
|
||||
@@ -57,14 +59,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
model.R1C2 = grid.WorldPosition.Y;
|
||||
gridProgram.SetUniform(UniModelMatrix, model);
|
||||
|
||||
foreach (var chunk in grid.GetMapChunks())
|
||||
foreach (var (index, chunk) in grid.GetMapChunks())
|
||||
{
|
||||
if (_isChunkDirty(grid, chunk))
|
||||
{
|
||||
_updateChunkMesh(grid, chunk);
|
||||
}
|
||||
|
||||
var datum = _mapChunkData[grid.Index][chunk.Index];
|
||||
var datum = _mapChunkData[grid.Index][chunk.Indices];
|
||||
|
||||
if (datum.TileCount == 0)
|
||||
{
|
||||
@@ -86,7 +88,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
var data = _mapChunkData[grid.Index];
|
||||
|
||||
if (!data.TryGetValue(chunk.Index, out var datum))
|
||||
if (!data.TryGetValue(chunk.Indices, out var datum))
|
||||
{
|
||||
datum = _initChunkBuffers(grid, chunk);
|
||||
}
|
||||
@@ -149,11 +151,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var eboSize = _indicesPerChunk(chunk) * sizeof(ushort);
|
||||
|
||||
var vbo = new Buffer(this, BufferTarget.ArrayBuffer, BufferUsageHint.DynamicDraw,
|
||||
vboSize, $"Grid {grid.Index} chunk {chunk.Index} VBO");
|
||||
vboSize, $"Grid {grid.Index} chunk {chunk.Indices} VBO");
|
||||
var ebo = new Buffer(this, BufferTarget.ElementArrayBuffer, BufferUsageHint.DynamicDraw,
|
||||
eboSize, $"Grid {grid.Index} chunk {chunk.Index} EBO");
|
||||
eboSize, $"Grid {grid.Index} chunk {chunk.Indices} EBO");
|
||||
|
||||
_objectLabelMaybe(ObjectLabelIdentifier.VertexArray, vao, $"Grid {grid.Index} chunk {chunk.Index} VAO");
|
||||
_objectLabelMaybe(ObjectLabelIdentifier.VertexArray, vao, $"Grid {grid.Index} chunk {chunk.Indices} VAO");
|
||||
// Vertex Coords
|
||||
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, Vertex2D.SizeOf, 0);
|
||||
GL.EnableVertexAttribArray(0);
|
||||
@@ -174,14 +176,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
VBO = vbo
|
||||
};
|
||||
|
||||
_mapChunkData[grid.Index].Add(chunk.Index, datum);
|
||||
_mapChunkData[grid.Index].Add(chunk.Indices, datum);
|
||||
return datum;
|
||||
}
|
||||
|
||||
private bool _isChunkDirty(IMapGrid grid, IMapChunk chunk)
|
||||
{
|
||||
var data = _mapChunkData[grid.Index];
|
||||
return !data.TryGetValue(chunk.Index, out var datum) || datum.Dirty;
|
||||
return !data.TryGetValue(chunk.Indices, out var datum) || datum.Dirty;
|
||||
}
|
||||
|
||||
public void _setChunkDirty(IMapGrid grid, MapIndices chunk)
|
||||
@@ -199,7 +201,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
foreach (var (pos, _) in args.Modified)
|
||||
{
|
||||
var grid = args.Grid;
|
||||
var chunk = grid.GridTileToGridChunk(pos);
|
||||
var chunk = grid.GridTileToChunkIndices(pos);
|
||||
_setChunkDirty(grid, chunk);
|
||||
}
|
||||
}
|
||||
@@ -207,7 +209,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private void _updateTileMapOnUpdate(object sender, TileChangedEventArgs args)
|
||||
{
|
||||
var grid = _mapManager.GetGrid(args.NewTile.GridIndex);
|
||||
var chunk = grid.GridTileToGridChunk(new MapIndices(args.NewTile.X, args.NewTile.Y));
|
||||
var chunk = grid.GridTileToChunkIndices(new MapIndices(args.NewTile.X, args.NewTile.Y));
|
||||
_setChunkDirty(grid, chunk);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Robust.Client.Placement.Modes
|
||||
{
|
||||
MouseCoords = ScreenToPlayerGrid(mouseScreen);
|
||||
var mapGrid = pManager.MapManager.GetGrid(MouseCoords.GridID);
|
||||
CurrentTile = mapGrid.GetTile(MouseCoords);
|
||||
CurrentTile = mapGrid.GetTileRef(MouseCoords);
|
||||
|
||||
if (pManager.CurrentPermission.IsTile)
|
||||
{
|
||||
@@ -32,7 +32,7 @@ namespace Robust.Client.Placement.Modes
|
||||
var manager = IoCManager.Resolve<IClientEntityManager>();
|
||||
|
||||
var snapToEntities = manager.GetEntitiesInRange(MouseCoords, SnapToRange)
|
||||
.Where(entity => entity.Prototype == pManager.CurrentPrototype && entity.Transform.MapID == mapGrid.ParentMap.Index)
|
||||
.Where(entity => entity.Prototype == pManager.CurrentPrototype && entity.Transform.MapID == mapGrid.ParentMapId)
|
||||
.OrderBy(entity => (entity.Transform.WorldPosition - MouseCoords.ToWorld(pManager.MapManager).Position).LengthSquared)
|
||||
.ToList();
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Robust.Client.Placement.Modes
|
||||
MouseCoords = ScreenToPlayerGrid(mouseScreen);
|
||||
|
||||
var mapGrid = pManager.MapManager.GetGrid(MouseCoords.GridID);
|
||||
CurrentTile = mapGrid.GetTile(MouseCoords);
|
||||
CurrentTile = mapGrid.GetTileRef(MouseCoords);
|
||||
float tileSize = mapGrid.TileSize; //convert from ushort to float
|
||||
GridDistancing = tileSize;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Robust.Client.Placement.Modes
|
||||
MouseCoords = ScreenToPlayerGrid(mouseScreen);
|
||||
|
||||
var mapGrid = pManager.MapManager.GetGrid(MouseCoords.GridID);
|
||||
CurrentTile = mapGrid.GetTile(MouseCoords);
|
||||
CurrentTile = mapGrid.GetTileRef(MouseCoords);
|
||||
float tileSize = mapGrid.TileSize; //convert from ushort to float
|
||||
GridDistancing = tileSize;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Robust.Client.Placement.Modes
|
||||
MouseCoords = ScreenToPlayerGrid(mouseScreen);
|
||||
|
||||
var mapGrid = pManager.MapManager.GetGrid(MouseCoords.GridID);
|
||||
CurrentTile = mapGrid.GetTile(MouseCoords);
|
||||
CurrentTile = mapGrid.GetTileRef(MouseCoords);
|
||||
float tileSize = mapGrid.TileSize; //convert from ushort to float
|
||||
GridDistancing = tileSize;
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Robust.Client.Placement.Modes
|
||||
}
|
||||
|
||||
var entitymanager = IoCManager.Resolve<IClientEntityManager>();
|
||||
return !(entitymanager.AnyEntitiesIntersecting(pManager.MapManager.GetGrid(MouseCoords.GridID).ParentMap.Index,
|
||||
return !(entitymanager.AnyEntitiesIntersecting(pManager.MapManager.GetGrid(MouseCoords.GridID).ParentMapId,
|
||||
new Box2(new Vector2(CurrentTile.X, CurrentTile.Y), new Vector2(CurrentTile.X + 0.99f, CurrentTile.Y + 0.99f))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Robust.Client.Placement.Modes
|
||||
MouseCoords = ScreenToPlayerGrid(mouseScreen);
|
||||
|
||||
var mapGrid = pManager.MapManager.GetGrid(MouseCoords.GridID);
|
||||
CurrentTile = mapGrid.GetTile(MouseCoords);
|
||||
CurrentTile = mapGrid.GetTileRef(MouseCoords);
|
||||
float tileSize = mapGrid.TileSize; //convert from ushort to float
|
||||
GridDistancing = tileSize;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Robust.Client.Placement.Modes
|
||||
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
|
||||
{
|
||||
MouseCoords = ScreenToPlayerGrid(mouseScreen);
|
||||
CurrentTile = pManager.MapManager.GetGrid(MouseCoords.GridID).GetTile(MouseCoords);
|
||||
CurrentTile = pManager.MapManager.GetGrid(MouseCoords.GridID).GetTileRef(MouseCoords);
|
||||
|
||||
if (pManager.CurrentPermission.IsTile)
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Robust.Client.Placement.Modes
|
||||
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
|
||||
{
|
||||
MouseCoords = ScreenToPlayerGrid(mouseScreen);
|
||||
CurrentTile = pManager.MapManager.GetGrid(MouseCoords.GridID).GetTile(MouseCoords);
|
||||
CurrentTile = pManager.MapManager.GetGrid(MouseCoords.GridID).GetTileRef(MouseCoords);
|
||||
}
|
||||
|
||||
public override bool IsValidPosition(GridCoordinates position)
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.Placement.Modes
|
||||
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
|
||||
{
|
||||
MouseCoords = ScreenToPlayerGrid(mouseScreen);
|
||||
CurrentTile = pManager.MapManager.GetGrid(MouseCoords.GridID).GetTile(MouseCoords);
|
||||
CurrentTile = pManager.MapManager.GetGrid(MouseCoords.GridID).GetTileRef(MouseCoords);
|
||||
}
|
||||
|
||||
public override bool IsValidPosition(GridCoordinates position)
|
||||
|
||||
@@ -573,7 +573,7 @@ namespace Robust.Client.Placement
|
||||
|
||||
private void RequestPlacement(GridCoordinates coordinates)
|
||||
{
|
||||
if (MapManager.GetGrid(coordinates.GridID).ParentMap.Index == MapId.Nullspace) return;
|
||||
if (MapManager.GetGrid(coordinates.GridID).ParentMapId == MapId.Nullspace) return;
|
||||
if (CurrentPermission == null) return;
|
||||
if (!CurrentMode.IsValidPosition(coordinates)) return;
|
||||
if (Hijack != null && Hijack.HijackPlacementRequest(coordinates)) return;
|
||||
@@ -583,7 +583,7 @@ namespace Robust.Client.Placement
|
||||
var grid = MapManager.GetGrid(coordinates.GridID);
|
||||
|
||||
// no point changing the tile to the same thing.
|
||||
if (grid.GetTile(coordinates).Tile.TypeId == CurrentPermission.TileType)
|
||||
if (grid.GetTileRef(coordinates).Tile.TypeId == CurrentPermission.TileType)
|
||||
return;
|
||||
|
||||
foreach (var tileChange in _pendingTileChanges)
|
||||
|
||||
@@ -198,7 +198,7 @@ namespace Robust.Client.Placement
|
||||
bounds.Width,
|
||||
bounds.Height);
|
||||
|
||||
if (pManager.PhysicsManager.IsColliding(collisionbox, pManager.MapManager.GetGrid(coordinates.GridID).ParentMap.Index))
|
||||
if (pManager.PhysicsManager.IsColliding(collisionbox, pManager.MapManager.GetGrid(coordinates.GridID).ParentMapId))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
||||
@@ -162,7 +162,7 @@ namespace Robust.Client.State.States
|
||||
public IList<IEntity> GetEntitiesUnderPosition(GridCoordinates coordinates)
|
||||
{
|
||||
// Find all the entities intersecting our click
|
||||
var entities = _entityManager.GetEntitiesIntersecting(_mapManager.GetGrid(coordinates.GridID).ParentMap.Index, coordinates.Position);
|
||||
var entities = _entityManager.GetEntitiesIntersecting(_mapManager.GetGrid(coordinates.GridID).ParentMapId, coordinates.Position);
|
||||
|
||||
// Check the entities against whether or not we can click them
|
||||
var foundEntities = new List<(IEntity clicked, int drawDepth)>();
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
try
|
||||
{
|
||||
var coords = eyeManager.ScreenToWorld(new ScreenCoordinates(mouseScreenPos));
|
||||
mouseWorldMap = (int) _mapManager.GetGrid(coords.GridID).ParentMap.Index;
|
||||
mouseWorldMap = (int) _mapManager.GetGrid(coords.GridID).ParentMapId;
|
||||
mouseWorldGrid = (int) coords.GridID;
|
||||
mouseWorldPos = coords;
|
||||
worldToScreen = eyeManager.WorldToScreen(coords);
|
||||
@@ -109,7 +109,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
mouseEntity = gameScreen.GetEntityUnderPosition(coords);
|
||||
}
|
||||
|
||||
tile = _mapManager.GetGrid(coords.GridID).GetTile(coords);
|
||||
tile = _mapManager.GetGrid(coords.GridID).GetTileRef(coords);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -161,7 +161,7 @@ namespace Robust.Server.Console.Commands
|
||||
|
||||
var pos = player.AttachedEntity.Transform.GridPosition;
|
||||
|
||||
shell.SendText(player, $"MapID:{IoCManager.Resolve<IMapManager>().GetGrid(pos.GridID).ParentMap.Index} GridID:{pos.GridID} X:{pos.X:N2} Y:{pos.Y:N2}");
|
||||
shell.SendText(player, $"MapID:{IoCManager.Resolve<IMapManager>().GetGrid(pos.GridID).ParentMapId} GridID:{pos.GridID} X:{pos.X:N2} Y:{pos.Y:N2}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ namespace Robust.Server.GameObjects.EntitySystems
|
||||
{
|
||||
var location = entity.Transform;
|
||||
var grid = _mapManager.GetGrid(location.GridPosition.GridID);
|
||||
var tile = grid.GetTile(location.GridPosition);
|
||||
var tile = grid.GetTileRef(location.GridPosition);
|
||||
var tileDef = _tileDefinitionManager[tile.Tile.TypeId];
|
||||
if (tileDef.Friction != 0)
|
||||
{
|
||||
|
||||
@@ -173,7 +173,7 @@ namespace Robust.Server.GameObjects
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntity> GetEntitiesIntersecting(GridCoordinates position)
|
||||
{
|
||||
return GetEntitiesIntersecting(_mapManager.GetGrid(position.GridID).ParentMap.Index, position.ToWorld(_mapManager).Position);
|
||||
return GetEntitiesIntersecting(_mapManager.GetGrid(position.GridID).ParentMapId, position.ToWorld(_mapManager).Position);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -191,7 +191,7 @@ namespace Robust.Server.GameObjects
|
||||
public IEnumerable<IEntity> GetEntitiesInRange(GridCoordinates position, float range)
|
||||
{
|
||||
var aabb = new Box2(position.Position - new Vector2(range / 2, range / 2), position.Position + new Vector2(range / 2, range / 2));
|
||||
return GetEntitiesIntersecting(_mapManager.GetGrid(position.GridID).ParentMap.Index, aabb);
|
||||
return GetEntitiesIntersecting(_mapManager.GetGrid(position.GridID).ParentMapId, aabb);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -15,8 +15,10 @@ namespace Robust.Server.Maps
|
||||
{
|
||||
public static class YamlGridSerializer
|
||||
{
|
||||
public static YamlMappingNode SerializeGrid(IMapGrid grid)
|
||||
public static YamlMappingNode SerializeGrid(IMapGrid mapGrid)
|
||||
{
|
||||
var grid = (IMapGridInternal) mapGrid;
|
||||
|
||||
var gridn = new YamlMappingNode();
|
||||
var info = new YamlMappingNode();
|
||||
var chunkSeq = new YamlSequenceNode();
|
||||
@@ -32,7 +34,7 @@ namespace Robust.Server.Maps
|
||||
var chunks = grid.GetMapChunks();
|
||||
foreach (var chunk in chunks)
|
||||
{
|
||||
var chunkNode = SerializeChunk(chunk);
|
||||
var chunkNode = SerializeChunk(chunk.Value);
|
||||
chunkSeq.Add(chunkNode);
|
||||
}
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace Robust.Server.Placement
|
||||
foreach (var grid in gridsInArea)
|
||||
{
|
||||
// figure out closest intersect
|
||||
var gridIntersect = gridSearchBox.Intersect(grid.AABBWorld);
|
||||
var gridIntersect = gridSearchBox.Intersect(grid.WorldBounds);
|
||||
var gridDist = (gridIntersect.Center - position).LengthSquared;
|
||||
|
||||
if (gridDist >= distance)
|
||||
|
||||
@@ -38,6 +38,11 @@ namespace Robust.Shared.Maths
|
||||
public Vector2 Size => new Vector2(Width, Height);
|
||||
public Vector2 Center => BottomLeft + Size / 2;
|
||||
|
||||
/// <summary>
|
||||
/// A 1x1 unit box with the origin centered.
|
||||
/// </summary>
|
||||
public static readonly Box2 UnitCentered = new Box2(-0.5f, -0.5f, 0.5f, 0.5f);
|
||||
|
||||
public Box2(Vector2 bottomLeft, Vector2 topRight) : this(bottomLeft.X, bottomLeft.Y, topRight.X, topRight.Y)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace Robust.Shared.Maths
|
||||
{
|
||||
@@ -62,6 +62,22 @@ namespace Robust.Shared.Maths
|
||||
return new Box2i(Left + point.X, Bottom + point.Y, Right + point.X, Top + point.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the smallest rectangle that contains both of the rectangles.
|
||||
/// </summary>
|
||||
public Box2i Union(in Box2i other)
|
||||
{
|
||||
var left = Math.Min(Left, other.Left);
|
||||
var right = Math.Max(Right, other.Right);
|
||||
var bottom = Math.Min(Bottom, other.Bottom);
|
||||
var top = Math.Max(Top, other.Top);
|
||||
|
||||
if (left <= right && bottom <= top)
|
||||
return new Box2i(left, bottom, right, top);
|
||||
|
||||
return new Box2i();
|
||||
}
|
||||
|
||||
// override object.Equals
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects.Components.Transform;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Robust.Shared.Interfaces.Map
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a square 'chunk' of a MapGrid.
|
||||
/// </summary>
|
||||
public interface IMapChunk : IEnumerable<TileRef>
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of tiles per side of the square chunk.
|
||||
/// </summary>
|
||||
ushort ChunkSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The X index of this chunk.
|
||||
/// </summary>
|
||||
int X { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The Y index of this chunk.
|
||||
/// </summary>
|
||||
int Y { get; }
|
||||
|
||||
MapIndices Index { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the tile at the given indices. The tile indices are relative locations to the
|
||||
/// chunk origin, NOT the grid origin.
|
||||
/// </summary>
|
||||
/// <param name="xTile">The X tile index relative to the chunk.</param>
|
||||
/// <param name="yTile">The Y tile index relative to the chunk.</param>
|
||||
/// <returns>A reference to a tile.</returns>
|
||||
TileRef GetTile(ushort xTile, ushort yTile);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all of the tiles in the chunk.
|
||||
/// </summary>
|
||||
/// <param name="ignoreEmpty">Will empty (space) tiles be added to the collection?</param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<TileRef> GetAllTiles(bool ignoreEmpty = true);
|
||||
|
||||
/// <summary>
|
||||
/// Replaces a single tile inside of the chunk.
|
||||
/// </summary>
|
||||
/// <param name="xChunkTile">The X tile index relative to the chunk.</param>
|
||||
/// <param name="yChunkTile">The Y tile index relative to the chunk.</param>
|
||||
/// <param name="tile">The new tile to insert.</param>
|
||||
void SetTile(ushort xChunkTile, ushort yChunkTile, Tile tile);
|
||||
|
||||
/// <summary>
|
||||
/// Transforms Tile indices relative to the grid into tile indices relative to this chunk.
|
||||
/// </summary>
|
||||
/// <param name="gridTile">Tile indices relative to the grid.</param>
|
||||
/// <returns>Tile indices relative to this chunk.</returns>
|
||||
MapIndices GridTileToChunkTile(MapIndices gridTile);
|
||||
|
||||
IEnumerable<SnapGridComponent> GetSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset);
|
||||
|
||||
void AddToSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset, SnapGridComponent snap);
|
||||
void RemoveFromSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset, SnapGridComponent snap);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects.Components.Transform;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
{
|
||||
/// <summary>
|
||||
/// A square section of a <see cref="IMapGrid"/>.
|
||||
/// </summary>
|
||||
public interface IMapChunk : IEnumerable<TileRef>
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of tiles per side of the square chunk.
|
||||
/// </summary>
|
||||
ushort ChunkSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The X index of this chunk inside the <see cref="IMapGrid"/>.
|
||||
/// </summary>
|
||||
int X { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The Y index of this chunk inside the <see cref="IMapGrid"/>.
|
||||
/// </summary>
|
||||
int Y { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The positional indices of this chunk in the <see cref="IMapGrid"/>.
|
||||
/// </summary>
|
||||
MapIndices Indices { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the tile at the given indices.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
TileRef GetTileRef(ushort xIndex, ushort yIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the tile reference at the given indices.
|
||||
/// </summary>
|
||||
/// <param name="indices">The tile indices relative to the chunk origin.</param>
|
||||
/// <returns>A reference to a tile.</returns>
|
||||
TileRef GetTileRef(MapIndices indices);
|
||||
|
||||
Tile GetTile(ushort xIndex, ushort yIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all of the tiles in the chunk, while optionally filtering empty files.
|
||||
/// Returned order is guaranteed to be row-major.
|
||||
/// </summary>
|
||||
/// <param name="ignoreEmpty">Will empty (space) tiles be added to the collection?</param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<TileRef> GetAllTiles(bool ignoreEmpty = true);
|
||||
|
||||
/// <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>
|
||||
void SetTile(ushort xIndex, ushort yIndex, Tile tile);
|
||||
|
||||
/// <summary>
|
||||
/// Transforms Tile indices relative to the grid into tile indices relative to this chunk.
|
||||
/// </summary>
|
||||
/// <param name="gridTile">Tile indices relative to the grid.</param>
|
||||
/// <returns>Tile indices relative to this chunk.</returns>
|
||||
MapIndices GridTileToChunkTile(MapIndices gridTile);
|
||||
|
||||
/// <summary>
|
||||
/// Translates chunk tile indices to grid tile indices.
|
||||
/// </summary>
|
||||
/// <param name="chunkTile">The indices relative to the chunk origin.</param>
|
||||
/// <returns>The indices relative to the grid origin.</returns>
|
||||
MapIndices ChunkTileToGridTile(MapIndices chunkTile);
|
||||
|
||||
IEnumerable<SnapGridComponent> GetSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset);
|
||||
|
||||
void AddToSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset, SnapGridComponent snap);
|
||||
void RemoveFromSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset, SnapGridComponent snap);
|
||||
|
||||
Box2i CalcLocalBounds();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
{
|
||||
/// <inheritdoc />
|
||||
internal interface IMapChunkInternal : IMapChunk
|
||||
{
|
||||
/// <summary>
|
||||
/// The last game simulation tick that this chunk was modified.
|
||||
/// </summary>
|
||||
GameTick LastModifiedTick { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects.Components.Transform;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.Interfaces.Map
|
||||
namespace Robust.Shared.Map
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a collection of tiles in a grid format.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public interface IMapGrid : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// True if we are the default grid of our map.
|
||||
/// </summary>
|
||||
bool IsDefaultGrid { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The map that this grid exists inside of.
|
||||
/// </summary>
|
||||
IMap ParentMap { get; }
|
||||
|
||||
/// <summary>
|
||||
@@ -22,6 +28,9 @@ namespace Robust.Shared.Interfaces.Map
|
||||
/// </summary>
|
||||
MapId ParentMapId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The identifier of this grid.
|
||||
/// </summary>
|
||||
GridId Index { get; }
|
||||
|
||||
/// <summary>
|
||||
@@ -32,7 +41,7 @@ namespace Robust.Shared.Interfaces.Map
|
||||
/// <summary>
|
||||
/// The bounding box of the grid in world coordinates.
|
||||
/// </summary>
|
||||
Box2 AABBWorld { get; }
|
||||
Box2 WorldBounds { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The length of a side of the square chunk in number of tiles.
|
||||
@@ -66,14 +75,14 @@ namespace Robust.Shared.Interfaces.Map
|
||||
/// </summary>
|
||||
/// <param name="worldPos">The location of the tile in coordinates.</param>
|
||||
/// <returns>The tile at the world coordinates.</returns>
|
||||
TileRef GetTile(GridCoordinates worldPos);
|
||||
TileRef GetTileRef(GridCoordinates worldPos);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a tile a the given tile coordinates. This will not create a new chunk.
|
||||
/// Gets a tile a the given grid indices. This will not create a new chunk.
|
||||
/// </summary>
|
||||
/// <param name="tileCoordinates">The location of the tile in coordinates.</param>
|
||||
/// <returns>The tile at the tile coordinates.</returns>
|
||||
TileRef GetTile(MapIndices tileCoordinates);
|
||||
TileRef GetTileRef(MapIndices tileCoordinates);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all tiles in the grid, in row-major order [xTileIndex, yTileIndex].
|
||||
@@ -91,11 +100,8 @@ namespace Robust.Shared.Interfaces.Map
|
||||
/// <summary>
|
||||
/// Modifies a single tile inside of the chunk.
|
||||
/// </summary>
|
||||
/// <param name="worldPos"></param>
|
||||
/// <param name="tileId">The new internal ID of the tile.</param>
|
||||
/// <param name="tileData">The new data of the tile.</param>
|
||||
void SetTile(GridCoordinates worldPos, ushort tileId, ushort tileData = 0);
|
||||
|
||||
/// <param name="gridIndices"></param>
|
||||
/// <param name="tile">The tile to insert at the coordinates.</param>
|
||||
void SetTile(MapIndices gridIndices, Tile tile);
|
||||
|
||||
/// <summary>
|
||||
@@ -122,39 +128,7 @@ namespace Robust.Shared.Interfaces.Map
|
||||
void RemoveFromSnapGridCell(GridCoordinates worldPos, SnapGridOffset offset, SnapGridComponent snap);
|
||||
|
||||
#endregion SnapGridAccess
|
||||
|
||||
#region ChunkAccess
|
||||
|
||||
/// <summary>
|
||||
/// The total number of chunks contained on this grid.
|
||||
/// </summary>
|
||||
int ChunkCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the chunk at the given indices. If the chunk does not exist,
|
||||
/// then a new one is generated that is filled with empty space.
|
||||
/// </summary>
|
||||
/// <param name="xIndex">The X index of the chunk in this grid.</param>
|
||||
/// <param name="yIndex">The Y index of the chunk in this grid.</param>
|
||||
/// <returns>The existing or new chunk.</returns>
|
||||
IMapChunk GetChunk(int xIndex, int yIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the chunk at the given indices. If the chunk does not exist,
|
||||
/// then a new one is generated that is filled with empty space.
|
||||
/// </summary>
|
||||
/// <param name="chunkIndices">The indices of the chunk in this grid.</param>
|
||||
/// <returns>The existing or new chunk.</returns>
|
||||
IMapChunk GetChunk(MapIndices chunkIndices);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all chunks in this grid. This will not generate new chunks.
|
||||
/// </summary>
|
||||
/// <returns>All chunks in the grid.</returns>
|
||||
IEnumerable<IMapChunk> GetMapChunks();
|
||||
|
||||
#endregion ChunkAccess
|
||||
|
||||
|
||||
#region Transforms
|
||||
|
||||
/// <summary>
|
||||
@@ -187,22 +161,29 @@ namespace Robust.Shared.Interfaces.Map
|
||||
|
||||
/// <summary>
|
||||
/// Transforms grid-space tile indices to local coordinates.
|
||||
/// The resulting coordinates are centered on the tile.
|
||||
/// </summary>
|
||||
/// <param name="gridTile"></param>
|
||||
/// <returns></returns>
|
||||
GridCoordinates GridTileToLocal(MapIndices gridTile);
|
||||
|
||||
/// <summary>
|
||||
/// Transforms grid indices into an outvar tile, returns false if no tile is found
|
||||
/// Transforms grid indices into a tile reference, returns false if no tile is found.
|
||||
/// </summary>
|
||||
/// <param name="gridTile">The Grid Tile indices.</param>
|
||||
/// <param name="indices">The Grid Tile indices.</param>
|
||||
/// <param name="tile"></param>
|
||||
/// <returns></returns>
|
||||
bool IndicesToTile(MapIndices indices, out TileRef tile);
|
||||
bool TryGetTileRef(MapIndices indices, out TileRef tile);
|
||||
|
||||
/// <summary>
|
||||
/// Transforms grid tile indices to grid chunk indices.
|
||||
/// Transforms grid tile indices to chunk indices.
|
||||
/// </summary>
|
||||
MapIndices GridTileToGridChunk(MapIndices gridTile);
|
||||
MapIndices GridTileToChunkIndices(MapIndices gridTile);
|
||||
|
||||
/// <summary>
|
||||
/// Transforms local grid coordinates to chunk indices.
|
||||
/// </summary>
|
||||
MapIndices LocalToChunkIndices(GridCoordinates posWorld);
|
||||
|
||||
#endregion Transforms
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
{
|
||||
internal interface IMapGridInternal : IMapGrid
|
||||
{
|
||||
GameTick LastModifiedTick { get; }
|
||||
|
||||
GameTick CurTick { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The total number of chunks contained on this grid.
|
||||
/// </summary>
|
||||
int ChunkCount { get; }
|
||||
|
||||
void NotifyTileChanged(in TileRef tileRef, in Tile oldTile);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the chunk at the given indices. If the chunk does not exist,
|
||||
/// then a new one is generated that is filled with empty space.
|
||||
/// </summary>
|
||||
/// <param name="xIndex">The X index of the chunk in this grid.</param>
|
||||
/// <param name="yIndex">The Y index of the chunk in this grid.</param>
|
||||
/// <returns>The existing or new chunk.</returns>
|
||||
IMapChunkInternal GetChunk(int xIndex, int yIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the chunk at the given indices. If the chunk does not exist,
|
||||
/// then a new one is generated that is filled with empty space.
|
||||
/// </summary>
|
||||
/// <param name="chunkIndices">The indices of the chunk in this grid.</param>
|
||||
/// <returns>The existing or new chunk.</returns>
|
||||
IMapChunkInternal GetChunk(MapIndices chunkIndices);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all chunks in this grid. This will not generate new chunks.
|
||||
/// </summary>
|
||||
/// <returns>All chunks in the grid.</returns>
|
||||
IReadOnlyDictionary<MapIndices, IMapChunkInternal> GetMapChunks();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
{
|
||||
/// <inheritdoc />
|
||||
internal interface IMapManagerInternal : IMapManager
|
||||
{
|
||||
IGameTiming GameTiming { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Raises the OnTileChanged event.
|
||||
/// </summary>
|
||||
/// <param name="tileRef">A reference to the new tile.</param>
|
||||
/// <param name="oldTile">The old tile that got replaced.</param>
|
||||
void RaiseOnTileChanged(TileRef tileRef, Tile oldTile);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects.Components.Transform;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
{
|
||||
/// <inheritdoc />
|
||||
internal class MapChunk : IMapChunkInternal
|
||||
{
|
||||
private readonly IMapGridInternal _grid;
|
||||
private readonly MapIndices _gridIndices;
|
||||
|
||||
private readonly Tile[,] _tiles;
|
||||
private readonly SnapGridCell[,] _snapGrid;
|
||||
|
||||
private Box2i _cachedBounds;
|
||||
|
||||
/// <inheritdoc />
|
||||
public GameTick LastModifiedTick { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of a MapGrid chunk.
|
||||
/// </summary>
|
||||
/// <param name="grid"></param>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
/// <param name="chunkSize"></param>
|
||||
public MapChunk(IMapGridInternal grid, int x, int y, ushort chunkSize)
|
||||
{
|
||||
_grid = grid;
|
||||
LastModifiedTick = grid.CurTick;
|
||||
_gridIndices = new MapIndices(x, y);
|
||||
ChunkSize = chunkSize;
|
||||
|
||||
_tiles = new Tile[ChunkSize, ChunkSize];
|
||||
_snapGrid = new SnapGridCell[ChunkSize, ChunkSize];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ushort ChunkSize { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public int X => _gridIndices.X;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Y => _gridIndices.Y;
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapIndices Indices => _gridIndices;
|
||||
|
||||
/// <inheritdoc />
|
||||
public TileRef GetTileRef(ushort xIndex, ushort yIndex)
|
||||
{
|
||||
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.");
|
||||
|
||||
var indices = ChunkTileToGridTile(new MapIndices(xIndex, yIndex));
|
||||
return new TileRef(_grid.ParentMapId, _grid.Index, indices, _tiles[xIndex, yIndex]);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public TileRef GetTileRef(MapIndices indices)
|
||||
{
|
||||
if (indices.X >= ChunkSize || indices.X < 0 || indices.Y >= ChunkSize || indices.Y < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(indices), "Tile indices out of bounds.");
|
||||
|
||||
var chunkIndices = ChunkTileToGridTile(indices);
|
||||
return new TileRef(_grid.ParentMapId, _grid.Index, chunkIndices, _tiles[indices.X, indices.Y]);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Tile GetTile(ushort xIndex, ushort yIndex)
|
||||
{
|
||||
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.");
|
||||
|
||||
return _tiles[xIndex, yIndex];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TileRef> GetAllTiles(bool ignoreEmpty = true)
|
||||
{
|
||||
for (var x = 0; x < ChunkSize; x++)
|
||||
for (var y = 0; y < ChunkSize; y++)
|
||||
{
|
||||
if (ignoreEmpty && _tiles[x, y].IsEmpty)
|
||||
continue;
|
||||
|
||||
var indices = ChunkTileToGridTile(new MapIndices(x, y));
|
||||
yield return new TileRef(_grid.ParentMapId, _grid.Index, indices.X, indices.Y, _tiles[x, y]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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].TypeId == tile.TypeId)
|
||||
return;
|
||||
|
||||
var gridTile = ChunkTileToGridTile(new MapIndices(xIndex, yIndex));
|
||||
var newTileRef = new TileRef(_grid.ParentMapId, _grid.Index, gridTile, tile);
|
||||
var oldTile = _tiles[xIndex, yIndex];
|
||||
LastModifiedTick = _grid.CurTick;
|
||||
|
||||
_tiles[xIndex, yIndex] = tile;
|
||||
CheckBounds(new Vector2i(xIndex, yIndex), tile.IsEmpty);
|
||||
|
||||
_grid.NotifyTileChanged(newTileRef, oldTile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through all grid tiles.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerator<TileRef> GetEnumerator()
|
||||
{
|
||||
for (var x = 0; x < ChunkSize; x++)
|
||||
for (var y = 0; y < ChunkSize; y++)
|
||||
{
|
||||
if (_tiles[x, y].IsEmpty)
|
||||
continue;
|
||||
|
||||
var gridTile = ChunkTileToGridTile(new MapIndices(x, y));
|
||||
yield return new TileRef(_grid.ParentMapId, _grid.Index, gridTile.X, gridTile.Y, _tiles[x, y]);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapIndices GridTileToChunkTile(MapIndices gridTile)
|
||||
{
|
||||
var size = ChunkSize;
|
||||
var x = MathHelper.Mod(gridTile.X, size);
|
||||
var y = MathHelper.Mod(gridTile.Y, size);
|
||||
return new MapIndices(x, y);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapIndices ChunkTileToGridTile(MapIndices chunkTile)
|
||||
{
|
||||
return chunkTile + _gridIndices * ChunkSize;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<SnapGridComponent> GetSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset)
|
||||
{
|
||||
if (xCell >= ChunkSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(xCell), "Tile indices out of bounds.");
|
||||
|
||||
if (yCell >= ChunkSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(yCell), "Tile indices out of bounds.");
|
||||
|
||||
var cell = _snapGrid[xCell, yCell];
|
||||
var list = offset == SnapGridOffset.Center ? cell.Center : cell.Edge;
|
||||
|
||||
if (list == null)
|
||||
yield break;
|
||||
|
||||
foreach (var element in list)
|
||||
{
|
||||
yield return element;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddToSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset, SnapGridComponent snap)
|
||||
{
|
||||
if (xCell >= ChunkSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(xCell), "Tile indices out of bounds.");
|
||||
|
||||
if (yCell >= ChunkSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(yCell), "Tile indices out of bounds.");
|
||||
|
||||
ref var cell = ref _snapGrid[xCell, yCell];
|
||||
if (offset == SnapGridOffset.Center)
|
||||
{
|
||||
if (cell.Center == null)
|
||||
{
|
||||
cell.Center = new List<SnapGridComponent>(1);
|
||||
}
|
||||
cell.Center.Add(snap);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cell.Edge == null)
|
||||
{
|
||||
cell.Edge = new List<SnapGridComponent>(1);
|
||||
}
|
||||
cell.Edge.Add(snap);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveFromSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset, SnapGridComponent snap)
|
||||
{
|
||||
if (xCell >= ChunkSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(xCell), "Tile indices out of bounds.");
|
||||
|
||||
if (yCell >= ChunkSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(yCell), "Tile indices out of bounds.");
|
||||
|
||||
ref var cell = ref _snapGrid[xCell, yCell];
|
||||
if (offset == SnapGridOffset.Center)
|
||||
{
|
||||
cell.Center?.Remove(snap);
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.Edge?.Remove(snap);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckBounds(in Vector2i indices, bool empty)
|
||||
{
|
||||
var tileBounds = new Box2i(0,0,1, 1).Translated(indices);
|
||||
|
||||
if (!empty)
|
||||
{
|
||||
// placing tiles can only expand the bounds, it is easier to just blind union
|
||||
_cachedBounds = _cachedBounds.Size.Equals(Vector2i.Zero) ? tileBounds : _cachedBounds.Union(tileBounds);
|
||||
}
|
||||
else
|
||||
{
|
||||
// removing tiles can only shrink the bounds, this is not an easy thing to detect
|
||||
ReduceBoundsX(_tiles, tileBounds, ref _cachedBounds);
|
||||
ReduceBoundsY(_tiles, tileBounds, ref _cachedBounds);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReduceBoundsX(in Tile[,] tiles, Box2i tBounds, ref Box2i cBounds)
|
||||
{
|
||||
if (tBounds.Left != cBounds.Left && tBounds.Right != cBounds.Right)
|
||||
return; // nothing to do, we are not on an edge
|
||||
|
||||
var left = tBounds.Left == cBounds.Left;
|
||||
|
||||
// removing a tile can shrink the side more than one tile
|
||||
while (cBounds.Width > 0)
|
||||
{
|
||||
// check if we are the only tile holding the side out
|
||||
if(!AnyTileOnY(tiles, new Vector2i(cBounds.Bottom, cBounds.Top), tBounds.BottomLeft))
|
||||
return; // our removal does not modify the edge, we are done here
|
||||
|
||||
// shrink the chunk bounds
|
||||
int newLeft;
|
||||
int newRight;
|
||||
if (left)
|
||||
{
|
||||
newLeft = cBounds.Left + 1;
|
||||
newRight = cBounds.Right;
|
||||
tBounds = tBounds.Translated(new Vector2i(1, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
newLeft = cBounds.Left;
|
||||
newRight = cBounds.Right - 1;
|
||||
tBounds = tBounds.Translated(new Vector2i(-1, 0));
|
||||
}
|
||||
|
||||
cBounds = new Box2i(newLeft, cBounds.Bottom, newRight, cBounds.Top);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReduceBoundsY(in Tile[,] tiles, Box2i tBounds, ref Box2i cBounds)
|
||||
{
|
||||
if (tBounds.Bottom != cBounds.Bottom && tBounds.Top != cBounds.Top)
|
||||
return; // nothing to do, we are not on an edge
|
||||
|
||||
var bottom = tBounds.Bottom == cBounds.Bottom; // which side we are moving
|
||||
|
||||
// removing a tile can shrink the side more than one tile
|
||||
while (cBounds.Height > 0)
|
||||
{
|
||||
// check if we are the only tile holding the side out
|
||||
if (!AnyTileOnX(tiles, new Vector2i(cBounds.Left, cBounds.Right), tBounds.BottomLeft))
|
||||
return; // our removal does not modify the edge
|
||||
|
||||
// shrink the chunk bounds
|
||||
int newBottom;
|
||||
int newTop;
|
||||
if (bottom)
|
||||
{
|
||||
newBottom = cBounds.Bottom + 1;
|
||||
newTop = cBounds.Top;
|
||||
tBounds = tBounds.Translated(new Vector2i(0, 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
newBottom = cBounds.Bottom;
|
||||
newTop = cBounds.Top - 1;
|
||||
tBounds = tBounds.Translated(new Vector2i(0, -1));
|
||||
}
|
||||
|
||||
cBounds = new Box2i(cBounds.Left, newBottom, cBounds.Right, newTop);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AnyTileOnX(in Tile[,] tiles, Vector2i extents, in Vector2i indices)
|
||||
{
|
||||
var y = indices.Y;
|
||||
for (var x = extents.X; x < extents.Y; x++)
|
||||
{
|
||||
if(tiles[x, y].IsEmpty)
|
||||
continue;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool AnyTileOnY(in Tile[,] tiles, Vector2i extents, in Vector2i indices)
|
||||
{
|
||||
var x = indices.X;
|
||||
for (var y = extents.X; y < extents.Y; y++)
|
||||
{
|
||||
if(tiles[x, y].IsEmpty)
|
||||
continue;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Box2i CalcLocalBounds()
|
||||
{
|
||||
return _cachedBounds;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Chunk {_gridIndices}";
|
||||
}
|
||||
|
||||
private struct SnapGridCell
|
||||
{
|
||||
public List<SnapGridComponent> Center;
|
||||
public List<SnapGridComponent> Edge;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects.Components.Transform;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
{
|
||||
/// <inheritdoc />
|
||||
internal class MapGrid : IMapGridInternal
|
||||
{
|
||||
/// <summary>
|
||||
/// Game tick that the map was created.
|
||||
/// </summary>
|
||||
public GameTick CreatedTick { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Last game tick that the map was modified.
|
||||
/// </summary>
|
||||
public GameTick LastModifiedTick { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public GameTick CurTick => _mapManager.GameTiming.CurTick;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsDefaultGrid => ParentMap.DefaultGrid == this;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMap ParentMap => _mapManager.GetMap(ParentMapId);
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapId ParentMapId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Grid chunks than make up this grid.
|
||||
/// </summary>
|
||||
private readonly Dictionary<MapIndices, IMapChunkInternal> _chunks = new Dictionary<MapIndices, IMapChunkInternal>();
|
||||
|
||||
private readonly IMapManagerInternal _mapManager;
|
||||
private Vector2 _worldPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MapGrid"/> class.
|
||||
/// </summary>
|
||||
/// <param name="mapManager">Reference to the <see cref="MapManager"/> that will manage this grid.</param>
|
||||
/// <param name="gridIndex">Index identifier of this grid.</param>
|
||||
/// <param name="chunkSize">The dimension of this square chunk.</param>
|
||||
/// <param name="snapSize">Distance in world units between the lines on the conceptual snap grid.</param>
|
||||
/// <param name="parentMapId">Parent map identifier.</param>
|
||||
internal MapGrid(IMapManagerInternal mapManager, GridId gridIndex, ushort chunkSize, float snapSize,
|
||||
MapId parentMapId)
|
||||
{
|
||||
_mapManager = mapManager;
|
||||
Index = gridIndex;
|
||||
ChunkSize = chunkSize;
|
||||
SnapSize = snapSize;
|
||||
ParentMapId = parentMapId;
|
||||
LastModifiedTick = CreatedTick = _mapManager.GameTiming.CurTick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the grid.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
// Nothing for now.
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Box2 WorldBounds => LocalBounds.Translated(WorldPosition);
|
||||
|
||||
public Box2 LocalBounds { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ushort ChunkSize { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float SnapSize { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public GridId Index { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The length of the side of a square tile in world units.
|
||||
/// </summary>
|
||||
public ushort TileSize { get; } = 1;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector2 WorldPosition
|
||||
{
|
||||
get => _worldPosition;
|
||||
set
|
||||
{
|
||||
_worldPosition = value;
|
||||
LastModifiedTick = _mapManager.GameTiming.CurTick;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expands the AABB for this grid when a new tile is added. If the tile is already inside the existing AABB,
|
||||
/// nothing happens. If it is outside, the AABB is expanded to fit the new tile.
|
||||
/// </summary>
|
||||
/// <param name="gridTile">The new tile to check.</param>
|
||||
/// <param name="empty"></param>
|
||||
private void UpdateAABB(MapIndices gridTile, bool empty)
|
||||
{
|
||||
LocalBounds = new Box2();
|
||||
foreach (var chunk in _chunks.Values)
|
||||
{
|
||||
var chunkBounds = chunk.CalcLocalBounds();
|
||||
|
||||
if(chunkBounds.Size.Equals(Vector2i.Zero))
|
||||
continue;
|
||||
|
||||
if (LocalBounds.Size == Vector2.Zero)
|
||||
{
|
||||
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
|
||||
LocalBounds = gridBounds;
|
||||
}
|
||||
else
|
||||
{
|
||||
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
|
||||
LocalBounds = LocalBounds.Union(gridBounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void NotifyTileChanged(in TileRef tileRef, in Tile oldTile)
|
||||
{
|
||||
LastModifiedTick = _mapManager.GameTiming.CurTick;
|
||||
UpdateAABB(tileRef.GridIndices, tileRef.Tile.IsEmpty);
|
||||
_mapManager.RaiseOnTileChanged(tileRef, oldTile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool OnSnapCenter(Vector2 position)
|
||||
{
|
||||
return (FloatMath.CloseTo(position.X % SnapSize, 0) && FloatMath.CloseTo(position.Y % SnapSize, 0));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool OnSnapBorder(Vector2 position)
|
||||
{
|
||||
return (FloatMath.CloseTo(position.X % SnapSize, SnapSize / 2) && FloatMath.CloseTo(position.Y % SnapSize, SnapSize / 2));
|
||||
}
|
||||
|
||||
#region TileAccess
|
||||
|
||||
/// <inheritdoc />
|
||||
public TileRef GetTileRef(GridCoordinates worldPos)
|
||||
{
|
||||
return GetTileRef(WorldToTile(worldPos));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public TileRef GetTileRef(MapIndices 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(ParentMapId, Index, tileCoordinates.X, tileCoordinates.Y, default);
|
||||
}
|
||||
|
||||
var chunkTileIndices = output.GridTileToChunkTile(tileCoordinates);
|
||||
return output.GetTileRef((ushort)chunkTileIndices.X, (ushort)chunkTileIndices.Y);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TileRef> GetAllTiles(bool ignoreSpace = true)
|
||||
{
|
||||
foreach (var kvChunk in _chunks)
|
||||
{
|
||||
foreach (var tileRef in kvChunk.Value)
|
||||
{
|
||||
if (!tileRef.Tile.IsEmpty)
|
||||
yield return tileRef;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetTile(GridCoordinates worldPos, Tile tile)
|
||||
{
|
||||
var localTile = WorldToTile(worldPos);
|
||||
SetTile(new MapIndices(localTile.X, localTile.Y), tile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetTile(MapIndices gridIndices, Tile tile)
|
||||
{
|
||||
var (chunk, chunkTile) = ChunkAndOffsetForTile(gridIndices);
|
||||
chunk.SetTile((ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TileRef> GetTilesIntersecting(Box2 worldArea, bool ignoreEmpty = true, Predicate<TileRef> predicate = null)
|
||||
{
|
||||
//TODO: needs world -> local -> tile translations.
|
||||
var gridTileLb = new MapIndices((int)Math.Floor(worldArea.Left), (int)Math.Floor(worldArea.Bottom));
|
||||
var gridTileRt = new MapIndices((int)Math.Floor(worldArea.Right), (int)Math.Floor(worldArea.Top));
|
||||
|
||||
var tiles = new List<TileRef>();
|
||||
|
||||
for (var x = gridTileLb.X; x <= gridTileRt.X; x++)
|
||||
{
|
||||
for (var y = gridTileLb.Y; y <= gridTileRt.Y; y++)
|
||||
{
|
||||
var gridChunk = GridTileToChunkIndices(new MapIndices(x, y));
|
||||
|
||||
if (_chunks.TryGetValue(gridChunk, out var chunk))
|
||||
{
|
||||
var chunkTile = chunk.GridTileToChunkTile(new MapIndices(x, y));
|
||||
var tile = chunk.GetTileRef((ushort)chunkTile.X, (ushort)chunkTile.Y);
|
||||
|
||||
if (ignoreEmpty && tile.Tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
if (predicate == null || predicate(tile))
|
||||
{
|
||||
tiles.Add(tile);
|
||||
}
|
||||
}
|
||||
else if (!ignoreEmpty)
|
||||
{
|
||||
var tile = new TileRef(ParentMapId, Index, x, y, new Tile());
|
||||
|
||||
if (predicate == null || predicate(tile))
|
||||
{
|
||||
tiles.Add(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tiles;
|
||||
}
|
||||
|
||||
#endregion TileAccess
|
||||
|
||||
#region ChunkAccess
|
||||
|
||||
/// <summary>
|
||||
/// The total number of allocated chunks in the grid.
|
||||
/// </summary>
|
||||
public int ChunkCount => _chunks.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMapChunkInternal GetChunk(int xIndex, int yIndex)
|
||||
{
|
||||
return GetChunk(new MapIndices(xIndex, yIndex));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMapChunkInternal GetChunk(MapIndices chunkIndices)
|
||||
{
|
||||
if (_chunks.TryGetValue(chunkIndices, out var output))
|
||||
return output;
|
||||
|
||||
return _chunks[chunkIndices] = new MapChunk(this, chunkIndices.X, chunkIndices.Y, ChunkSize);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyDictionary<MapIndices, IMapChunkInternal> GetMapChunks()
|
||||
{
|
||||
return _chunks;
|
||||
}
|
||||
|
||||
#endregion ChunkAccess
|
||||
|
||||
#region SnapGridAccess
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<SnapGridComponent> GetSnapGridCell(GridCoordinates worldPos, SnapGridOffset offset)
|
||||
{
|
||||
return GetSnapGridCell(SnapGridCellFor(worldPos, offset), offset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<SnapGridComponent> GetSnapGridCell(MapIndices pos, SnapGridOffset offset)
|
||||
{
|
||||
var (chunk, chunkTile) = ChunkAndOffsetForTile(pos);
|
||||
return chunk.GetSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, offset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapIndices SnapGridCellFor(GridCoordinates worldPos, SnapGridOffset offset)
|
||||
{
|
||||
var local = worldPos.ConvertToGrid(_mapManager, this);
|
||||
if (offset == SnapGridOffset.Edge)
|
||||
{
|
||||
local = local.Offset(new Vector2(TileSize / 2f, TileSize / 2f));
|
||||
}
|
||||
var x = (int)Math.Floor(local.X / TileSize);
|
||||
var y = (int)Math.Floor(local.Y / TileSize);
|
||||
return new MapIndices(x, y);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddToSnapGridCell(MapIndices pos, SnapGridOffset offset, SnapGridComponent snap)
|
||||
{
|
||||
var (chunk, chunkTile) = ChunkAndOffsetForTile(pos);
|
||||
chunk.AddToSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, offset, snap);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddToSnapGridCell(GridCoordinates worldPos, SnapGridOffset offset, SnapGridComponent snap)
|
||||
{
|
||||
AddToSnapGridCell(SnapGridCellFor(worldPos, offset), offset, snap);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveFromSnapGridCell(MapIndices pos, SnapGridOffset offset, SnapGridComponent snap)
|
||||
{
|
||||
var (chunk, chunkTile) = ChunkAndOffsetForTile(pos);
|
||||
chunk.RemoveFromSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, offset, snap);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveFromSnapGridCell(GridCoordinates worldPos, SnapGridOffset offset, SnapGridComponent snap)
|
||||
{
|
||||
RemoveFromSnapGridCell(SnapGridCellFor(worldPos, offset), offset, snap);
|
||||
}
|
||||
|
||||
private (IMapChunk, MapIndices) ChunkAndOffsetForTile(MapIndices pos)
|
||||
{
|
||||
var gridChunkIndices = GridTileToChunkIndices(pos);
|
||||
var chunk = GetChunk(gridChunkIndices);
|
||||
var chunkTile = chunk.GridTileToChunkTile(pos);
|
||||
return (chunk, chunkTile);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Transforms
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector2 WorldToLocal(Vector2 posWorld)
|
||||
{
|
||||
return posWorld - WorldPosition;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public GridCoordinates LocalToWorld(GridCoordinates posLocal)
|
||||
{
|
||||
return new GridCoordinates(posLocal.Position + WorldPosition, _mapManager.GetGrid(posLocal.GridID).ParentMap);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector2 ConvertToWorld(Vector2 posLocal)
|
||||
{
|
||||
return posLocal + WorldPosition;
|
||||
}
|
||||
|
||||
public MapIndices 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 MapIndices(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms global world coordinates to tile indices relative to grid origin.
|
||||
/// </summary>
|
||||
public MapIndices WorldToTile(GridCoordinates posWorld)
|
||||
{
|
||||
var local = posWorld.ConvertToGrid(_mapManager, this);
|
||||
var x = (int)Math.Floor(local.X / TileSize);
|
||||
var y = (int)Math.Floor(local.Y / TileSize);
|
||||
return new MapIndices(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms global world coordinates to chunk indices relative to grid origin.
|
||||
/// </summary>
|
||||
public MapIndices LocalToChunkIndices(GridCoordinates posWorld)
|
||||
{
|
||||
var local = posWorld.ConvertToGrid(_mapManager, this);
|
||||
var x = (int)Math.Floor(local.X / (TileSize * ChunkSize));
|
||||
var y = (int)Math.Floor(local.Y / (TileSize * ChunkSize));
|
||||
return new MapIndices(x, y);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapIndices GridTileToChunkIndices(MapIndices gridTile)
|
||||
{
|
||||
var x = (int)Math.Floor(gridTile.X / (float)ChunkSize);
|
||||
var y = (int)Math.Floor(gridTile.Y / (float)ChunkSize);
|
||||
|
||||
return new MapIndices(x, y);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public GridCoordinates GridTileToLocal(MapIndices gridTile)
|
||||
{
|
||||
return new GridCoordinates(gridTile.X * TileSize + (TileSize / 2f), gridTile.Y * TileSize + (TileSize / 2f), this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetTileRef(MapIndices 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 = chunk.GetTileRef(cTileIndices);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion Transforms
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
@@ -87,5 +88,15 @@ namespace Robust.Shared.Map
|
||||
{
|
||||
return X ^ (Y * 23011);
|
||||
}
|
||||
|
||||
public static implicit operator Vector2i(in MapIndices indices)
|
||||
{
|
||||
return new Vector2i(indices.X, indices.Y);
|
||||
}
|
||||
|
||||
public static implicit operator MapIndices(in Vector2i indices)
|
||||
{
|
||||
return new MapIndices(indices.X, indices.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects.Components.Transform;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
{
|
||||
public partial class MapManager
|
||||
{
|
||||
/// <summary>
|
||||
/// A square section of the map.
|
||||
/// </summary>
|
||||
internal class Chunk : IMapChunk
|
||||
{
|
||||
internal GameTick LastModifiedTick { get; private set; }
|
||||
private readonly MapGrid _grid;
|
||||
private readonly MapIndices _gridIndices;
|
||||
private readonly MapManager _mapManager;
|
||||
|
||||
internal readonly Tile[,] _tiles;
|
||||
private readonly SnapGridCell[,] _snapGrid;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of a MapGrid chunk.
|
||||
/// </summary>
|
||||
/// <param name="manager"></param>
|
||||
/// <param name="grid"></param>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
/// <param name="chunkSize"></param>
|
||||
public Chunk(MapManager manager, MapGrid grid, int x, int y, ushort chunkSize)
|
||||
{
|
||||
_mapManager = manager;
|
||||
LastModifiedTick = _mapManager._gameTiming.CurTick;
|
||||
ChunkSize = chunkSize;
|
||||
_grid = grid;
|
||||
_gridIndices = new MapIndices(x, y);
|
||||
|
||||
_tiles = new Tile[ChunkSize, ChunkSize];
|
||||
_snapGrid = new SnapGridCell[ChunkSize, ChunkSize];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ushort ChunkSize { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public int X => _gridIndices.X;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Y => _gridIndices.Y;
|
||||
|
||||
public MapIndices Index => new MapIndices(X, Y);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the tile at the given indices. The tile indices are relative locations to the chunk origin,
|
||||
/// NOT local to the grid.
|
||||
/// </summary>
|
||||
/// <param name="xTile">The X tile index relative to the chunk origin.</param>
|
||||
/// <param name="yTile">The Y tile index relative to the chunk origin.</param>
|
||||
/// <returns>A reference to a tile.</returns>
|
||||
public TileRef GetTile(ushort xTile, ushort yTile)
|
||||
{
|
||||
// array out of bounds
|
||||
if (xTile >= ChunkSize || yTile >= ChunkSize)
|
||||
throw new ArgumentOutOfRangeException("Tile indices out of bounds.");
|
||||
|
||||
var indices = ChunkTileToGridTile(new MapIndices(xTile, yTile));
|
||||
return new TileRef(_grid.ParentMapId, _grid.Index, indices.X, indices.Y, _tiles[xTile, yTile]);
|
||||
}
|
||||
public TileRef GetTile(MapIndices indices)
|
||||
{
|
||||
// array out of bounds
|
||||
if (indices.X >= ChunkSize || indices.X < 0 || indices.Y >= ChunkSize || indices.Y < 0)
|
||||
throw new ArgumentOutOfRangeException("Tile indices out of bounds.");
|
||||
|
||||
return new TileRef(_grid.ParentMapId, _grid.Index, indices.X, indices.Y, _tiles[indices.X, indices.Y]);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Enumerate over the chunk instead.")]
|
||||
public IEnumerable<TileRef> GetAllTiles(bool ignoreEmpty = true)
|
||||
{
|
||||
for (var x = 0; x < ChunkSize; x++)
|
||||
for (var y = 0; y < ChunkSize; y++)
|
||||
{
|
||||
if (_tiles[x, y].IsEmpty)
|
||||
continue;
|
||||
|
||||
var indices = ChunkTileToGridTile(new MapIndices(x, y));
|
||||
yield return new TileRef(_grid.ParentMapId, _grid.Index, indices.X, indices.Y, _tiles[x, y]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetTile(ushort xChunkTile, ushort yChunkTile, Tile tile)
|
||||
{
|
||||
if (xChunkTile >= ChunkSize || yChunkTile >= ChunkSize)
|
||||
throw new ArgumentException("Tile indices out of bounds.");
|
||||
|
||||
// same tile, no point to continue
|
||||
if (_tiles[xChunkTile, yChunkTile].TypeId == tile.TypeId)
|
||||
return;
|
||||
|
||||
var gridTile = ChunkTileToGridTile(new MapIndices(xChunkTile, yChunkTile));
|
||||
var newTileRef = new TileRef(_grid.ParentMapId, _grid.Index, gridTile.X, gridTile.Y, tile);
|
||||
var oldTile = _tiles[xChunkTile, yChunkTile];
|
||||
_grid.LastModifiedTick = LastModifiedTick = _mapManager._gameTiming.CurTick;
|
||||
_mapManager.RaiseOnTileChanged(newTileRef, oldTile);
|
||||
_grid.UpdateAABB(gridTile);
|
||||
|
||||
_tiles[xChunkTile, yChunkTile] = tile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through all grid tiles.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerator<TileRef> GetEnumerator()
|
||||
{
|
||||
for (var x = 0; x < ChunkSize; x++)
|
||||
for (var y = 0; y < ChunkSize; y++)
|
||||
{
|
||||
var gridTile = ChunkTileToGridTile(new MapIndices(x, y));
|
||||
yield return new TileRef(_grid.ParentMapId, _grid.Index, gridTile.X, gridTile.Y, _tiles[x, y]);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapIndices GridTileToChunkTile(MapIndices gridTile)
|
||||
{
|
||||
var size = ChunkSize;
|
||||
var x = MathHelper.Mod(gridTile.X, size);
|
||||
var y = MathHelper.Mod(gridTile.Y, size);
|
||||
return new MapIndices(x, y);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetTile(ushort xChunkTile, ushort yChunkTile, ushort tileId, ushort tileData = 0)
|
||||
{
|
||||
SetTile(xChunkTile, yChunkTile, new Tile(tileId, tileData));
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<SnapGridComponent> GetSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset)
|
||||
{
|
||||
var cell = _snapGrid[xCell, yCell];
|
||||
List<SnapGridComponent> list;
|
||||
if (offset == SnapGridOffset.Center)
|
||||
{
|
||||
list = cell.Center;
|
||||
}
|
||||
else
|
||||
{
|
||||
list = cell.Edge;
|
||||
}
|
||||
|
||||
if (list != null)
|
||||
{
|
||||
foreach (var element in list)
|
||||
{
|
||||
yield return element;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddToSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset, SnapGridComponent snap)
|
||||
{
|
||||
ref var cell = ref _snapGrid[xCell, yCell];
|
||||
if (offset == SnapGridOffset.Center)
|
||||
{
|
||||
if (cell.Center == null)
|
||||
{
|
||||
cell.Center = new List<SnapGridComponent>(1);
|
||||
}
|
||||
cell.Center.Add(snap);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cell.Edge == null)
|
||||
{
|
||||
cell.Edge = new List<SnapGridComponent>(1);
|
||||
}
|
||||
cell.Edge.Add(snap);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveFromSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset, SnapGridComponent snap)
|
||||
{
|
||||
ref var cell = ref _snapGrid[xCell, yCell];
|
||||
if (offset == SnapGridOffset.Center)
|
||||
{
|
||||
cell.Center?.Remove(snap);
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.Edge?.Remove(snap);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translates chunk tile indices to grid tile indices.
|
||||
/// </summary>
|
||||
/// <param name="chunkTile">The indices relative to the chunk origin.</param>
|
||||
/// <returns>The indices relative to the grid origin.</returns>
|
||||
private MapIndices ChunkTileToGridTile(MapIndices chunkTile)
|
||||
{
|
||||
return chunkTile + _gridIndices * ChunkSize;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Chunk {_gridIndices}, {ChunkSize}";
|
||||
}
|
||||
|
||||
private struct SnapGridCell
|
||||
{
|
||||
public List<SnapGridComponent> Center;
|
||||
public List<SnapGridComponent> Edge;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ namespace Robust.Shared.Map
|
||||
public IMapGrid FindGridAt(Vector2 worldPos)
|
||||
{
|
||||
foreach (var kvGrid in _grids)
|
||||
if (kvGrid.Value.AABBWorld.Contains(worldPos) && kvGrid.Value != DefaultGrid)
|
||||
if (kvGrid.Value.WorldBounds.Contains(worldPos) && kvGrid.Value != DefaultGrid)
|
||||
return kvGrid.Value;
|
||||
return DefaultGrid;
|
||||
}
|
||||
@@ -89,7 +89,7 @@ namespace Robust.Shared.Map
|
||||
{
|
||||
var gridList = new List<MapGrid>();
|
||||
foreach (var kvGrid in _grids)
|
||||
if (kvGrid.Value.AABBWorld.Intersects(worldArea))
|
||||
if (kvGrid.Value.WorldBounds.Intersects(worldArea))
|
||||
gridList.Add(kvGrid.Value);
|
||||
return gridList;
|
||||
}
|
||||
|
||||
@@ -1,418 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects.Components.Transform;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
{
|
||||
/// <inheritdoc cref="IMapManager"/>
|
||||
public partial class MapManager
|
||||
{
|
||||
/// <inheritdoc />
|
||||
internal class MapGrid : IMapGrid
|
||||
{
|
||||
/// <summary>
|
||||
/// Game tick that the map was created.
|
||||
/// </summary>
|
||||
public GameTick CreatedTick { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Last game tick that the map was modified.
|
||||
/// </summary>
|
||||
public GameTick LastModifiedTick { get; internal set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsDefaultGrid => ParentMap.DefaultGrid == this;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMap ParentMap => _mapManager.GetMap(ParentMapId);
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapId ParentMapId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Grid chunks than make up this grid.
|
||||
/// </summary>
|
||||
internal readonly Dictionary<MapIndices, Chunk> _chunks = new Dictionary<MapIndices, Chunk>();
|
||||
|
||||
private readonly MapManager _mapManager;
|
||||
private Vector2 _worldPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MapGrid"/> class.
|
||||
/// </summary>
|
||||
/// <param name="mapManager">Reference to the <see cref="MapManager"/> that will manage this grid.</param>
|
||||
/// <param name="gridIndex">Index identifier of this grid.</param>
|
||||
/// <param name="chunkSize">The dimension of this square chunk.</param>
|
||||
/// <param name="snapSize">Distance in world units between the lines on the conceptual snap grid.</param>
|
||||
/// <param name="parentMapId">Parent map identifier.</param>
|
||||
internal MapGrid(MapManager mapManager, GridId gridIndex, ushort chunkSize, float snapSize, MapId parentMapId)
|
||||
{
|
||||
_mapManager = mapManager;
|
||||
Index = gridIndex;
|
||||
ChunkSize = chunkSize;
|
||||
SnapSize = snapSize;
|
||||
ParentMapId = parentMapId;
|
||||
LastModifiedTick = CreatedTick = _mapManager._gameTiming.CurTick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the grid.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
// Nothing for now.
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Box2 AABBWorld { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ushort ChunkSize { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float SnapSize { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public GridId Index { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The length of the side of a square tile in world units.
|
||||
/// </summary>
|
||||
public ushort TileSize { get; } = 1;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector2 WorldPosition
|
||||
{
|
||||
get => _worldPosition;
|
||||
set
|
||||
{
|
||||
_worldPosition = value;
|
||||
LastModifiedTick = _mapManager._gameTiming.CurTick;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expands the AABB for this grid when a new tile is added. If the tile is already inside the existing AABB,
|
||||
/// nothing happens. If it is outside, the AABB is expanded to fit the new tile.
|
||||
/// </summary>
|
||||
/// <param name="gridTile">The new tile to check.</param>
|
||||
public void UpdateAABB(MapIndices gridTile)
|
||||
{
|
||||
var worldPos = GridTileToLocal(gridTile).ToWorld(_mapManager);
|
||||
|
||||
if (AABBWorld.Contains(worldPos.Position))
|
||||
return;
|
||||
|
||||
// rect union
|
||||
var a = AABBWorld;
|
||||
var b = worldPos;
|
||||
|
||||
var minX = Math.Min(a.Left, b.X);
|
||||
var maxX = Math.Max(a.Right, b.X);
|
||||
|
||||
var minY = Math.Min(a.Bottom, b.Y);
|
||||
var maxY = Math.Max(a.Top, b.Y);
|
||||
|
||||
AABBWorld = Box2.FromDimensions(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool OnSnapCenter(Vector2 position)
|
||||
{
|
||||
return (FloatMath.CloseTo(position.X % SnapSize, 0) && FloatMath.CloseTo(position.Y % SnapSize, 0));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool OnSnapBorder(Vector2 position)
|
||||
{
|
||||
return (FloatMath.CloseTo(position.X % SnapSize, SnapSize / 2) && FloatMath.CloseTo(position.Y % SnapSize, SnapSize / 2));
|
||||
}
|
||||
|
||||
#region TileAccess
|
||||
|
||||
/// <inheritdoc />
|
||||
public TileRef GetTile(GridCoordinates worldPos)
|
||||
{
|
||||
return GetTile(WorldToTile(worldPos));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public TileRef GetTile(MapIndices tileCoordinates)
|
||||
{
|
||||
var chunkIndices = GridTileToGridChunk(tileCoordinates);
|
||||
|
||||
if (!_chunks.TryGetValue(chunkIndices, out var output))
|
||||
{
|
||||
// Chunk doesn't exist, return a tileRef to an empty (space) tile.
|
||||
return new TileRef(ParentMapId, Index, tileCoordinates.X, tileCoordinates.Y, default);
|
||||
}
|
||||
|
||||
var chunkTileIndices = output.GridTileToChunkTile(tileCoordinates);
|
||||
return output.GetTile((ushort)chunkTileIndices.X, (ushort)chunkTileIndices.Y);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TileRef> GetAllTiles(bool ignoreSpace = true)
|
||||
{
|
||||
foreach (var kvChunk in _chunks)
|
||||
{
|
||||
foreach (var tileRef in kvChunk.Value)
|
||||
{
|
||||
if (!tileRef.Tile.IsEmpty)
|
||||
yield return tileRef;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetTile(GridCoordinates worldPos, Tile tile)
|
||||
{
|
||||
var localTile = WorldToTile(worldPos);
|
||||
SetTile(localTile.X, localTile.Y, tile);
|
||||
}
|
||||
|
||||
private void SetTile(int xIndex, int yIndex, Tile tile)
|
||||
{
|
||||
var (chunk, chunkTile) = ChunkAndOffsetForTile(new MapIndices(xIndex, yIndex));
|
||||
chunk.SetTile((ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetTile(GridCoordinates worldPos, ushort tileId, ushort tileData = 0)
|
||||
{
|
||||
SetTile(worldPos, new Tile(tileId, tileData));
|
||||
}
|
||||
|
||||
public void SetTile(MapIndices gridIndices, Tile tile)
|
||||
{
|
||||
var (chunk, chunkTile) = ChunkAndOffsetForTile(gridIndices);
|
||||
chunk.SetTile((ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TileRef> GetTilesIntersecting(Box2 worldArea, bool ignoreEmpty = true, Predicate<TileRef> predicate = null)
|
||||
{
|
||||
//TODO: needs world -> local -> tile translations.
|
||||
var gridTileLb = new MapIndices((int)Math.Floor(worldArea.Left), (int)Math.Floor(worldArea.Bottom));
|
||||
var gridTileRt = new MapIndices((int)Math.Floor(worldArea.Right), (int)Math.Floor(worldArea.Top));
|
||||
|
||||
var tiles = new List<TileRef>();
|
||||
|
||||
for (var x = gridTileLb.X; x <= gridTileRt.X; x++)
|
||||
{
|
||||
for (var y = gridTileLb.Y; y <= gridTileRt.Y; y++)
|
||||
{
|
||||
var gridChunk = GridTileToGridChunk(new MapIndices(x, y));
|
||||
|
||||
if (_chunks.TryGetValue(gridChunk, out var chunk))
|
||||
{
|
||||
var chunkTile = chunk.GridTileToChunkTile(new MapIndices(x, y));
|
||||
var tile = chunk.GetTile((ushort)chunkTile.X, (ushort)chunkTile.Y);
|
||||
|
||||
if (ignoreEmpty && tile.Tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
if (predicate == null || predicate(tile))
|
||||
{
|
||||
tiles.Add(tile);
|
||||
}
|
||||
}
|
||||
else if (!ignoreEmpty)
|
||||
{
|
||||
var tile = new TileRef(ParentMapId, Index, x, y, new Tile());
|
||||
|
||||
if (predicate == null || predicate(tile))
|
||||
{
|
||||
tiles.Add(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tiles;
|
||||
}
|
||||
|
||||
#endregion TileAccess
|
||||
|
||||
#region ChunkAccess
|
||||
|
||||
/// <summary>
|
||||
/// The total number of allocated chunks in the grid.
|
||||
/// </summary>
|
||||
public int ChunkCount => _chunks.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMapChunk GetChunk(int xIndex, int yIndex)
|
||||
{
|
||||
return GetChunk(new MapIndices(xIndex, yIndex));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMapChunk GetChunk(MapIndices chunkIndices)
|
||||
{
|
||||
if (_chunks.TryGetValue(chunkIndices, out var output))
|
||||
return output;
|
||||
|
||||
return _chunks[chunkIndices] = new Chunk(_mapManager, this, chunkIndices.X, chunkIndices.Y, ChunkSize);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IMapChunk> GetMapChunks()
|
||||
{
|
||||
foreach (var kvChunk in _chunks)
|
||||
{
|
||||
yield return kvChunk.Value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion ChunkAccess
|
||||
|
||||
#region SnapGridAccess
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<SnapGridComponent> GetSnapGridCell(GridCoordinates worldPos, SnapGridOffset offset)
|
||||
{
|
||||
return GetSnapGridCell(SnapGridCellFor(worldPos, offset), offset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<SnapGridComponent> GetSnapGridCell(MapIndices pos, SnapGridOffset offset)
|
||||
{
|
||||
var (chunk, chunkTile) = ChunkAndOffsetForTile(pos);
|
||||
return chunk.GetSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, offset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapIndices SnapGridCellFor(GridCoordinates worldPos, SnapGridOffset offset)
|
||||
{
|
||||
var local = worldPos.ConvertToGrid(_mapManager, this);
|
||||
if (offset == SnapGridOffset.Edge)
|
||||
{
|
||||
local = local.Offset(new Vector2(TileSize / 2f, TileSize / 2f));
|
||||
}
|
||||
var x = (int)Math.Floor(local.X / TileSize);
|
||||
var y = (int)Math.Floor(local.Y / TileSize);
|
||||
return new MapIndices(x, y);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddToSnapGridCell(MapIndices pos, SnapGridOffset offset, SnapGridComponent snap)
|
||||
{
|
||||
var (chunk, chunkTile) = ChunkAndOffsetForTile(pos);
|
||||
chunk.AddToSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, offset, snap);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddToSnapGridCell(GridCoordinates worldPos, SnapGridOffset offset, SnapGridComponent snap)
|
||||
{
|
||||
AddToSnapGridCell(SnapGridCellFor(worldPos, offset), offset, snap);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveFromSnapGridCell(MapIndices pos, SnapGridOffset offset, SnapGridComponent snap)
|
||||
{
|
||||
var (chunk, chunkTile) = ChunkAndOffsetForTile(pos);
|
||||
chunk.RemoveFromSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, offset, snap);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveFromSnapGridCell(GridCoordinates worldPos, SnapGridOffset offset, SnapGridComponent snap)
|
||||
{
|
||||
RemoveFromSnapGridCell(SnapGridCellFor(worldPos, offset), offset, snap);
|
||||
}
|
||||
|
||||
private (IMapChunk, MapIndices) ChunkAndOffsetForTile(MapIndices pos)
|
||||
{
|
||||
var gridChunkIndices = GridTileToGridChunk(pos);
|
||||
var chunk = GetChunk(gridChunkIndices);
|
||||
var chunkTile = chunk.GridTileToChunkTile(pos);
|
||||
return (chunk, chunkTile);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Transforms
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector2 WorldToLocal(Vector2 posWorld)
|
||||
{
|
||||
return posWorld - WorldPosition;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public GridCoordinates LocalToWorld(GridCoordinates posLocal)
|
||||
{
|
||||
return new GridCoordinates(posLocal.Position + WorldPosition, _mapManager.GetGrid(posLocal.GridID).ParentMap);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector2 ConvertToWorld(Vector2 posLocal)
|
||||
{
|
||||
return posLocal + WorldPosition;
|
||||
}
|
||||
|
||||
public MapIndices 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 MapIndices(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms global world coordinates to tile indices relative to grid origin.
|
||||
/// </summary>
|
||||
public MapIndices WorldToTile(GridCoordinates posWorld)
|
||||
{
|
||||
var local = posWorld.ConvertToGrid(_mapManager, this);
|
||||
var x = (int)Math.Floor(local.X / TileSize);
|
||||
var y = (int)Math.Floor(local.Y / TileSize);
|
||||
return new MapIndices(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms global world coordinates to chunk indices relative to grid origin.
|
||||
/// </summary>
|
||||
public MapIndices WorldToChunk(GridCoordinates posWorld)
|
||||
{
|
||||
var local = posWorld.ConvertToGrid(_mapManager, this);
|
||||
var x = (int)Math.Floor(local.X / (TileSize * ChunkSize));
|
||||
var y = (int)Math.Floor(local.Y / (TileSize * ChunkSize));
|
||||
return new MapIndices(x, y);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapIndices GridTileToGridChunk(MapIndices gridTile)
|
||||
{
|
||||
var x = (int)Math.Floor(gridTile.X / (float)ChunkSize);
|
||||
var y = (int)Math.Floor(gridTile.Y / (float)ChunkSize);
|
||||
|
||||
return new MapIndices(x, y);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public GridCoordinates GridTileToLocal(MapIndices gridTile)
|
||||
{
|
||||
return new GridCoordinates(gridTile.X * TileSize + (TileSize / 2), gridTile.Y * TileSize + (TileSize / 2), this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IndicesToTile(MapIndices indices, out TileRef tile)
|
||||
{
|
||||
var chunkIndices = new MapIndices(indices.X / ChunkSize, indices.Y / ChunkSize);
|
||||
if (!_chunks.ContainsKey(chunkIndices))
|
||||
{
|
||||
tile = new TileRef(); //Nothing should ever use or access this, bool check should occur first
|
||||
return false;
|
||||
}
|
||||
var chunk = _chunks[chunkIndices];
|
||||
tile = chunk.GetTile(new MapIndices(indices.X % ChunkSize, indices.Y % ChunkSize));
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion Transforms
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ namespace Robust.Shared.Map
|
||||
}
|
||||
|
||||
var chunkData = new List<GameStateMapData.ChunkDatum>();
|
||||
foreach (var (index, chunk) in grid._chunks)
|
||||
foreach (var (index, chunk) in grid.GetMapChunks())
|
||||
{
|
||||
if (chunk.LastModifiedTick < fromTick)
|
||||
{
|
||||
@@ -45,7 +45,7 @@ namespace Robust.Shared.Map
|
||||
for (var x = 0; x < grid.ChunkSize; x++)
|
||||
for (var y = 0; y < grid.ChunkSize; y++)
|
||||
{
|
||||
tileBuffer[x * grid.ChunkSize + y] = chunk._tiles[x, y];
|
||||
tileBuffer[x * grid.ChunkSize + y] = chunk.GetTile((ushort) x, (ushort) y);
|
||||
}
|
||||
|
||||
chunkData.Add(new GameStateMapData.ChunkDatum(index, tileBuffer));
|
||||
@@ -153,7 +153,7 @@ namespace Robust.Shared.Map
|
||||
for (ushort y = 0; y < grid.ChunkSize; y++)
|
||||
{
|
||||
var tile = chunkData.TileData[counter++];
|
||||
if (chunk.GetTile(x, y).Tile != tile)
|
||||
if (chunk.GetTileRef(x, y).Tile != tile)
|
||||
{
|
||||
chunk.SetTile(x, y, tile);
|
||||
modified.Add((new MapIndices(chunk.X * grid.ChunkSize + x, chunk.Y * grid.ChunkSize + y), tile));
|
||||
|
||||
@@ -9,10 +9,12 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class MapManager : IMapManager, IPostInjectInit
|
||||
/// <inheritdoc cref="IMapManager"/>
|
||||
public partial class MapManager : IMapManagerInternal, IPostInjectInit
|
||||
{
|
||||
[Dependency] private protected readonly IGameTiming _gameTiming;
|
||||
[Dependency] private readonly IGameTiming _gameTiming;
|
||||
|
||||
public IGameTiming GameTiming => _gameTiming;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMap DefaultMap => GetMap(MapId.Nullspace);
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Robust.Shared.Map
|
||||
public readonly MapId MapIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Identifier of the <see cref="MapManager.MapGrid"/> this Tile belongs to.
|
||||
/// Identifier of the <see cref="MapGrid"/> this Tile belongs to.
|
||||
/// </summary>
|
||||
public readonly GridId GridIndex;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
@@ -171,8 +171,7 @@
|
||||
<Compile Include="Interfaces\Resources\IResourceManager.cs" />
|
||||
<Compile Include="Interfaces\Resources\IWritableDirProvider.cs" />
|
||||
<Compile Include="Interfaces\Serialization\IRobustSerializer.cs" />
|
||||
<Compile Include="Interfaces\Map\IMapChunk.cs" />
|
||||
<Compile Include="Interfaces\Map\IMapGrid.cs" />
|
||||
<Compile Include="Map\IMapGrid.cs" />
|
||||
<Compile Include="Interfaces\Map\IMapManager.cs" />
|
||||
<Compile Include="Interfaces\Map\ITileDefinition.cs" />
|
||||
<Compile Include="Interfaces\Map\ITileDefinitionManager.cs" />
|
||||
@@ -194,6 +193,10 @@
|
||||
<Compile Include="Log\LogManager.cs" />
|
||||
<Compile Include="Map\GridId.cs" />
|
||||
<Compile Include="Map\Coordinates.cs" />
|
||||
<Compile Include="Map\IMapChunk.cs" />
|
||||
<Compile Include="Map\IMapChunkInternal.cs" />
|
||||
<Compile Include="Map\IMapGridInternal.cs" />
|
||||
<Compile Include="Map\IMapManagerInternal.cs" />
|
||||
<Compile Include="Map\MapId.cs" />
|
||||
<Compile Include="Map\MapManager.cs" />
|
||||
<Compile Include="Map\MapManager.Network.cs" />
|
||||
@@ -304,7 +307,7 @@
|
||||
<Compile Include="GameObjects\Components\NetIDs.cs" />
|
||||
<Compile Include="Input\BoundKeyMap.cs" />
|
||||
<Compile Include="Map\MapManager.Map.cs" />
|
||||
<Compile Include="Map\MapManager.MapGrid.cs" />
|
||||
<Compile Include="Map\MapGrid.cs" />
|
||||
<Compile Include="ViewVariables\ViewVariablesAttribute.cs" />
|
||||
<Compile Include="ViewVariables\ViewVariablesBlob.cs" />
|
||||
<Compile Include="ViewVariables\ViewVariablesManagerShared.cs" />
|
||||
@@ -374,7 +377,7 @@
|
||||
<ResourceRoot />
|
||||
<Visible>False</Visible>
|
||||
</Resources>
|
||||
<Compile Include="Map\MapManager.Chunk.cs" />
|
||||
<Compile Include="Map\MapChunk.cs" />
|
||||
<Compile Include="Map\MapIndices.cs" />
|
||||
<Compile Include="GameObjects\Components\Renderable\SharedSpriteComponent.cs" />
|
||||
<Compile Include="Interfaces\Serialization\IExposeData.cs" />
|
||||
|
||||
@@ -7,6 +7,7 @@ using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
|
||||
@@ -58,9 +58,9 @@
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
|
||||
<PackageReference Include="Moq" Version="4.10.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||
<PackageReference Include="NUnit" Version="3.10.1" />
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
|
||||
<PackageReference Include="NUnit" Version="3.11.0" />
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.10.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
|
||||
<PackageReference Include="Smocks.ExtensionPackage">
|
||||
<Version>0.7.5-beta2</Version>
|
||||
</PackageReference>
|
||||
@@ -110,7 +110,10 @@
|
||||
<Compile Include="Shared\GameObjects\ComponentManager_Test.cs" />
|
||||
<Compile Include="Shared\GameObjects\EntityState_Tests.cs" />
|
||||
<Compile Include="Shared\IoC\IoCManager_Test.cs" />
|
||||
<Compile Include="Shared\Map\MapChunk_Tests.cs" />
|
||||
<Compile Include="Shared\Map\MapGrid_Tests.cs" />
|
||||
<Compile Include="Shared\Maths\Angle_Test.cs" />
|
||||
<Compile Include="Shared\Maths\Box2i_Test.cs" />
|
||||
<Compile Include="Shared\Maths\UIBox2i_Test.cs" />
|
||||
<Compile Include="Shared\Maths\Box2_Test.cs" />
|
||||
<Compile Include="Shared\Maths\UIBox2_Test.cs" />
|
||||
|
||||
@@ -0,0 +1,455 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects.Components.Transform;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.Map
|
||||
{
|
||||
[TestFixture, Parallelizable, TestOf(typeof(MapChunk))]
|
||||
class MapChunk_Tests
|
||||
{
|
||||
[Test]
|
||||
public void GetChunkSize()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
|
||||
Assert.That(chunk.ChunkSize, Is.EqualTo((ushort)8));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetIndices()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
|
||||
Assert.That(chunk.X, Is.EqualTo(7));
|
||||
Assert.That(chunk.Y, Is.EqualTo(9));
|
||||
Assert.That(chunk.Indices, Is.EqualTo(new MapIndices(7,9)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConstructorSetsLastTick()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
|
||||
Assert.That(chunk.LastModifiedTick, Is.EqualTo(new GameTick(11)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetTileThrowsOutOfRange()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.SetTile(8,0, new Tile())));
|
||||
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.SetTile(0, 8, new Tile())));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetTileModifiesLastTick()
|
||||
{
|
||||
var curTick = new GameTick(11);
|
||||
var mapGrid = new Mock<IMapGridInternal>();
|
||||
mapGrid.SetupGet(f => f.CurTick).Returns((() => curTick));
|
||||
mapGrid.SetupGet(f => f.ParentMapId).Returns(new MapId(11));
|
||||
mapGrid.SetupGet(f => f.Index).Returns(new GridId(13));
|
||||
|
||||
var chunk = new MapChunk(mapGrid.Object, 7, 9, 8);
|
||||
|
||||
curTick = new GameTick(13);
|
||||
chunk.SetTile(3, 5, new Tile(1, 3));
|
||||
|
||||
Assert.That(chunk.LastModifiedTick, Is.EqualTo(new GameTick(13)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetTileDuplicateDoesNotModifyLastTick()
|
||||
{
|
||||
var curTick = new GameTick(11);
|
||||
var mapGrid = new Mock<IMapGridInternal>();
|
||||
mapGrid.SetupGet(f => f.CurTick).Returns((() => curTick));
|
||||
mapGrid.SetupGet(f => f.ParentMapId).Returns(new MapId(11));
|
||||
mapGrid.SetupGet(f => f.Index).Returns(new GridId(13));
|
||||
|
||||
var chunk = new MapChunk(mapGrid.Object, 7, 9, 8);
|
||||
|
||||
curTick = new GameTick(13);
|
||||
chunk.SetTile(3, 5, new Tile(1, 3));
|
||||
curTick = new GameTick(14);
|
||||
chunk.SetTile(3, 5, new Tile(1, 3));
|
||||
|
||||
Assert.That(chunk.LastModifiedTick, Is.EqualTo(new GameTick(13)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetTileRefByIndex()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
chunk.SetTile(3, 5, new Tile(1, 3));
|
||||
|
||||
var result = chunk.GetTileRef(3, 5);
|
||||
|
||||
Assert.That(result.X, Is.EqualTo(8 * 7 + 3));
|
||||
Assert.That(result.Y, Is.EqualTo(8 * 9 + 5));
|
||||
Assert.That(result.Tile.TypeId, Is.EqualTo(1));
|
||||
Assert.That(result.Tile.Data, Is.EqualTo((ushort) 3));
|
||||
Assert.That(result.GridIndex, Is.EqualTo(new GridId(13)));
|
||||
Assert.That(result.MapIndex, Is.EqualTo(new MapId(11)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetTileRefByIndices()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
chunk.SetTile(3, 5, new Tile(1, 3));
|
||||
|
||||
var result = chunk.GetTileRef(new MapIndices(3, 5));
|
||||
|
||||
Assert.That(result.X, Is.EqualTo(8 * 7 + 3));
|
||||
Assert.That(result.Y, Is.EqualTo(8 * 9 + 5));
|
||||
Assert.That(result.Tile.TypeId, Is.EqualTo(1));
|
||||
Assert.That(result.Tile.Data, Is.EqualTo((ushort)3));
|
||||
Assert.That(result.GridIndex, Is.EqualTo(new GridId(13)));
|
||||
Assert.That(result.MapIndex, Is.EqualTo(new MapId(11)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetTileRefThrowsOutOfRange()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.GetTileRef(8, 0)));
|
||||
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.GetTileRef(0, 8)));
|
||||
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.GetTileRef(new MapIndices(8,0))));
|
||||
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.GetTileRef(new MapIndices(0, 8))));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetTileByIndex()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
chunk.SetTile(3, 5, new Tile(1, 3));
|
||||
|
||||
var result = chunk.GetTile(3, 5);
|
||||
|
||||
Assert.That(result.TypeId, Is.EqualTo(1));
|
||||
Assert.That(result.Data, Is.EqualTo((ushort)3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetTileByIndexThrowsOutOfRange()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.GetTile(8, 0)));
|
||||
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.GetTile(0, 8)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetAllTilesNotEmpty()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
chunk.SetTile(5, 4, new Tile(5, 7));
|
||||
chunk.SetTile(3, 5, new Tile(1, 3));
|
||||
|
||||
var tiles = chunk.GetAllTiles().ToList();
|
||||
|
||||
Assert.That(tiles.Count, Is.EqualTo(2));
|
||||
|
||||
// Order is guaranteed to be row-major (because C# is), the Y value is contiguous in memory
|
||||
Assert.That(tiles[0],
|
||||
Is.EqualTo(new TileRef(new MapId(11),
|
||||
new GridId(13),
|
||||
new MapIndices(8 * 7 + 3, 8 * 9 + 5),
|
||||
new Tile(1, 3))));
|
||||
|
||||
Assert.That(tiles[1],
|
||||
Is.EqualTo(new TileRef(new MapId(11),
|
||||
new GridId(13),
|
||||
new MapIndices(8 * 7 + 5, 8 * 9 + 4),
|
||||
new Tile(5, 7))));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetAllTilesEmpty()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
chunk.SetTile(5, 4, new Tile(5, 7));
|
||||
chunk.SetTile(3, 5, new Tile(1, 3));
|
||||
|
||||
var tiles = chunk.GetAllTiles(false).ToList();
|
||||
|
||||
Assert.That(tiles.Count, Is.EqualTo(8*8));
|
||||
|
||||
// Order is guaranteed to be row-major (because C# is), the Y value is contiguous in memory
|
||||
Assert.That(tiles[8*3+5],
|
||||
Is.EqualTo(new TileRef(new MapId(11),
|
||||
new GridId(13),
|
||||
new MapIndices(8 * 7 + 3, 8 * 9 + 5),
|
||||
new Tile(1, 3))));
|
||||
|
||||
Assert.That(tiles[8*5+4],
|
||||
Is.EqualTo(new TileRef(new MapId(11),
|
||||
new GridId(13),
|
||||
new MapIndices(8 * 7 + 5, 8 * 9 + 4),
|
||||
new Tile(5, 7))));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetTileNotifiesGrid()
|
||||
{
|
||||
var mapGrid = new Mock<IMapGridInternal>();
|
||||
mapGrid.SetupGet(f => f.CurTick).Returns(new GameTick(11));
|
||||
mapGrid.SetupGet(f => f.ParentMapId).Returns(new MapId(11));
|
||||
mapGrid.SetupGet(f => f.Index).Returns(new GridId(13));
|
||||
mapGrid.Setup(f => f.NotifyTileChanged(It.Ref<TileRef>.IsAny, It.Ref<Tile>.IsAny)).Verifiable();
|
||||
|
||||
var chunk = new MapChunk(mapGrid.Object, 7, 9, 8);
|
||||
chunk.SetTile(3, 5, new Tile(1, 3));
|
||||
|
||||
mapGrid.Verify(f => f.NotifyTileChanged(It.Ref<TileRef>.IsAny, It.Ref<Tile>.IsAny), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetTileDuplicateNotifiesOnce()
|
||||
{
|
||||
var mapGrid = new Mock<IMapGridInternal>();
|
||||
mapGrid.SetupGet(f => f.CurTick).Returns(new GameTick(11));
|
||||
mapGrid.SetupGet(f => f.ParentMapId).Returns(new MapId(11));
|
||||
mapGrid.SetupGet(f => f.Index).Returns(new GridId(13));
|
||||
mapGrid.Setup(f => f.NotifyTileChanged(It.Ref<TileRef>.IsAny, It.Ref<Tile>.IsAny)).Verifiable();
|
||||
|
||||
var chunk = new MapChunk(mapGrid.Object, 7, 9, 8);
|
||||
chunk.SetTile(3, 5, new Tile(1, 3));
|
||||
chunk.SetTile(3, 5, new Tile(1, 3));
|
||||
|
||||
mapGrid.Verify(f => f.NotifyTileChanged(It.Ref<TileRef>.IsAny, It.Ref<Tile>.IsAny), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EnumerateTiles()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
chunk.SetTile(5, 4, new Tile(5, 7));
|
||||
chunk.SetTile(3, 5, new Tile(1, 3));
|
||||
|
||||
var tiles = new List<TileRef>();
|
||||
foreach (var tileRef in ((IEnumerable)chunk))
|
||||
{
|
||||
tiles.Add((TileRef)tileRef);
|
||||
}
|
||||
|
||||
Assert.That(tiles.Count, Is.EqualTo(2));
|
||||
|
||||
// Order is guaranteed to be row-major (because C# is), the Y value is contiguous in memory
|
||||
Assert.That(tiles[0],
|
||||
Is.EqualTo(new TileRef(new MapId(11),
|
||||
new GridId(13),
|
||||
new MapIndices(8 * 7 + 3, 8 * 9 + 5),
|
||||
new Tile(1, 3))));
|
||||
|
||||
Assert.That(tiles[1],
|
||||
Is.EqualTo(new TileRef(new MapId(11),
|
||||
new GridId(13),
|
||||
new MapIndices(8 * 7 + 5, 8 * 9 + 4),
|
||||
new Tile(5, 7))));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GridToChunkIndices()
|
||||
{
|
||||
// chunk indices are relative to the lowest coordinates of the chunk
|
||||
// EX 8x8 chunk (0,0) occupies tiles 0 to 8 on each axis
|
||||
// 8x8 chunk (-1,-1) occupies tiles -8 to -1 on each axis
|
||||
var chunk = MapChunkFactory(-1, -1);
|
||||
|
||||
var indices = chunk.GridTileToChunkTile(new MapIndices(-3, -5));
|
||||
|
||||
// drawing this out helps a ton
|
||||
// grid tile -1,-1 is chunk tile 7,7
|
||||
// grid tile -8,-8 is chunk tile 0,0
|
||||
Assert.That(indices, Is.EqualTo(new MapIndices(5, 3)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetToString()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
|
||||
var result = chunk.ToString();
|
||||
|
||||
Assert.That(result, Is.EqualTo("Chunk {7,9}"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetEmptySnapGrid()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
|
||||
Assert.That(chunk.GetSnapGridCell(0,0, SnapGridOffset.Center).ToList().Count, Is.EqualTo(0));
|
||||
Assert.That(chunk.GetSnapGridCell(0, 0, SnapGridOffset.Edge).ToList().Count, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetSnapGridThrowsOutOfRange()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.GetSnapGridCell(8,0, SnapGridOffset.Center).ToList()));
|
||||
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.GetSnapGridCell(0, 8, SnapGridOffset.Center).ToList()));
|
||||
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.GetSnapGridCell(8,0,SnapGridOffset.Edge).ToList()));
|
||||
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.GetSnapGridCell(0, 8, SnapGridOffset.Edge).ToList()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddSnapGridCellCenter()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
|
||||
var snapGridComponent = new SnapGridComponent();
|
||||
chunk.AddToSnapGridCell(3, 5, SnapGridOffset.Center, snapGridComponent);
|
||||
chunk.AddToSnapGridCell(3, 5, SnapGridOffset.Edge, new SnapGridComponent());
|
||||
chunk.AddToSnapGridCell(3, 6, SnapGridOffset.Center, new SnapGridComponent());
|
||||
|
||||
var result = chunk.GetSnapGridCell(3, 5, SnapGridOffset.Center).ToList();
|
||||
|
||||
Assert.That(result.Count, Is.EqualTo(1));
|
||||
Assert.That(result[0], Is.EqualTo(snapGridComponent));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddSnapGridCellEdge()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
|
||||
var snapGridComponent = new SnapGridComponent();
|
||||
chunk.AddToSnapGridCell(3, 5, SnapGridOffset.Edge, snapGridComponent);
|
||||
chunk.AddToSnapGridCell(3, 5, SnapGridOffset.Center, new SnapGridComponent());
|
||||
chunk.AddToSnapGridCell(3, 6, SnapGridOffset.Edge, new SnapGridComponent());
|
||||
|
||||
var result = chunk.GetSnapGridCell(3, 5, SnapGridOffset.Edge).ToList();
|
||||
|
||||
Assert.That(result.Count, Is.EqualTo(1));
|
||||
Assert.That(result[0], Is.EqualTo(snapGridComponent));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddSnapGridThrowsOutOfRange()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.AddToSnapGridCell(8, 0, SnapGridOffset.Center, new SnapGridComponent())));
|
||||
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.AddToSnapGridCell(0, 8, SnapGridOffset.Center, new SnapGridComponent())));
|
||||
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.AddToSnapGridCell(8, 0, SnapGridOffset.Edge, new SnapGridComponent())));
|
||||
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.AddToSnapGridCell(0, 8, SnapGridOffset.Edge, new SnapGridComponent())));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RemoveSnapGridCellCenter()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
|
||||
var snapGridComponent = new SnapGridComponent();
|
||||
chunk.AddToSnapGridCell(3, 5, SnapGridOffset.Center, snapGridComponent);
|
||||
chunk.AddToSnapGridCell(3, 5, SnapGridOffset.Edge, new SnapGridComponent());
|
||||
chunk.AddToSnapGridCell(3, 6, SnapGridOffset.Center, new SnapGridComponent());
|
||||
|
||||
chunk.RemoveFromSnapGridCell(3, 5, SnapGridOffset.Center, snapGridComponent);
|
||||
|
||||
var result = chunk.GetSnapGridCell(3, 5, SnapGridOffset.Center).ToList();
|
||||
Assert.That(result.Count, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RemoveSnapGridCellEdge()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
|
||||
var snapGridComponent = new SnapGridComponent();
|
||||
chunk.AddToSnapGridCell(3, 5, SnapGridOffset.Edge, snapGridComponent);
|
||||
chunk.AddToSnapGridCell(3, 5, SnapGridOffset.Center, new SnapGridComponent());
|
||||
chunk.AddToSnapGridCell(3, 6, SnapGridOffset.Edge, new SnapGridComponent());
|
||||
|
||||
chunk.RemoveFromSnapGridCell(3, 5, SnapGridOffset.Edge, snapGridComponent);
|
||||
|
||||
var result = chunk.GetSnapGridCell(3, 5, SnapGridOffset.Edge).ToList();
|
||||
Assert.That(result.Count, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RemoveSnapGridThrowsOutOfRange()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.RemoveFromSnapGridCell(8, 0, SnapGridOffset.Center, new SnapGridComponent())));
|
||||
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.RemoveFromSnapGridCell(0, 8, SnapGridOffset.Center, new SnapGridComponent())));
|
||||
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.RemoveFromSnapGridCell(8, 0, SnapGridOffset.Edge, new SnapGridComponent())));
|
||||
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.RemoveFromSnapGridCell(0, 8, SnapGridOffset.Edge, new SnapGridComponent())));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BoundsExpandWhenTileAdded()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
|
||||
chunk.SetTile(3, 5, new Tile(1));
|
||||
chunk.SetTile(5, 7, new Tile(1));
|
||||
|
||||
var bounds = chunk.CalcLocalBounds();
|
||||
|
||||
Assert.That(bounds.Left, Is.EqualTo(3));
|
||||
Assert.That(bounds.Bottom, Is.EqualTo(5));
|
||||
Assert.That(bounds.Right, Is.EqualTo(6));
|
||||
Assert.That(bounds.Top, Is.EqualTo(8));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BoundsContractWhenTileRemovedBL()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
|
||||
chunk.SetTile(3, 5, new Tile(1));
|
||||
chunk.SetTile(5, 7, new Tile(1));
|
||||
|
||||
chunk.SetTile(3, 5, Tile.Empty);
|
||||
|
||||
var bounds = chunk.CalcLocalBounds();
|
||||
|
||||
Assert.That(bounds.Left, Is.EqualTo(5));
|
||||
Assert.That(bounds.Bottom, Is.EqualTo(7));
|
||||
Assert.That(bounds.Right, Is.EqualTo(6));
|
||||
Assert.That(bounds.Top, Is.EqualTo(8));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BoundsContractWhenTileRemovedTR()
|
||||
{
|
||||
var chunk = MapChunkFactory(7, 9);
|
||||
|
||||
chunk.SetTile(3, 5, new Tile(1));
|
||||
chunk.SetTile(5, 7, new Tile(1));
|
||||
|
||||
chunk.SetTile(5, 7, Tile.Empty);
|
||||
|
||||
var bounds = chunk.CalcLocalBounds();
|
||||
|
||||
Assert.That(bounds.Left, Is.EqualTo(3));
|
||||
Assert.That(bounds.Bottom, Is.EqualTo(5));
|
||||
Assert.That(bounds.Right, Is.EqualTo(4));
|
||||
Assert.That(bounds.Top, Is.EqualTo(6));
|
||||
}
|
||||
|
||||
public IMapChunkInternal MapChunkFactory(int xChunkIndex, int yChunkIndex)
|
||||
{
|
||||
var mapGrid = new Mock<IMapGridInternal>();
|
||||
mapGrid.SetupGet(f=>f.CurTick).Returns(new GameTick(11));
|
||||
mapGrid.SetupGet(f => f.ParentMapId).Returns(new MapId(11));
|
||||
mapGrid.SetupGet(f => f.Index).Returns(new GridId(13));
|
||||
|
||||
return new MapChunk(mapGrid.Object, xChunkIndex, yChunkIndex, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using MapGrid = Robust.Shared.Map.MapGrid;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.Map
|
||||
{
|
||||
[TestFixture, Parallelizable, TestOf(typeof(MapGrid))]
|
||||
class MapGrid_Tests
|
||||
{
|
||||
[Test]
|
||||
public void GetTileRefCoords()
|
||||
{
|
||||
var grid = MapGridFactory(new GridId(1));
|
||||
grid.SetTile(new MapIndices(-9, -1), new Tile(1, 2));
|
||||
|
||||
var result = grid.GetTileRef(new MapIndices(-9, -1));
|
||||
|
||||
Assert.That(grid.ChunkCount, Is.EqualTo(1));
|
||||
Assert.That(grid.GetMapChunks().Keys.ToList()[0], Is.EqualTo(new MapIndices(-2, -1)));
|
||||
Assert.That(result, Is.EqualTo(new TileRef(new MapId(3), new GridId(1), new MapIndices(-9,-1), new Tile(1, 2))));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the world Bounds of the grid properly expand when tiles are placed.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void BoundsExpansion()
|
||||
{
|
||||
var grid = MapGridFactory(new GridId(1));
|
||||
|
||||
grid.SetTile(new MapIndices(-1, -2), new Tile(1));
|
||||
grid.SetTile(new MapIndices(1, 2), new Tile(1));
|
||||
|
||||
var bounds = grid.WorldBounds;
|
||||
|
||||
// this is world, so add the grid world pos
|
||||
Assert.That(bounds.Bottom, Is.EqualTo(-2+5));
|
||||
Assert.That(bounds.Left, Is.EqualTo(-1+3));
|
||||
Assert.That(bounds.Top, Is.EqualTo(3+5));
|
||||
Assert.That(bounds.Right, Is.EqualTo(2+3));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the world bounds of the grid properly contract when a tile is removed.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void BoundsContract()
|
||||
{
|
||||
var grid = MapGridFactory(new GridId(1));
|
||||
|
||||
grid.SetTile(new MapIndices(-1, -2), new Tile(1));
|
||||
grid.SetTile(new MapIndices(1, 2), new Tile(1));
|
||||
|
||||
grid.SetTile(new MapIndices(1, 2), Tile.Empty);
|
||||
|
||||
var bounds = grid.WorldBounds;
|
||||
|
||||
// this is world, so add the grid world pos
|
||||
Assert.That(bounds.Bottom, Is.EqualTo(-2+5));
|
||||
Assert.That(bounds.Left, Is.EqualTo(-1+3));
|
||||
Assert.That(bounds.Top, Is.EqualTo(-1+5));
|
||||
Assert.That(bounds.Right, Is.EqualTo(0+3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GridTileToChunkIndices()
|
||||
{
|
||||
var grid = MapGridFactory(new GridId(1));
|
||||
|
||||
var result = grid.GridTileToChunkIndices(new MapIndices(-9, -1));
|
||||
|
||||
Assert.That(result, Is.EqualTo(new MapIndices(-2, -1)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the local position is centered on the tile, instead of bottom left.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ToLocalCentered()
|
||||
{
|
||||
var grid = MapGridFactory(new GridId(1));
|
||||
|
||||
var result = grid.GridTileToLocal(new MapIndices(0, 0)).Position;
|
||||
|
||||
Assert.That(result.X, Is.EqualTo(0.5f));
|
||||
Assert.That(result.Y, Is.EqualTo(0.5f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryGetTileRefNoTile()
|
||||
{
|
||||
var grid = MapGridFactory(new GridId(1));
|
||||
|
||||
var foundTile = grid.TryGetTileRef(new MapIndices(-9, -1), out var tileRef);
|
||||
|
||||
Assert.That(foundTile, Is.False);
|
||||
Assert.That(tileRef, Is.EqualTo(new TileRef()));
|
||||
Assert.That(grid.ChunkCount, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryGetTileRefTileExists()
|
||||
{
|
||||
var grid = MapGridFactory(new GridId(1));
|
||||
grid.SetTile(new MapIndices(-9, -1), new Tile(1, 2));
|
||||
|
||||
var foundTile = grid.TryGetTileRef(new MapIndices(-9, -1), out var tileRef);
|
||||
|
||||
Assert.That(foundTile, Is.True);
|
||||
Assert.That(grid.ChunkCount, Is.EqualTo(1));
|
||||
Assert.That(grid.GetMapChunks().Keys.ToList()[0], Is.EqualTo(new MapIndices(-2, -1)));
|
||||
Assert.That(tileRef, Is.EqualTo(new TileRef(new MapId(3), new GridId(1), new MapIndices(-9, -1), new Tile(1, 2))));
|
||||
}
|
||||
|
||||
private static IMapGridInternal MapGridFactory(GridId id)
|
||||
{
|
||||
var timing = new Mock<IGameTiming>();
|
||||
var mapMan = new Mock<IMapManagerInternal>();
|
||||
mapMan.SetupGet(p => p.GameTiming).Returns(timing.Object);
|
||||
|
||||
var newGrid = new MapGrid(mapMan.Object, id, 8, 1, new MapId(3))
|
||||
{
|
||||
WorldPosition = new Vector2(3, 5)
|
||||
};
|
||||
|
||||
return newGrid;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.Maths
|
||||
{
|
||||
[TestFixture, Parallelizable, TestOf(typeof(Box2i))]
|
||||
class Box2i_Test
|
||||
{
|
||||
[Test]
|
||||
public void Box2iUnion()
|
||||
{
|
||||
var boxOne = new Box2i(-1, -1, 1, 1);
|
||||
var boxTwo = new Box2i(0, 0, 2, 2);
|
||||
|
||||
var result = boxOne.Union(boxTwo);
|
||||
|
||||
Assert.That(result.Left, Is.EqualTo(-1));
|
||||
Assert.That(result.Bottom, Is.EqualTo(-1));
|
||||
Assert.That(result.Right, Is.EqualTo(2));
|
||||
Assert.That(result.Top, Is.EqualTo(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user