using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Map.Components;
///
///
/// Provides a tile grid as part of an entity, with APIs to manipulate it provided on .
/// Grids function as the optional second level in the physics hierarchy, and implicitly provide physics and the ability to collide with one-another.
///
///
/// Map grids are an unbounded 2D grid composed of s,
/// which have their properties defined by inheriting prototypes.
/// A tile with the id 0 is automatically assumed to be the "empty" tile and is the default state of any tile that has not been assigned to.
///
///
/// Collision is automatically generated from set grid tiles (excluding tiles with id 0, i.e. empty).
/// Map grids have an implied additional level of physics acceleration structures,
/// functioning as an optimization for games that rely on moving grids regularly by avoiding the need to update the grids' children.
///
///
[RegisterComponent, NetworkedComponent]
public sealed partial class MapGridComponent : Component
{
public const ushort DefaultChunkSize = 16;
// This field is used for deserialization internally in the map loader.
// If you want to remove this, you would have to restructure the map save file.
[DataField("index")]
internal int GridIndex;
// the grid section now writes the grid's EntityUID. as long as existing maps get updated (just a load+save),
// this can be removed
[DataField]
internal ushort ChunkSize = DefaultChunkSize;
[ViewVariables]
public int ChunkCount => Chunks.Count;
///
/// The length of the side of a square tile in world units.
///
[DataField]
public ushort TileSize { get; internal set; } = 1;
///
/// A square the size of one tile.
///
public Vector2 TileSizeVector => new(TileSize, TileSize);
///
/// A square the size of a quarter tile.
///
public Vector2 TileSizeHalfVector => new(TileSize / 2f, TileSize / 2f);
[ViewVariables]
internal readonly List<(GameTick tick, Vector2i indices)> ChunkDeletionHistory = new();
///
/// Last game tick that the map was modified.
///
[ViewVariables]
public GameTick LastTileModifiedTick { get; internal set; }
///
/// Map DynamicTree proxy to lookup for grid intersection.
///
internal DynamicTree.Proxy MapProxy = DynamicTree.Proxy.Free;
///
/// Grid chunks than make up this grid.
///
[DataField]
internal Dictionary Chunks = new();
[ViewVariables]
public Box2 LocalAABB { get; internal set; }
///
/// Set to enable or disable grid splitting.
/// You must ensure you handle this properly and check for splits afterwards if relevant!
///
[DataField]
public bool CanSplit = true;
/// True if the specified chunk exists on this grid.
[Pure]
public bool HasChunk(Vector2i indices)
{
return Chunks.ContainsKey(indices);
}
}
///
/// Serialized state of a .
///
[Serializable, NetSerializable]
internal sealed class MapGridComponentState(ushort chunkSize, Dictionary fullGridData, GameTick lastTileModifiedTick) : ComponentState
{
///
/// The size of the chunks in the map grid.
///
public ushort ChunkSize = chunkSize;
///
/// Networked chunk data containing the full grid state.
///
public Dictionary FullGridData = fullGridData;
///
/// Last game tick that the tile on the grid was modified.
///
public GameTick LastTileModifiedTick = lastTileModifiedTick;
}
///
/// Serialized state of a .
///
[Serializable, NetSerializable]
internal sealed class MapGridComponentDeltaState(ushort chunkSize, Dictionary? chunkData, GameTick lastTileModifiedTick)
: ComponentState, IComponentDeltaState
{
///
/// The size of the chunks in the map grid.
///
public readonly ushort ChunkSize = chunkSize;
///
/// Networked chunk data.
///
public readonly Dictionary? ChunkData = chunkData;
///
/// Last game tick that the tile on the grid was modified.
///
public GameTick LastTileModifiedTick = lastTileModifiedTick;
public void ApplyToFullState(MapGridComponentState state)
{
state.ChunkSize = ChunkSize;
if (ChunkData == null)
return;
foreach (var (index, data) in ChunkData)
{
if (data.IsDeleted())
state.FullGridData.Remove(index);
else
state.FullGridData[index] = data;
}
state.LastTileModifiedTick = LastTileModifiedTick;
}
public MapGridComponentState CreateNewFullState(MapGridComponentState state)
{
if (ChunkData == null)
return new(ChunkSize, state.FullGridData, state.LastTileModifiedTick);
var newState = new MapGridComponentState(ChunkSize, state.FullGridData.ShallowClone(), LastTileModifiedTick);
ApplyToFullState(newState);
return newState;
}
}