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 { // 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 = 16; [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; } }