if (prototype.Mode == RcdMode.ConstructTile)
{
// Check rule: Tile placement is valid
- if (!_floors.CanPlaceTile(gridUid, mapGrid, out var reason))
+ if (!_floors.CanPlaceTile(gridUid, mapGrid, tile.GridIndices, out var reason))
{
if (popMsgs)
_popup.PopupClient(reason, uid, user);
/// Raised directed on a grid when attempting a floor tile placement.
/// </summary>
[ByRefEvent]
-public record struct FloorTileAttemptEvent(bool Cancelled);
+public record struct FloorTileAttemptEvent(Vector2i GridIndices, bool Cancelled = false);
if (mapGrid != null)
{
var gridUid = location.EntityId;
+ var tile = _map.GetTileRef(gridUid, mapGrid, location);
- if (!CanPlaceTile(gridUid, mapGrid, out var reason))
+ if (!CanPlaceTile(gridUid, mapGrid, tile.GridIndices, out var reason))
{
_popup.PopupClient(reason, args.User, args.User);
return;
}
- var tile = _map.GetTileRef(gridUid, mapGrid, location);
var baseTurf = (ContentTileDefinition) _tileDefinitionManager[tile.Tile.TypeId];
if (HasBaseTurf(currentTileDefinition, baseTurf.ID))
_audio.PlayPredicted(placeSound, location, user);
}
- public bool CanPlaceTile(EntityUid gridUid, MapGridComponent component, [NotNullWhen(false)] out string? reason)
+ public bool CanPlaceTile(EntityUid gridUid, MapGridComponent component, Vector2i gridIndices, [NotNullWhen(false)] out string? reason)
{
- var ev = new FloorTileAttemptEvent();
+ var ev = new FloorTileAttemptEvent(gridIndices);
RaiseLocalEvent(gridUid, ref ev);
- if (HasComp<ProtectedGridComponent>(gridUid) || ev.Cancelled)
+ if (ev.Cancelled)
{
reason = Loc.GetString("invalid-floor-placement");
return false;
/// <summary>
/// Prevents floor tile updates when attached to a grid.
/// </summary>
-[RegisterComponent, NetworkedComponent]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(ProtectedGridSystem))]
public sealed partial class ProtectedGridComponent : Component
{
-
+ /// <summary>
+ /// A bitmask of all the initial tiles on this grid.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public Dictionary<Vector2i, ulong> BaseIndices = new();
}
--- /dev/null
+using System.Linq;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Map.Enumerators;
+
+namespace Content.Shared.Tiles;
+
+public sealed class ProtectedGridSystem : EntitySystem
+{
+ [Dependency] private readonly SharedMapSystem _map = default!;
+
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ SubscribeLocalEvent<ProtectedGridComponent, MapInitEvent>(OnMapInit);
+ SubscribeLocalEvent<ProtectedGridComponent, FloorTileAttemptEvent>(OnFloorTileAttempt);
+ }
+
+ private void OnMapInit(Entity<ProtectedGridComponent> ent, ref MapInitEvent args)
+ {
+ if (!TryComp<MapGridComponent>(ent, out var grid))
+ return;
+
+ // Engine default is currently 16x size chunks which means we can't just easily have 64bit flags.
+ var chunkEnumerator = new ChunkIndicesEnumerator(grid.LocalAABB, 8);
+
+ while (chunkEnumerator.MoveNext(out var chunk))
+ {
+ ulong flag = 0;
+
+ for (var x = 0; x < 8; x++)
+ {
+ for (var y = 0; y < 8; y++)
+ {
+ var index = new Vector2i(x + chunk.Value.X * 8, y + chunk.Value.Y * 8);
+ var tile = _map.GetTileRef(ent.Owner, grid, index);
+
+ if (tile.Tile.IsEmpty)
+ continue;
+
+ var data = SharedMapSystem.ToBitmask(new Vector2i(x, y));
+
+ flag |= data;
+ }
+ }
+
+ if (flag == 0)
+ continue;
+
+ ent.Comp.BaseIndices[chunk.Value] = flag;
+ }
+
+ Dirty(ent);
+ }
+
+ private void OnFloorTileAttempt(Entity<ProtectedGridComponent> ent, ref FloorTileAttemptEvent args)
+ {
+ var chunkOrigin = SharedMapSystem.GetChunkIndices(args.GridIndices, 8);
+
+ if (!ent.Comp.BaseIndices.TryGetValue(chunkOrigin, out var data))
+ {
+ args.Cancelled = true;
+ return;
+ }
+
+ if (SharedMapSystem.FromBitmask(args.GridIndices, data))
+ {
+ args.Cancelled = true;
+ }
+ }
+}