]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add RoomFill markers (#22293)
authormetalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Thu, 4 Jan 2024 04:17:04 +0000 (15:17 +1100)
committerGitHub <noreply@github.com>
Thu, 4 Jan 2024 04:17:04 +0000 (15:17 +1100)
* Add RoomFill markers

* weh

* Also deez

* Working

* Randomised fills working

* Fixes

* Fix lack of prototypes

* Fix tests

* Fix tests?

Content.Server/Procedural/DungeonJob.PrefabDunGen.cs
Content.Server/Procedural/DungeonSystem.Rooms.cs [new file with mode: 0644]
Content.Server/Procedural/DungeonSystem.cs
Content.Server/Procedural/RoomFillComponent.cs [new file with mode: 0644]
Content.Server/Procedural/RoomFillSystem.cs [new file with mode: 0644]
Resources/Prototypes/Entities/Markers/rooms.yml [new file with mode: 0644]

index 7720110fd24065c19307234d8d03fdd308692ddf..9314a784e828294f12d44bd120e25a1a2976418d 100644 (file)
@@ -191,59 +191,29 @@ public sealed partial class DungeonJob
 
                         grid.SetTiles(tiles);
                         tiles.Clear();
-                        Logger.Error($"Unable to find room variant for {roomDimensions}, leaving empty.");
+                        _sawmill.Error($"Unable to find room variant for {roomDimensions}, leaving empty.");
                         continue;
                     }
 
                     roomRotation = new Angle(Math.PI / 2);
-                    Logger.Debug($"Using rotated variant for room");
-                }
-
-                if (roomDimensions.X == roomDimensions.Y)
-                {
-                    // Give it a random rotation
-                    roomRotation = random.Next(4) * Math.PI / 2;
-                }
-                else if (random.Next(2) == 1)
-                {
-                    roomRotation += Math.PI;
+                    _sawmill.Debug($"Using rotated variant for room");
                 }
 
                 var roomTransform = Matrix3.CreateTransform(roomSize.Center - packCenter, roomRotation);
-                var finalRoomRotation = roomRotation + packRotation + dungeonRotation;
 
                 Matrix3.Multiply(roomTransform, packTransform, out matty);
                 Matrix3.Multiply(matty, dungeonTransform, out var dungeonMatty);
 
+                // The expensive bit yippy.
                 var room = roomProto[random.Next(roomProto.Count)];
-                var roomMap = _dungeon.GetOrCreateTemplate(room);
-                var templateMapUid = _mapManager.GetMapEntityId(roomMap);
-                var templateGrid = _entManager.GetComponent<MapGridComponent>(templateMapUid);
+                _dungeon.SpawnRoom(gridUid, grid, matty, room, random, rotation: true);
+
                 var roomCenter = (room.Offset + room.Size / 2f) * grid.TileSize;
                 var roomTiles = new HashSet<Vector2i>(room.Size.X * room.Size.Y);
                 var exterior = new HashSet<Vector2i>(room.Size.X * 2 + room.Size.Y * 2);
                 var tileOffset = -roomCenter + grid.TileSizeHalfVector;
                 Box2i? mapBounds = null;
 
-                // Load tiles
-                for (var x = 0; x < room.Size.X; x++)
-                {
-                    for (var y = 0; y < room.Size.Y; y++)
-                    {
-                        var indices = new Vector2i(x + room.Offset.X, y + room.Offset.Y);
-                        var tileRef = templateGrid.GetTileRef(indices);
-
-                        var tilePos = dungeonMatty.Transform(indices + tileOffset);
-                        var rounded = tilePos.Floored();
-                        tiles.Add((rounded, tileRef.Tile));
-                        roomTiles.Add(rounded);
-
-                        // If this were a Box2 we'd add tilesize although here I think that's undesirable as
-                        // for example, a box2i of 0,0,1,1 is assumed to also include the tile at 1,1
-                        mapBounds = mapBounds?.Union(new Box2i(rounded, rounded)) ?? new Box2i(rounded, rounded);
-                    }
-                }
-
                 for (var x = -1; x <= room.Size.X; x++)
                 {
                     for (var y = -1; y <= room.Size.Y; y++)
@@ -258,111 +228,16 @@ public sealed partial class DungeonJob
                     }
                 }
 
-                var bounds = new Box2(room.Offset, room.Offset + room.Size);
                 var center = Vector2.Zero;
 
                 foreach (var tile in roomTiles)
                 {
-                    center += (Vector2) tile + grid.TileSizeHalfVector;
+                    center += tile + grid.TileSizeHalfVector;
                 }
 
                 center /= roomTiles.Count;
 
                 dungeon.Rooms.Add(new DungeonRoom(roomTiles, center, mapBounds!.Value, exterior));
-                grid.SetTiles(tiles);
-                tiles.Clear();
-                var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
-                var metaQuery = _entManager.GetEntityQuery<MetaDataComponent>();
-
-                // Load entities
-                // TODO: I don't think engine supports full entity copying so we do this piece of shit.
-
-                foreach (var templateEnt in _lookup.GetEntitiesIntersecting(templateMapUid, bounds, LookupFlags.Uncontained))
-                {
-                    var templateXform = xformQuery.GetComponent(templateEnt);
-                    var childPos = dungeonMatty.Transform(templateXform.LocalPosition - roomCenter);
-                    var childRot = templateXform.LocalRotation + finalRoomRotation;
-                    var protoId = metaQuery.GetComponent(templateEnt).EntityPrototype?.ID;
-
-                    // TODO: Copy the templated entity as is with serv
-                    var ent = _entManager.SpawnEntity(protoId,
-                        new EntityCoordinates(gridUid, childPos));
-
-                    var childXform = xformQuery.GetComponent(ent);
-                    var anchored = templateXform.Anchored;
-                    _transform.SetLocalRotation(ent, childRot, childXform);
-
-                    // If the templated entity was anchored then anchor us too.
-                    if (anchored && !childXform.Anchored)
-                        _transform.AnchorEntity(ent, childXform, grid);
-                    else if (!anchored && childXform.Anchored)
-                        _transform.Unanchor(ent, childXform);
-                }
-
-                // Load decals
-                if (_entManager.TryGetComponent<DecalGridComponent>(templateMapUid, out var loadedDecals))
-                {
-                    _entManager.EnsureComponent<DecalGridComponent>(gridUid);
-
-                    foreach (var (_, decal) in _decals.GetDecalsIntersecting(templateMapUid, bounds, loadedDecals))
-                    {
-                        // Offset by 0.5 because decals are offset from bot-left corner
-                        // So we convert it to center of tile then convert it back again after transform.
-                        // Do these shenanigans because 32x32 decals assume as they are centered on bottom-left of tiles.
-                        var position = dungeonMatty.Transform(decal.Coordinates + Vector2Helpers.Half - roomCenter);
-                        position -= Vector2Helpers.Half;
-
-                        // Umm uhh I love decals so uhhhh idk what to do about this
-                        var angle = (decal.Angle + finalRoomRotation).Reduced();
-
-                        // Adjust because 32x32 so we can't rotate cleanly
-                        // Yeah idk about the uhh vectors here but it looked visually okay but they may still be off by 1.
-                        // Also EyeManager.PixelsPerMeter should really be in shared.
-                        if (angle.Equals(Math.PI))
-                        {
-                            position += new Vector2(-1f / 32f, 1f / 32f);
-                        }
-                        else if (angle.Equals(-Math.PI / 2f))
-                        {
-                            position += new Vector2(-1f / 32f, 0f);
-                        }
-                        else if (angle.Equals(Math.PI / 2f))
-                        {
-                            position += new Vector2(0f, 1f / 32f);
-                        }
-                        else if (angle.Equals(Math.PI * 1.5f))
-                        {
-                            // I hate this but decals are bottom-left rather than center position and doing the
-                            // matrix ops is a PITA hence this workaround for now; I also don't want to add a stupid
-                            // field for 1 specific op on decals
-                            if (decal.Id != "DiagonalCheckerAOverlay" &&
-                                decal.Id != "DiagonalCheckerBOverlay")
-                            {
-                                position += new Vector2(-1f / 32f, 0f);
-                            }
-                        }
-
-                        var tilePos = position.Floored();
-
-                        // Fallback because uhhhhhhhh yeah, a corner tile might look valid on the original
-                        // but place 1 nanometre off grid and fail the add.
-                        if (!grid.TryGetTileRef(tilePos, out var tileRef) || tileRef.Tile.IsEmpty)
-                        {
-                            grid.SetTile(tilePos, fallbackTile);
-                        }
-
-                        var result = _decals.TryAddDecal(
-                            decal.Id,
-                            new EntityCoordinates(gridUid, position),
-                            out _,
-                            decal.Color,
-                            angle,
-                            decal.ZIndex,
-                            decal.Cleanable);
-
-                        DebugTools.Assert(result);
-                    }
-                }
 
                 await SuspendIfOutOfTime();
                 ValidateResume();
diff --git a/Content.Server/Procedural/DungeonSystem.Rooms.cs b/Content.Server/Procedural/DungeonSystem.Rooms.cs
new file mode 100644 (file)
index 0000000..c7445e9
--- /dev/null
@@ -0,0 +1,240 @@
+using System.Numerics;
+using Content.Shared.Decals;
+using Content.Shared.Procedural;
+using Content.Shared.Random.Helpers;
+using Content.Shared.Whitelist;
+using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Utility;
+
+namespace Content.Server.Procedural;
+
+public sealed partial class DungeonSystem
+{
+    // Temporary caches.
+    private readonly HashSet<EntityUid> _entitySet = new();
+    private readonly List<DungeonRoomPrototype> _availableRooms = new();
+
+    /// <summary>
+    /// Gets a random dungeon room matching the specified area and whitelist.
+    /// </summary>
+    public DungeonRoomPrototype? GetRoomPrototype(Vector2i size, Random random, EntityWhitelist? whitelist = null)
+    {
+        // Can never be true.
+        if (whitelist is { Tags: null })
+        {
+            return null;
+        }
+
+        _availableRooms.Clear();
+
+        foreach (var proto in _prototype.EnumeratePrototypes<DungeonRoomPrototype>())
+        {
+            if (proto.Size != size)
+                continue;
+
+            if (whitelist == null)
+            {
+                _availableRooms.Add(proto);
+                continue;
+            }
+
+            foreach (var tag in whitelist.Tags)
+            {
+                if (!proto.Tags.Contains(tag))
+                    continue;
+
+                _availableRooms.Add(proto);
+                break;
+            }
+        }
+
+        if (_availableRooms.Count == 0)
+            return null;
+
+        var room = _availableRooms[random.Next(_availableRooms.Count)];
+
+        return room;
+    }
+
+    public void SpawnRoom(
+        EntityUid gridUid,
+        MapGridComponent grid,
+        Vector2i origin,
+        DungeonRoomPrototype room,
+        Random random,
+        bool clearExisting = false,
+        bool rotation = false)
+    {
+        var originTransform = Matrix3.CreateTranslation(origin);
+        SpawnRoom(gridUid, grid, originTransform, room, random, clearExisting, rotation);
+    }
+
+    public void SpawnRoom(
+        EntityUid gridUid,
+        MapGridComponent grid,
+        Matrix3 transform,
+        DungeonRoomPrototype room,
+        Random random,
+        bool clearExisting = false,
+        bool rotation = false)
+    {
+        // Ensure the underlying template exists.
+        var roomMap = GetOrCreateTemplate(room);
+        var templateMapUid = _mapManager.GetMapEntityId(roomMap);
+        var templateGrid = Comp<MapGridComponent>(templateMapUid);
+        var roomRotation = Angle.Zero;
+        var roomDimensions = room.Size;
+
+        if (rotation)
+        {
+            if (roomDimensions.X == roomDimensions.Y)
+            {
+                // Give it a random rotation
+                roomRotation = random.Next(4) * Math.PI / 2;
+            }
+            else if (random.Next(2) == 1)
+            {
+                roomRotation += Math.PI;
+            }
+        }
+
+        var roomTransform = Matrix3.CreateTransform((Vector2) room.Size / 2f, roomRotation);
+        Matrix3.Multiply(roomTransform, transform, out var finalTransform);
+        var finalRoomRotation = finalTransform.Rotation();
+
+        // go BRRNNTTT on existing stuff
+        if (clearExisting)
+        {
+            var gridBounds = new Box2(transform.Transform(Vector2.Zero), transform.Transform(room.Size));
+            _entitySet.Clear();
+            // Polygon skin moment
+            gridBounds = gridBounds.Enlarged(-0.05f);
+            _lookup.GetLocalEntitiesIntersecting(gridUid, gridBounds, _entitySet, LookupFlags.Uncontained);
+
+            foreach (var templateEnt in _entitySet)
+            {
+                Del(templateEnt);
+            }
+
+            if (TryComp(gridUid, out DecalGridComponent? decalGrid))
+            {
+                foreach (var decal in _decals.GetDecalsIntersecting(gridUid, gridBounds, decalGrid))
+                {
+                    _decals.RemoveDecal(gridUid, decal.Index, decalGrid);
+                }
+            }
+        }
+
+        var roomCenter = (room.Offset + room.Size / 2f) * grid.TileSize;
+        var tileOffset = -roomCenter + grid.TileSizeHalfVector;
+        _tiles.Clear();
+
+        // Load tiles
+        for (var x = 0; x < roomDimensions.X; x++)
+        {
+            for (var y = 0; y < roomDimensions.Y; y++)
+            {
+                var indices = new Vector2i(x + room.Offset.X, y + room.Offset.Y);
+                var tileRef = _maps.GetTileRef(templateMapUid, templateGrid, indices);
+
+                var tilePos = finalTransform.Transform(indices + tileOffset);
+                var rounded = tilePos.Floored();
+                _tiles.Add((rounded, tileRef.Tile));
+            }
+        }
+
+        var bounds = new Box2(room.Offset, room.Offset + room.Size);
+
+        _maps.SetTiles(gridUid, grid, _tiles);
+
+        // Load entities
+        // TODO: I don't think engine supports full entity copying so we do this piece of shit.
+
+        foreach (var templateEnt in _lookup.GetEntitiesIntersecting(templateMapUid, bounds, LookupFlags.Uncontained))
+        {
+            var templateXform = _xformQuery.GetComponent(templateEnt);
+            var childPos = finalTransform.Transform(templateXform.LocalPosition - roomCenter);
+            var childRot = templateXform.LocalRotation + finalRoomRotation;
+            var protoId = _metaQuery.GetComponent(templateEnt).EntityPrototype?.ID;
+
+            // TODO: Copy the templated entity as is with serv
+            var ent = Spawn(protoId, new EntityCoordinates(gridUid, childPos));
+
+            var childXform = _xformQuery.GetComponent(ent);
+            var anchored = templateXform.Anchored;
+            _transform.SetLocalRotation(ent, childRot, childXform);
+
+            // If the templated entity was anchored then anchor us too.
+            if (anchored && !childXform.Anchored)
+                _transform.AnchorEntity(ent, childXform, grid);
+            else if (!anchored && childXform.Anchored)
+                _transform.Unanchor(ent, childXform);
+        }
+
+        // Load decals
+        if (TryComp<DecalGridComponent>(templateMapUid, out var loadedDecals))
+        {
+            EnsureComp<DecalGridComponent>(gridUid);
+
+            foreach (var (_, decal) in _decals.GetDecalsIntersecting(templateMapUid, bounds, loadedDecals))
+            {
+                // Offset by 0.5 because decals are offset from bot-left corner
+                // So we convert it to center of tile then convert it back again after transform.
+                // Do these shenanigans because 32x32 decals assume as they are centered on bottom-left of tiles.
+                var position = finalTransform.Transform(decal.Coordinates + Vector2Helpers.Half - roomCenter);
+                position -= Vector2Helpers.Half;
+
+                // Umm uhh I love decals so uhhhh idk what to do about this
+                var angle = (decal.Angle + finalRoomRotation).Reduced();
+
+                // Adjust because 32x32 so we can't rotate cleanly
+                // Yeah idk about the uhh vectors here but it looked visually okay but they may still be off by 1.
+                // Also EyeManager.PixelsPerMeter should really be in shared.
+                if (angle.Equals(Math.PI))
+                {
+                    position += new Vector2(-1f / 32f, 1f / 32f);
+                }
+                else if (angle.Equals(-Math.PI / 2f))
+                {
+                    position += new Vector2(-1f / 32f, 0f);
+                }
+                else if (angle.Equals(Math.PI / 2f))
+                {
+                    position += new Vector2(0f, 1f / 32f);
+                }
+                else if (angle.Equals(Math.PI * 1.5f))
+                {
+                    // I hate this but decals are bottom-left rather than center position and doing the
+                    // matrix ops is a PITA hence this workaround for now; I also don't want to add a stupid
+                    // field for 1 specific op on decals
+                    if (decal.Id != "DiagonalCheckerAOverlay" &&
+                        decal.Id != "DiagonalCheckerBOverlay")
+                    {
+                        position += new Vector2(-1f / 32f, 0f);
+                    }
+                }
+
+                var tilePos = position.Floored();
+
+                // Fallback because uhhhhhhhh yeah, a corner tile might look valid on the original
+                // but place 1 nanometre off grid and fail the add.
+                if (!_maps.TryGetTileRef(gridUid, grid, tilePos, out var tileRef) || tileRef.Tile.IsEmpty)
+                {
+                    _maps.SetTile(gridUid, grid, tilePos, _tileDefManager.GetVariantTile(FallbackTileId, _random));
+                }
+
+                var result = _decals.TryAddDecal(
+                    decal.Id,
+                    new EntityCoordinates(gridUid, position),
+                    out _,
+                    decal.Color,
+                    angle,
+                    decal.ZIndex,
+                    decal.Cleanable);
+
+                DebugTools.Assert(result);
+            }
+        }
+    }
+}
index d8377940f8eadb41e809e2af860ad5b78aca5ad9..2352aa5120471e69be514bda89ee02ba7f0fd75f 100644 (file)
@@ -7,6 +7,7 @@ using Content.Server.GameTicking.Events;
 using Content.Shared.CCVar;
 using Content.Shared.Construction.EntitySystems;
 using Content.Shared.GameTicking;
+using Content.Shared.Maps;
 using Content.Shared.Physics;
 using Content.Shared.Procedural;
 using Robust.Server.GameObjects;
@@ -15,6 +16,7 @@ using Robust.Shared.Console;
 using Robust.Shared.Map;
 using Robust.Shared.Map.Components;
 using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
 
 namespace Content.Server.Procedural;
 
@@ -24,14 +26,20 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
     [Dependency] private readonly IConsoleHost _console = default!;
     [Dependency] private readonly IMapManager _mapManager = default!;
     [Dependency] private readonly IPrototypeManager _prototype = default!;
+    [Dependency] private readonly IRobustRandom _random = default!;
     [Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
     [Dependency] private readonly AnchorableSystem _anchorable = default!;
     [Dependency] private readonly DecalSystem _decals = default!;
     [Dependency] private readonly EntityLookupSystem _lookup = default!;
     [Dependency] private readonly MapLoaderSystem _loader = default!;
+    [Dependency] private readonly SharedMapSystem _maps = default!;
     [Dependency] private readonly SharedTransformSystem _transform = default!;
 
-    private ISawmill _sawmill = default!;
+    private HashSet<EntityUid> _entSet = new();
+    private readonly List<(Vector2i, Tile)> _tiles = new();
+
+    private EntityQuery<MetaDataComponent> _metaQuery;
+    private EntityQuery<TransformComponent> _xformQuery;
 
     private const double DungeonJobTime = 0.005;
 
@@ -41,10 +49,15 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
     private readonly JobQueue _dungeonJobQueue = new(DungeonJobTime);
     private readonly Dictionary<DungeonJob, CancellationTokenSource> _dungeonJobs = new();
 
+    [ValidatePrototypeId<ContentTileDefinition>]
+    public const string FallbackTileId = "FloorSteel";
+
     public override void Initialize()
     {
         base.Initialize();
-        _sawmill = Logger.GetSawmill("dungen");
+
+        _metaQuery = GetEntityQuery<MetaDataComponent>();
+        _xformQuery = GetEntityQuery<TransformComponent>();
         _console.RegisterCommand("dungen", Loc.GetString("cmd-dungen-desc"), Loc.GetString("cmd-dungen-help"), GenerateDungeon, CompletionCallback);
         _console.RegisterCommand("dungen_preset_vis", Loc.GetString("cmd-dungen_preset_vis-desc"), Loc.GetString("cmd-dungen_preset_vis-help"), DungeonPresetVis, PresetCallback);
         _console.RegisterCommand("dungen_pack_vis", Loc.GetString("cmd-dungen_pack_vis-desc"), Loc.GetString("cmd-dungen_pack_vis-help"), DungeonPackVis, PackCallback);
@@ -176,7 +189,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
     {
         var cancelToken = new CancellationTokenSource();
         var job = new DungeonJob(
-            _sawmill,
+            Log,
             DungeonJobTime,
             EntityManager,
             _mapManager,
@@ -207,7 +220,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
     {
         var cancelToken = new CancellationTokenSource();
         var job = new DungeonJob(
-            _sawmill,
+            Log,
             DungeonJobTime,
             EntityManager,
             _mapManager,
diff --git a/Content.Server/Procedural/RoomFillComponent.cs b/Content.Server/Procedural/RoomFillComponent.cs
new file mode 100644 (file)
index 0000000..50d0fa7
--- /dev/null
@@ -0,0 +1,37 @@
+using Content.Shared.Procedural;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Procedural;
+
+/// <summary>
+/// Marker that indicates the specified room prototype should occupy this space.
+/// </summary>
+[RegisterComponent]
+public sealed partial class RoomFillComponent : Component
+{
+    /// <summary>
+    /// Are we allowed to rotate room templates?
+    /// If the room is not a square this will only do 180 degree rotations.
+    /// </summary>
+    [DataField]
+    public bool Rotation = true;
+
+    /// <summary>
+    /// Size of the room to fill.
+    /// </summary>
+    [DataField(required: true)]
+    public Vector2i Size;
+
+    /// <summary>
+    /// Rooms allowed for the marker.
+    /// </summary>
+    [DataField]
+    public EntityWhitelist? RoomWhitelist;
+    
+    /// <summary>
+    /// Should any existing entities / decals be bulldozed first.
+    /// </summary>
+    [DataField]
+    public bool ClearExisting;
+}
diff --git a/Content.Server/Procedural/RoomFillSystem.cs b/Content.Server/Procedural/RoomFillSystem.cs
new file mode 100644 (file)
index 0000000..20ffa98
--- /dev/null
@@ -0,0 +1,50 @@
+using Robust.Shared.Map.Components;
+
+namespace Content.Server.Procedural;
+
+public sealed class RoomFillSystem : EntitySystem
+{
+    [Dependency] private readonly DungeonSystem _dungeon = default!;
+    [Dependency] private readonly SharedMapSystem _maps = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+        SubscribeLocalEvent<RoomFillComponent, MapInitEvent>(OnRoomFillMapInit);
+    }
+
+    private void OnRoomFillMapInit(EntityUid uid, RoomFillComponent component, MapInitEvent args)
+    {
+        // Just test things.
+        if (component.Size == Vector2i.Zero)
+            return;
+
+        var xform = Transform(uid);
+
+        if (xform.GridUid != null)
+        {
+            var random = new Random();
+            var room = _dungeon.GetRoomPrototype(component.Size, random, component.RoomWhitelist);
+
+            if (room != null)
+            {
+                var mapGrid = Comp<MapGridComponent>(xform.GridUid.Value);
+                _dungeon.SpawnRoom(
+                    xform.GridUid.Value,
+                    mapGrid,
+                    _maps.LocalToTile(xform.GridUid.Value, mapGrid, xform.Coordinates),
+                    room,
+                    random,
+                    clearExisting: component.ClearExisting,
+                    rotation: component.Rotation);
+            }
+            else
+            {
+                Log.Error($"Unable to find matching room prototype for {ToPrettyString(uid)}");
+            }
+        }
+
+        // Final cleanup
+        QueueDel(uid);
+    }
+}
diff --git a/Resources/Prototypes/Entities/Markers/rooms.yml b/Resources/Prototypes/Entities/Markers/rooms.yml
new file mode 100644 (file)
index 0000000..e4f341d
--- /dev/null
@@ -0,0 +1,13 @@
+- type: entity
+  id: BaseRoomMarker
+  name: Room marker
+  parent: MarkerBase
+  suffix: Weh
+  components:
+  - type: RoomFill
+    size: 5,5
+  - type: Sprite
+    layers:
+      - state: red
+      - sprite: Mobs/Aliens/elemental.rsi
+        state: alive