]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Allow station tiles to be placed on solid ground and other platings. (#38898)
authorVelken <8467292+Velken@users.noreply.github.com>
Tue, 13 Jan 2026 13:51:40 +0000 (10:51 -0300)
committerGitHub <noreply@github.com>
Tue, 13 Jan 2026 13:51:40 +0000 (13:51 +0000)
* WORK IN PROGRESS 1

* ITS ALIVE, ALIVE!!!!

* clean up

* WIP 1

* fix small oversight

* big diff of doom

* added CVAR to tile history stack size

* component time

* filescoped namespaces + remove redundant nametag

* fix silly little mistakes

* typo

* TileStacksTest

* bweeeeeeh :P

* nuke cvar

* :3

* WIP2025

* Fix submodule

* It's beginning to look a lot like Christmas

* It's the Most Wonderful Time of the Year

* tiny fix

* fixed extra spacing on yaml

* slightly improve tilestacking test

* Part 1 out of 2 (part 2 tomorrow)

* Part 2

* add a simple tile construction test for tilestacking

* guh

* address reviews (no documentation yet)

* documentation be upon ye

* remove extra spaces

* prediction fix

* dirt

* oops :p

---------

Co-authored-by: Killerqu00 <killerqueen1777@gmail.com>
Co-authored-by: Killerqu00 <47712032+Killerqu00@users.noreply.github.com>
Co-authored-by: ScarKy0 <scarky0@onet.eu>
15 files changed:
Content.IntegrationTests/Tests/Construction/RCDTest.cs
Content.IntegrationTests/Tests/Interaction/InteractionTest.Constants.cs
Content.IntegrationTests/Tests/Tiles/TileConstructionTests.cs
Content.IntegrationTests/Tests/Tiles/TileStackRecursionTest.cs [new file with mode: 0644]
Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs
Content.Server/Maps/TileGridSplitSystem.cs [new file with mode: 0644]
Content.Shared/CCVar/CCVars.Game.cs
Content.Shared/Maps/ContentTileDefinition.cs
Content.Shared/Maps/TileHistoryComponent.cs [new file with mode: 0644]
Content.Shared/Maps/TileSystem.cs
Content.Shared/RCD/Systems/RCDSystem.cs
Content.Shared/Tiles/FloorTileSystem.cs
Resources/Prototypes/Tiles/floors.yml
Resources/Prototypes/Tiles/planet.yml
Resources/Prototypes/Tiles/plating.yml

index 814f7e89aa81155a11342d4a378c47b1518b3c59..f20a0cb434c50fe6c2a83545168b255fca6ad132 100644 (file)
@@ -194,7 +194,7 @@ public sealed class RCDTest : InteractionTest
         // Deconstruct the steel tile.
         await Interact(null, pEast);
         await RunSeconds(settingDeconstructTile.Delay + 1); // wait for the deconstruction to finish
-        await AssertTile(Lattice, FromServer(pEast));
+        await AssertTile(Plating, FromServer(pEast));
 
         // Check that the cost of the deconstruction was subtracted from the current charges.
         newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
index 8917ba7ead558495aa05aacc0821f39db1f6c3a3..3cfb5a5dbaffa52d5956509053f73076ebc0a7f5 100644 (file)
@@ -12,6 +12,7 @@ public abstract partial class InteractionTest
     protected const string FloorItem = "FloorTileItemSteel";
     protected const string Plating = "Plating";
     protected const string Lattice = "Lattice";
+    protected const string PlatingBrass = "PlatingBrass";
 
     // Structures
     protected const string Airlock = "Airlock";
index 0827e11b705804791ed186dfdb02bfa06023663a..64c4c291feb70bf9c9eca043b56f40e7b77a6406 100644 (file)
@@ -100,4 +100,25 @@ public sealed class TileConstructionTests : InteractionTest
 
         await AssertEntityLookup((FloorItem, 1));
     }
+
+    /// <summary>
+    /// Test brassPlating -> floor -> brassPlating using tilestacking
+    /// </summary>
+    [Test]
+    public async Task BrassPlatingPlace()
+    {
+        await SetTile(PlatingBrass);
+
+        // Brass Plating -> Tile
+        await InteractUsing(FloorItem);
+        Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
+        await AssertTile(Floor);
+        AssertGridCount(1);
+
+        // Tile -> Brass Plating
+        await InteractUsing(Pry);
+        await AssertTile(PlatingBrass);
+        AssertGridCount(1);
+        await AssertEntityLookup((FloorItem, 1));
+    }
 }
diff --git a/Content.IntegrationTests/Tests/Tiles/TileStackRecursionTest.cs b/Content.IntegrationTests/Tests/Tiles/TileStackRecursionTest.cs
new file mode 100644 (file)
index 0000000..52c5b03
--- /dev/null
@@ -0,0 +1,66 @@
+using System.Collections.Generic;
+using System.Linq;
+using Content.Shared.CCVar;
+using Content.Shared.Maps;
+using Robust.Shared.Configuration;
+using Robust.Shared.Prototypes;
+
+namespace Content.IntegrationTests.Tests.Tiles;
+
+public sealed class TileStackRecursionTest
+{
+    [Test]
+    public async Task TestBaseTurfRecursion()
+    {
+        await using var pair = await PoolManager.GetServerClient();
+        var protoMan = pair.Server.ResolveDependency<IPrototypeManager>();
+        var cfg = pair.Server.ResolveDependency<IConfigurationManager>();
+        var maxTileHistoryLength = cfg.GetCVar(CCVars.TileStackLimit);
+        Assert.That(protoMan.TryGetInstances<ContentTileDefinition>(out var tiles));
+        Assert.That(tiles, Is.Not.EqualTo(null));
+        //store the distance from the root node to the given tile node
+        var nodes = new List<(ProtoId<ContentTileDefinition>, int)>();
+        //each element of list is a connection from BaseTurf tile to tile that goes on it
+        var edges = new List<(ProtoId<ContentTileDefinition>, ProtoId<ContentTileDefinition>)>();
+        foreach (var ctdef in tiles!.Values)
+        {
+            //at first, each node is unexplored and has infinite distance to root.
+            //we use space node as root - everything is supposed to start at space, and it's hardcoded into the game anyway.
+            if (ctdef.ID == ContentTileDefinition.SpaceID)
+            {
+                nodes.Insert(0, (ctdef.ID, 0)); //space is the first element
+                continue;
+            }
+            Assert.That(ctdef.BaseTurf != ctdef.ID);
+            nodes.Add((ctdef.ID, int.MaxValue));
+            if (ctdef.BaseTurf != null)
+                edges.Add((ctdef.BaseTurf.Value, ctdef.ID));
+            Assert.That(ctdef.BaseWhitelist, Does.Not.Contain(ctdef.ID));
+            edges.AddRange(ctdef.BaseWhitelist.Select(possibleTurf =>
+                (possibleTurf, new ProtoId<ContentTileDefinition>(ctdef.ID))));
+        }
+        Bfs(nodes, edges, maxTileHistoryLength);
+        await pair.CleanReturnAsync();
+    }
+
+    private void Bfs(List<(ProtoId<ContentTileDefinition>, int)> nodes, List<(ProtoId<ContentTileDefinition>, ProtoId<ContentTileDefinition>)> edges, int depthLimit)
+    {
+        var root = nodes[0];
+        var queue = new Queue<(ProtoId<ContentTileDefinition>, int)>();
+        queue.Enqueue(root);
+        while (queue.Count != 0)
+        {
+            var u = queue.Dequeue();
+            //get a list of tiles that can be put on this tile
+            var adj = edges.Where(n => n.Item1 == u.Item1).Select(n => n.Item2);
+            var adjNodes = nodes.Where(n => adj.Contains(n.Item1)).ToList();
+            foreach (var node in adjNodes)
+            {
+                var adjNode = node;
+                adjNode.Item2 = u.Item2 + 1;
+                Assert.That(adjNode.Item2, Is.LessThanOrEqualTo(depthLimit)); //we can doomstack tiles on top of each other. Bad!
+                queue.Enqueue(adjNode);
+            }
+        }
+    }
+}
index 2cec8d707aab221d67b283ee59bcbe63af65f9dc..37045097f3f3f18fbd41115862e149a63e552f04 100644 (file)
@@ -517,17 +517,39 @@ public sealed partial class ExplosionSystem
         else if (tileDef.MapAtmosphere)
             canCreateVacuum = true; // is already a vacuum.
 
+        var history = CompOrNull<TileHistoryComponent>(tileRef.GridUid);
+
+        // break the tile into its underlying parts
         int tileBreakages = 0;
         while (maxTileBreak > tileBreakages && _robustRandom.Prob(type.TileBreakChance(effectiveIntensity)))
         {
             tileBreakages++;
             effectiveIntensity -= type.TileBreakRerollReduction;
 
-            // does this have a base-turf that we can break it down to?
-            if (string.IsNullOrEmpty(tileDef.BaseTurf))
-                break;
+            ContentTileDefinition? newDef = null;
+
+            // if we have tile history, we revert the tile to its previous state
+            var chunkIndices = SharedMapSystem.GetChunkIndices(tileRef.GridIndices, TileSystem.ChunkSize);
+            if (history != null && history.ChunkHistory.TryGetValue(chunkIndices, out var chunk) &&
+                chunk.History.TryGetValue(tileRef.GridIndices, out var stack) && stack.Count > 0)
+            {
+                // last entry in the stack
+                var newId = stack[^1];
+                stack.RemoveAt(stack.Count - 1);
+                if (stack.Count == 0)
+                    chunk.History.Remove(tileRef.GridIndices);
+
+                Dirty(tileRef.GridUid, history);
+
+                newDef = (ContentTileDefinition) _tileDefinitionManager[newId.Id];
+            }
+            else if (tileDef.BaseTurf.HasValue)
+            {
+                // otherwise, we just use the base turf
+                newDef = (ContentTileDefinition) _tileDefinitionManager[tileDef.BaseTurf.Value];
+            }
 
-            if (_tileDefinitionManager[tileDef.BaseTurf] is not ContentTileDefinition newDef)
+            if (newDef == null)
                 break;
 
             if (newDef.MapAtmosphere && !canCreateVacuum)
diff --git a/Content.Server/Maps/TileGridSplitSystem.cs b/Content.Server/Maps/TileGridSplitSystem.cs
new file mode 100644 (file)
index 0000000..fef0efe
--- /dev/null
@@ -0,0 +1,74 @@
+using System.Numerics;
+using Content.Shared.Maps;
+using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Maps;
+
+/// <summary>
+/// This system handles transferring <see cref="TileHistoryComponent"/> data when a grid is split.
+/// </summary>
+public sealed class TileGridSplitSystem : EntitySystem
+{
+    [Dependency] private readonly SharedMapSystem _maps = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+        SubscribeLocalEvent<GridSplitEvent>(OnGridSplit);
+    }
+
+    /// <summary>
+    /// Transfer tile history from the old grid to the new grids.
+    /// </summary>
+    private void OnGridSplit(ref GridSplitEvent ev)
+    {
+        if (!TryComp<TileHistoryComponent>(ev.Grid, out var oldHistory))
+            return;
+
+        var oldGrid = Comp<MapGridComponent>(ev.Grid);
+
+        foreach (var gridUid in ev.NewGrids)
+        {
+            // ensure the new grid has a history component and get its grid component
+            var newHistory = EnsureComp<TileHistoryComponent>(gridUid);
+            var newGrid = Comp<MapGridComponent>(gridUid);
+
+            foreach (var tile in _maps.GetAllTiles(gridUid, newGrid))
+            {
+                // calculate where this tile was on the old grid
+                var oldIndices = _maps.LocalToTile(ev.Grid, oldGrid, new EntityCoordinates(gridUid, new Vector2(tile.GridIndices.X + 0.5f, tile.GridIndices.Y + 0.5f)));
+
+                var chunkIndices = SharedMapSystem.GetChunkIndices(oldIndices, TileSystem.ChunkSize);
+                if (oldHistory.ChunkHistory.TryGetValue(chunkIndices, out var oldChunk) &&
+                    oldChunk.History.TryGetValue(oldIndices, out var history))
+                {
+                    // now we move the history from the old grid to the new grid
+                    var newChunkIndices = SharedMapSystem.GetChunkIndices(tile.GridIndices, TileSystem.ChunkSize);
+                    if (!newHistory.ChunkHistory.TryGetValue(newChunkIndices, out var newChunk))
+                    {
+                        newChunk = new TileHistoryChunk();
+                        newHistory.ChunkHistory[newChunkIndices] = newChunk;
+                    }
+
+                    newChunk.History[tile.GridIndices] = new List<ProtoId<ContentTileDefinition>>(history);
+                    newChunk.LastModified = _timing.CurTick;
+
+                    // clean up the old history
+                    oldChunk.History.Remove(oldIndices);
+                    if (oldChunk.History.Count == 0)
+                        oldHistory.ChunkHistory.Remove(chunkIndices);
+                    else
+                        oldChunk.LastModified = _timing.CurTick;
+                }
+            }
+
+            Dirty(gridUid, newHistory);
+        }
+
+        Dirty(ev.Grid, oldHistory);
+    }
+}
index 68342733f8ecf2524d4e59bc0af412cd3837f913..03cf6a673ae138fccfca42832cee198e56a4128f 100644 (file)
@@ -409,4 +409,13 @@ public sealed partial class CCVars
     /// </summary>
     public static readonly CVarDef<bool> GameHostnameInTitlebar =
         CVarDef.Create("game.hostname_in_titlebar", true, CVar.SERVER | CVar.REPLICATED);
+
+    /// <summary>
+    /// The maximum amount of tiles you can stack on top of each other. 0 is unlimited.
+    /// </summary>
+    /// <remarks>
+    /// Having it too high can result in "doomstacking" tiles - this messes with efficiency of explosions, deconstruction of tiles, and might result in memory problems.
+    /// </remarks>
+    public static readonly CVarDef<int> TileStackLimit =
+        CVarDef.Create("game.tile_stack_limit", 5, CVar.SERVER | CVar.REPLICATED);
 }
index 46ce7a212ec6c047e7375ae0448320fb061e7f80..672eb95911fdbe425ce79cc38d2643db7b49800b 100644 (file)
@@ -8,6 +8,7 @@ using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
 using Robust.Shared.Utility;
 
 namespace Content.Shared.Maps
@@ -41,7 +42,13 @@ namespace Content.Shared.Maps
         [DataField("isSubfloor")] public bool IsSubFloor { get; private set; }
 
         [DataField("baseTurf")]
-        public string BaseTurf { get; private set; } = string.Empty;
+        public ProtoId<ContentTileDefinition>? BaseTurf { get; private set; }
+
+        /// <summary>
+        /// On what tiles this tile can be placed on. BaseTurf is already included.
+        /// </summary>
+        [DataField]
+        public List<ProtoId<ContentTileDefinition>> BaseWhitelist { get; private set; } = new();
 
         [DataField]
         public PrototypeFlags<ToolQualityPrototype> DeconstructTools { get; set; } = new();
diff --git a/Content.Shared/Maps/TileHistoryComponent.cs b/Content.Shared/Maps/TileHistoryComponent.cs
new file mode 100644 (file)
index 0000000..0e02d60
--- /dev/null
@@ -0,0 +1,125 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Maps;
+
+[RegisterComponent, NetworkedComponent]
+public sealed partial class TileHistoryComponent : Component
+{
+    // History of tiles for each grid chunk.
+    [DataField]
+    public Dictionary<Vector2i, TileHistoryChunk> ChunkHistory = new();
+
+    /// <summary>
+    ///     Tick at which PVS was last toggled. Ensures that all players receive a full update when toggling PVS.
+    /// </summary>
+    public GameTick ForceTick { get; set; }
+}
+
+[Serializable, NetSerializable]
+public sealed class TileHistoryState : ComponentState
+{
+    public Dictionary<Vector2i, TileHistoryChunk> ChunkHistory;
+
+    public TileHistoryState(Dictionary<Vector2i, TileHistoryChunk> chunkHistory)
+    {
+        ChunkHistory = chunkHistory;
+    }
+}
+
+[Serializable, NetSerializable]
+public sealed class TileHistoryDeltaState : ComponentState, IComponentDeltaState<TileHistoryState>
+{
+    public Dictionary<Vector2i, TileHistoryChunk> ChunkHistory;
+    public HashSet<Vector2i> AllHistoryChunks;
+
+    public TileHistoryDeltaState(Dictionary<Vector2i, TileHistoryChunk> chunkHistory, HashSet<Vector2i> allHistoryChunks)
+    {
+        ChunkHistory = chunkHistory;
+        AllHistoryChunks = allHistoryChunks;
+    }
+
+    public void ApplyToFullState(TileHistoryState state)
+    {
+        var toRemove = new List<Vector2i>();
+        foreach (var key in state.ChunkHistory.Keys)
+        {
+            if (!AllHistoryChunks.Contains(key))
+                toRemove.Add(key);
+        }
+
+        foreach (var key in toRemove)
+        {
+            state.ChunkHistory.Remove(key);
+        }
+
+        foreach (var (indices, chunk) in ChunkHistory)
+        {
+            state.ChunkHistory[indices] = new TileHistoryChunk(chunk);
+        }
+    }
+
+    public void ApplyToComponent(TileHistoryComponent component)
+    {
+        var toRemove = new List<Vector2i>();
+        foreach (var key in component.ChunkHistory.Keys)
+        {
+            if (!AllHistoryChunks.Contains(key))
+                toRemove.Add(key);
+        }
+
+        foreach (var key in toRemove)
+        {
+            component.ChunkHistory.Remove(key);
+        }
+
+        foreach (var (indices, chunk) in ChunkHistory)
+        {
+            component.ChunkHistory[indices] = new TileHistoryChunk(chunk);
+        }
+    }
+
+    public TileHistoryState CreateNewFullState(TileHistoryState state)
+    {
+        var chunks = new Dictionary<Vector2i, TileHistoryChunk>(state.ChunkHistory.Count);
+
+        foreach (var (indices, chunk) in ChunkHistory)
+        {
+            chunks[indices] = new TileHistoryChunk(chunk);
+        }
+
+        foreach (var (indices, chunk) in state.ChunkHistory)
+        {
+            if (AllHistoryChunks.Contains(indices))
+                chunks.TryAdd(indices, new TileHistoryChunk(chunk));
+        }
+
+        return new TileHistoryState(chunks);
+    }
+}
+
+[DataDefinition, Serializable, NetSerializable]
+public sealed partial class TileHistoryChunk
+{
+    [DataField]
+    public Dictionary<Vector2i, List<ProtoId<ContentTileDefinition>>> History = new();
+
+    [ViewVariables]
+    public GameTick LastModified;
+
+    public TileHistoryChunk()
+    {
+    }
+
+    public TileHistoryChunk(TileHistoryChunk other)
+    {
+        History = new Dictionary<Vector2i, List<ProtoId<ContentTileDefinition>>>(other.History.Count);
+        foreach (var (key, value) in other.History)
+        {
+            History[key] = new List<ProtoId<ContentTileDefinition>>(value);
+        }
+        LastModified = other.LastModified;
+    }
+}
index d87b3ca50d542abcf232a0ca86ed6d55c61da760..908507e54dd4350a90a0e1a01a7a66adc1fb58d9 100644 (file)
@@ -1,10 +1,16 @@
 using System.Linq;
 using System.Numerics;
+using Content.Shared.CCVar;
 using Content.Shared.Coordinates.Helpers;
 using Content.Shared.Decals;
+using Content.Shared.Tiles;
+using Robust.Shared.Configuration;
+using Robust.Shared.GameStates;
 using Robust.Shared.Map;
 using Robust.Shared.Map.Components;
+using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
+using Robust.Shared.Timing;
 using Robust.Shared.Utility;
 
 namespace Content.Shared.Maps;
@@ -14,12 +20,85 @@ namespace Content.Shared.Maps;
 /// </summary>
 public sealed class TileSystem : EntitySystem
 {
+    [Dependency] private readonly IConfigurationManager _cfg = default!;
     [Dependency] private readonly IMapManager _mapManager = default!;
     [Dependency] private readonly IRobustRandom _robustRandom = default!;
     [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
     [Dependency] private readonly SharedDecalSystem _decal = default!;
     [Dependency] private readonly SharedMapSystem _maps = default!;
     [Dependency] private readonly TurfSystem _turf = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
+
+    public const int ChunkSize = 16;
+
+    private int _tileStackLimit;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+        SubscribeLocalEvent<GridInitializeEvent>(OnGridStartup);
+        SubscribeLocalEvent<TileHistoryComponent, ComponentGetState>(OnGetState);
+        SubscribeLocalEvent<TileHistoryComponent, ComponentHandleState>(OnHandleState);
+        SubscribeLocalEvent<TileHistoryComponent, FloorTileAttemptEvent>(OnFloorTileAttempt);
+
+        _cfg.OnValueChanged(CCVars.TileStackLimit, t => _tileStackLimit = t, true);
+    }
+
+    private void OnHandleState(EntityUid uid, TileHistoryComponent component, ref ComponentHandleState args)
+    {
+        if (args.Current is not TileHistoryState state && args.Current is not TileHistoryDeltaState)
+            return;
+
+        if (args.Current is TileHistoryState fullState)
+        {
+            component.ChunkHistory.Clear();
+            foreach (var (key, value) in fullState.ChunkHistory)
+            {
+                component.ChunkHistory[key] = new TileHistoryChunk(value);
+            }
+
+            return;
+        }
+
+        if (args.Current is TileHistoryDeltaState deltaState)
+        {
+            deltaState.ApplyToComponent(component);
+        }
+    }
+
+    private void OnGetState(EntityUid uid, TileHistoryComponent component, ref ComponentGetState args)
+    {
+        if (args.FromTick <= component.CreationTick || args.FromTick <= component.ForceTick)
+        {
+            var fullHistory = new Dictionary<Vector2i, TileHistoryChunk>(component.ChunkHistory.Count);
+            foreach (var (key, value) in component.ChunkHistory)
+            {
+                fullHistory[key] = new TileHistoryChunk(value);
+            }
+            args.State = new TileHistoryState(fullHistory);
+            return;
+        }
+
+        var data = new Dictionary<Vector2i, TileHistoryChunk>();
+        foreach (var (index, chunk) in component.ChunkHistory)
+        {
+            if (chunk.LastModified >= args.FromTick)
+                data[index] = new TileHistoryChunk(chunk);
+        }
+
+        args.State = new TileHistoryDeltaState(data, new(component.ChunkHistory.Keys));
+    }
+
+    /// <summary>
+    /// On grid startup, ensure that we have Tile History.
+    /// </summary>
+    private void OnGridStartup(GridInitializeEvent ev)
+    {
+        if (HasComp<MapComponent>(ev.EntityUid))
+            return;
+
+        EnsureComp<TileHistoryComponent>(ev.EntityUid);
+    }
 
     /// <summary>
     ///     Returns a weighted pick of a tile variant.
@@ -85,7 +164,7 @@ public sealed class TileSystem : EntitySystem
         return PryTile(tileRef);
     }
 
-       public bool PryTile(TileRef tileRef)
+    public bool PryTile(TileRef tileRef)
     {
         return PryTile(tileRef, false);
     }
@@ -97,7 +176,7 @@ public sealed class TileSystem : EntitySystem
         if (tile.IsEmpty)
             return false;
 
-        var tileDef = (ContentTileDefinition) _tileDefinitionManager[tile.TypeId];
+        var tileDef = (ContentTileDefinition)_tileDefinitionManager[tile.TypeId];
 
         if (!tileDef.CanCrowbar)
             return false;
@@ -112,33 +191,73 @@ public sealed class TileSystem : EntitySystem
         return ReplaceTile(tileref, replacementTile, tileref.GridUid, grid);
     }
 
-    public bool ReplaceTile(TileRef tileref, ContentTileDefinition replacementTile, EntityUid grid, MapGridComponent? component = null)
+    public bool ReplaceTile(TileRef tileref, ContentTileDefinition replacementTile, EntityUid grid, MapGridComponent? component = null, byte? variant = null)
     {
         DebugTools.Assert(tileref.GridUid == grid);
 
         if (!Resolve(grid, ref component))
             return false;
 
+        var key = tileref.GridIndices;
+        var currentTileDef = (ContentTileDefinition) _tileDefinitionManager[tileref.Tile.TypeId];
+
+        // If the tile we're placing has a baseTurf that matches the tile we're replacing, we don't need to create a history
+        // unless the tile already has a history.
+        var history = EnsureComp<TileHistoryComponent>(grid);
+        var chunkIndices = SharedMapSystem.GetChunkIndices(key, ChunkSize);
+        history.ChunkHistory.TryGetValue(chunkIndices, out var chunk);
+        var historyExists = chunk != null && chunk.History.ContainsKey(key);
+
+        if (replacementTile.BaseTurf != currentTileDef.ID || historyExists)
+        {
+            if (chunk == null)
+            {
+                chunk = new TileHistoryChunk();
+                history.ChunkHistory[chunkIndices] = chunk;
+            }
+
+            chunk.LastModified = _timing.CurTick;
+            Dirty(grid, history);
+
+            //Create stack if needed
+            if (!chunk.History.TryGetValue(key, out var stack))
+            {
+                stack = new List<ProtoId<ContentTileDefinition>>();
+                chunk.History[key] = stack;
+            }
+
+            //Prevent the doomstack
+            if (stack.Count >= _tileStackLimit && _tileStackLimit != 0)
+                return false;
+
+            //Push current tile to the stack, if not empty
+            if (!tileref.Tile.IsEmpty)
+            {
+                stack.Add(currentTileDef.ID);
+            }
+        }
 
-        var variant = PickVariant(replacementTile);
+        variant ??= PickVariant(replacementTile);
         var decals = _decal.GetDecalsInRange(tileref.GridUid, _turf.GetTileCenter(tileref).Position, 0.5f);
         foreach (var (id, _) in decals)
         {
             _decal.RemoveDecal(tileref.GridUid, id);
         }
 
-        _maps.SetTile(grid, component, tileref.GridIndices, new Tile(replacementTile.TileId, 0, variant));
+        _maps.SetTile(grid, component, tileref.GridIndices, new Tile(replacementTile.TileId, 0, variant.Value));
         return true;
     }
 
-    public bool DeconstructTile(TileRef tileRef)
+
+    public bool DeconstructTile(TileRef tileRef, bool spawnItem = true)
     {
         if (tileRef.Tile.IsEmpty)
             return false;
 
-        var tileDef = (ContentTileDefinition) _tileDefinitionManager[tileRef.Tile.TypeId];
+        var tileDef = (ContentTileDefinition)_tileDefinitionManager[tileRef.Tile.TypeId];
 
-        if (string.IsNullOrEmpty(tileDef.BaseTurf))
+        //Can't deconstruct anything that doesn't have a base turf.
+        if (tileDef.BaseTurf == null)
             return false;
 
         var gridUid = tileRef.GridUid;
@@ -152,20 +271,68 @@ public sealed class TileSystem : EntitySystem
                 (_robustRandom.NextFloat() - 0.5f) * bounds,
                 (_robustRandom.NextFloat() - 0.5f) * bounds));
 
-        //Actually spawn the relevant tile item at the right position and give it some random offset.
-        var tileItem = Spawn(tileDef.ItemDropPrototypeName, coordinates);
-        Transform(tileItem).LocalRotation = _robustRandom.NextDouble() * Math.Tau;
+        var historyComp = EnsureComp<TileHistoryComponent>(gridUid);
+        ProtoId<ContentTileDefinition> previousTileId;
 
-        // Destroy any decals on the tile
+        var chunkIndices = SharedMapSystem.GetChunkIndices(indices, ChunkSize);
+
+        //Pop from stack if we have history
+        if (historyComp.ChunkHistory.TryGetValue(chunkIndices, out var chunk) &&
+            chunk.History.TryGetValue(indices, out var stack) && stack.Count > 0)
+        {
+            chunk.LastModified = _timing.CurTick;
+            Dirty(gridUid, historyComp);
+
+            previousTileId = stack.Last();
+            stack.RemoveAt(stack.Count - 1);
+
+            //Clean up empty stacks to avoid memory buildup
+            if (stack.Count == 0)
+            {
+                chunk.History.Remove(indices);
+            }
+
+            // Clean up empty chunks
+            if (chunk.History.Count == 0)
+            {
+                historyComp.ChunkHistory.Remove(chunkIndices);
+            }
+        }
+        else
+        {
+            //No stack? Assume BaseTurf was the layer below
+            previousTileId = tileDef.BaseTurf.Value;
+        }
+
+        if (spawnItem)
+        {
+            //Actually spawn the relevant tile item at the right position and give it some random offset.
+            var tileItem = Spawn(tileDef.ItemDropPrototypeName, coordinates);
+            Transform(tileItem).LocalRotation = _robustRandom.NextDouble() * Math.Tau;
+        }
+
+        //Destroy any decals on the tile
         var decals = _decal.GetDecalsInRange(gridUid, coordinates.SnapToGrid(EntityManager, _mapManager).Position, 0.5f);
         foreach (var (id, _) in decals)
         {
             _decal.RemoveDecal(tileRef.GridUid, id);
         }
 
-        var plating = _tileDefinitionManager[tileDef.BaseTurf];
-        _maps.SetTile(gridUid, mapGrid, tileRef.GridIndices, new Tile(plating.TileId));
+        //Replace tile with the one it was placed on
+        var previousDef = (ContentTileDefinition)_tileDefinitionManager[previousTileId];
+        _maps.SetTile(gridUid, mapGrid, indices, new Tile(previousDef.TileId));
 
         return true;
     }
+
+    private void OnFloorTileAttempt(Entity<TileHistoryComponent> ent, ref FloorTileAttemptEvent args)
+    {
+        if (_tileStackLimit == 0)
+            return;
+        var chunkIndices = SharedMapSystem.GetChunkIndices(args.GridIndices, ChunkSize);
+        if (!ent.Comp.ChunkHistory.TryGetValue(chunkIndices, out var chunk) ||
+            !chunk.History.TryGetValue(args.GridIndices, out var stack))
+            return;
+        args.Cancelled = stack.Count >= _tileStackLimit; // greater or equals because the attempt itself counts as a tile we're trying to place
+    }
 }
index 504576216ac4b0f728d247285c67d03020f70085..8b3ae16a1f5c5bd1f7e3e5cd347504692c0d7932 100644 (file)
@@ -38,6 +38,7 @@ public sealed class RCDSystem : EntitySystem
     [Dependency] private readonly SharedInteractionSystem _interaction = default!;
     [Dependency] private readonly SharedPopupSystem _popup = default!;
     [Dependency] private readonly TurfSystem _turf = default!;
+    [Dependency] private readonly TileSystem _tile = default!;
     [Dependency] private readonly EntityLookupSystem _lookup = default!;
     [Dependency] private readonly IPrototypeManager _protoManager = default!;
     [Dependency] private readonly SharedMapSystem _mapSystem = default!;
@@ -560,10 +561,9 @@ public sealed class RCDSystem : EntitySystem
 
                 if (target == null)
                 {
-                    // Deconstruct tile (either converts the tile to lattice, or removes lattice)
-                    var tileDef = (_turf.GetContentTileDefinition(tile).ID != "Lattice") ? new Tile(_tileDefMan["Lattice"].TileId) : Tile.Empty;
-                    _mapSystem.SetTile(gridUid, mapGrid, position, tileDef);
-                    _adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(user):user} used RCD to set grid: {gridUid} tile: {position} open to space");
+                    // Deconstruct tile, don't drop tile as item
+                    if (_tile.DeconstructTile(tile, spawnItem: false))
+                        _adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(user):user} used RCD to set grid: {gridUid} tile: {position} open to space");
                 }
                 else
                 {
index 2c6df5ce89b16ef8117fa79134197787a4091c6c..a2743ca6caa165821ef7f903e9c4990e205f3257 100644 (file)
@@ -16,6 +16,7 @@ using Robust.Shared.Network;
 using Robust.Shared.Physics;
 using Robust.Shared.Physics.Components;
 using Robust.Shared.Physics.Systems;
+using Robust.Shared.Prototypes;
 using Robust.Shared.Timing;
 
 namespace Content.Shared.Tiles;
@@ -142,7 +143,7 @@ public sealed class FloorTileSystem : EntitySystem
 
                 var baseTurf = (ContentTileDefinition) _tileDefinitionManager[tile.Tile.TypeId];
 
-                if (HasBaseTurf(currentTileDefinition, baseTurf.ID))
+                if (CanPlaceOn(currentTileDefinition, baseTurf.ID))
                 {
                     if (!_stackSystem.TryUse((uid, stack), 1))
                         continue;
@@ -152,7 +153,7 @@ public sealed class FloorTileSystem : EntitySystem
                     return;
                 }
             }
-            else if (HasBaseTurf(currentTileDefinition, ContentTileDefinition.SpaceID))
+            else if (HasBaseTurf(currentTileDefinition, new ProtoId<ContentTileDefinition>(ContentTileDefinition.SpaceID)))
             {
                 if (!_stackSystem.TryUse((uid, stack), 1))
                     continue;
@@ -171,19 +172,35 @@ public sealed class FloorTileSystem : EntitySystem
         }
     }
 
-    public bool HasBaseTurf(ContentTileDefinition tileDef, string baseTurf)
+    public bool HasBaseTurf(ContentTileDefinition tileDef, ProtoId<ContentTileDefinition> baseTurf)
     {
         return tileDef.BaseTurf == baseTurf;
     }
 
+    private bool CanPlaceOn(ContentTileDefinition tileDef, ProtoId<ContentTileDefinition> currentTurfId)
+    {
+        //Check exact BaseTurf match
+        if (tileDef.BaseTurf == currentTurfId)
+            return true;
+
+        // Check whitelist match
+        if (tileDef.BaseWhitelist.Count > 0 && tileDef.BaseWhitelist.Contains(currentTurfId))
+            return true;
+
+        return false;
+    }
+
     private void PlaceAt(EntityUid user, EntityUid gridUid, MapGridComponent mapGrid, EntityCoordinates location,
         ushort tileId, SoundSpecifier placeSound, float offset = 0)
     {
         _adminLogger.Add(LogType.Tile, LogImpact.Low, $"{ToPrettyString(user):actor} placed tile {_tileDefinitionManager[tileId].Name} at {ToPrettyString(gridUid)} {location}");
 
-        var random = new System.Random((int) _timing.CurTick.Value);
-        var variant = _tile.PickVariant((ContentTileDefinition) _tileDefinitionManager[tileId], random);
-        _map.SetTile(gridUid, mapGrid,location.Offset(new Vector2(offset, offset)), new Tile(tileId, 0, variant));
+        var tileDef = (ContentTileDefinition) _tileDefinitionManager[tileId];
+        var random = new System.Random((int)_timing.CurTick.Value);
+        var variant = _tile.PickVariant(tileDef, random);
+
+        var tileRef = _map.GetTileRef(gridUid, mapGrid, location.Offset(new Vector2(offset, offset)));
+        _tile.ReplaceTile(tileRef, tileDef, gridUid, mapGrid, variant: variant);
 
         _audio.PlayPredicted(placeSound, location, user);
     }
index de0f0dd9b464be8dbf39269551e294d2ce7a9f9c..52657990d1f128b3d9c02c1431cdef3634dfd0dd 100644 (file)
@@ -1,5 +1,31 @@
+- type: tile
+  id: BaseStationTile
+  abstract: true
+  isSubfloor: false
+  deconstructTools: [ Prying ]
+  footstepSounds:
+    collection: FootstepFloor
+  heatCapacity: 10000
+  baseTurf: Plating
+  baseWhitelist:
+  - PlatingBrass
+  - FloorAsteroidIronsand
+  - FloorAsteroidSand
+  - FloorAsteroidSandBorderless
+  - FloorAsteroidIronsandBorderless
+  - FloorAsteroidSandRedBorderless
+  - PlatingAsteroid
+  - PlatingSnow
+  - FloorPlanetDirt
+  - FloorDesert
+  - FloorLowDesert
+  - FloorPlanetGrass
+  - FloorSnow
+  - FloorDirt
+
 - type: tile
   id: FloorSteel
+  parent: BaseStationTile
   name: tiles-steel-floor
   sprite: /Textures/Tiles/steel.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemSteel
-  heatCapacity: 10000
 
 - type: tile
   id: FloorSteelCheckerLight
+  parent: BaseStationTile
   name: tiles-steel-floor-checker-light
   sprite: /Textures/Tiles/cafeteria.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemSteelCheckerLight
-  heatCapacity: 10000
 
 - type: tile
   id: FloorSteelCheckerDark
+  parent: BaseStationTile
   name: tiles-steel-floor-checker-dark
   sprite: /Textures/Tiles/checker_dark.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemSteelCheckerDark
-  heatCapacity: 10000
 
 - type: tile
   id: FloorSteelMini
+  parent: BaseStationTile
   name: tiles-steel-floor-mini
   sprite: /Textures/Tiles/steel_mini.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemSteelMini
-  heatCapacity: 10000
 
 - type: tile
   id: FloorSteelPavement
+  parent: BaseStationTile
   name: tiles-steel-floor-pavement
   sprite: /Textures/Tiles/steel_pavement.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemSteelPavement
-  heatCapacity: 10000
 
 - type: tile
   id: FloorSteelDiagonal
+  parent: BaseStationTile
   name: tiles-steel-floor-diagonal
   sprite: /Textures/Tiles/steel_diagonal.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemSteelDiagonal
-  heatCapacity: 10000
 
 - type: tile
   id: FloorSteelOffset
+  parent: BaseStationTile
   name: tiles-steel-floor-offset
   sprite: /Textures/Tiles/steel_offset.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemSteelOffset
-  heatCapacity: 10000
 
 - type: tile
   id: FloorSteelMono
+  parent: BaseStationTile
   name: tiles-steel-floor-mono
   sprite: /Textures/Tiles/steel_mono.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepTile
   itemDrop: FloorTileItemSteelMono
-  heatCapacity: 10000
 
 - type: tile
   id: FloorSteelPavementVertical
+  parent: BaseStationTile
   name: tiles-steel-floor-pavement-vertical
   sprite: /Textures/Tiles/steel_pavement_vertical.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepTile
   itemDrop: FloorTileItemSteelPavementVertical
-  heatCapacity: 10000
 
 - type: tile
   id: FloorSteelHerringbone
+  parent: BaseStationTile
   name: tiles-steel-floor-herringbone
   sprite: /Textures/Tiles/steel_herringbone.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepTile
   itemDrop: FloorTileItemSteelHerringbone
-  heatCapacity: 10000
 
 - type: tile
   id: FloorSteelDiagonalMini
+  parent: BaseStationTile
   name: tiles-steel-floor-diagonal-mini
   sprite: /Textures/Tiles/steel_diagonal_mini.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepTile
   itemDrop: FloorTileItemSteelDiagonalMini
-  heatCapacity: 10000
 
 - type: tile
   id: FloorBrassFilled
+  parent: BaseStationTile
   name: tiles-brass-floor-filled
   sprite: /Textures/Tiles/Misc/clockwork/clockwork_floor_filled.png
-  baseTurf: PlatingBrass
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepHull
   itemDrop: FloorTileItemBrassFilled
-  heatCapacity: 10000
 
 - type: tile
   id: FloorBrassReebe
+  parent: BaseStationTile
   name: tiles-brass-floor-reebe
   sprite: /Textures/Tiles/Misc/clockwork/reebe.png
-  baseTurf: PlatingBrass
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepHull
   itemDrop: FloorTileItemBrassReebe
-  heatCapacity: 10000
 
 - type: tile
   id: FloorPlastic
+  parent: BaseStationTile
   name: tiles-plastic-floor
   sprite: /Textures/Tiles/plastic.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemSteel
-  heatCapacity: 10000
 
 - type: tile
   id: FloorWood
+  parent: BaseStationTile
   name: tiles-wood
   sprite: /Textures/Tiles/wood.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepWood
   barestepSounds:
     collection: BarestepWood
   itemDrop: FloorTileItemWood
-  heatCapacity: 10000
 
 - type: tile
   id: FloorWhite
+  parent: BaseStationTile
   name: tiles-white-floor
   sprite: /Textures/Tiles/white.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemWhite
-  heatCapacity: 10000
 
 - type: tile
   id: FloorWhiteMini
+  parent: BaseStationTile
   name: tiles-white-floor-mini
   sprite: /Textures/Tiles/white_mini.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemWhiteMini
-  heatCapacity: 10000
 
 - type: tile
   id: FloorWhitePavement
+  parent: BaseStationTile
   name: tiles-white-floor-pavement
   sprite: /Textures/Tiles/white_pavement.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemWhitePavement
-  heatCapacity: 10000
 
 - type: tile
   id: FloorWhiteDiagonal
+  parent: BaseStationTile
   name: tiles-white-floor-diagonal
   sprite: /Textures/Tiles/white_diagonal.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemWhiteDiagonal
-  heatCapacity: 10000
 
 - type: tile
   id: FloorWhiteOffset
+  parent: BaseStationTile
   name: tiles-white-floor-offset
   sprite: /Textures/Tiles/white_offset.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemWhiteOffset
-  heatCapacity: 10000
 
 - type: tile
   id: FloorWhiteMono
+  parent: BaseStationTile
   name: tiles-white-floor-mono
   sprite: /Textures/Tiles/white_mono.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemWhiteMono
-  heatCapacity: 10000
 
 - type: tile
   id: FloorWhitePavementVertical
+  parent: BaseStationTile
   name: tiles-white-floor-pavement-vertical
   sprite: /Textures/Tiles/white_pavement_vertical.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemWhitePavementVertical
-  heatCapacity: 10000
 
 - type: tile
   id: FloorWhiteHerringbone
+  parent: BaseStationTile
   name: tiles-white-floor-herringbone
   sprite: /Textures/Tiles/white_herringbone.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemWhiteHerringbone
-  heatCapacity: 10000
 
 - type: tile
   id: FloorWhiteDiagonalMini
+  parent: BaseStationTile
   name: tiles-white-floor-diagonal-mini
   sprite: /Textures/Tiles/white_diagonal_mini.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemWhiteDiagonalMini
-  heatCapacity: 10000
 
 - type: tile
   id: FloorWhitePlastic
+  parent: BaseStationTile
   name: tiles-plastic-white-floor
   sprite: /Textures/Tiles/white_plastic.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemWhite
-  heatCapacity: 10000
 
 - type: tile
   id: FloorDark
+  parent: BaseStationTile
   name: tiles-dark-floor
   sprite: /Textures/Tiles/dark.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemDark
-  heatCapacity: 10000
 
 - type: tile
   id: FloorDarkMini
+  parent: BaseStationTile
   name: tiles-dark-floor-mini
   sprite: /Textures/Tiles/dark_mini.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemDarkMini
-  heatCapacity: 10000
 
 - type: tile
   id: FloorDarkPavement
+  parent: BaseStationTile
   name: tiles-dark-floor-pavement
   sprite: /Textures/Tiles/dark_pavement.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemDarkPavement
-  heatCapacity: 10000
 
 - type: tile
   id: FloorDarkDiagonal
+  parent: BaseStationTile
   name: tiles-dark-floor-diagonal
   sprite: /Textures/Tiles/dark_diagonal.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemDarkDiagonal
-  heatCapacity: 10000
 
 - type: tile
   id: FloorDarkOffset
+  parent: BaseStationTile
   name: tiles-dark-floor-offset
   sprite: /Textures/Tiles/dark_offset.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemDarkOffset
-  heatCapacity: 10000
 
 - type: tile
   id: FloorDarkMono
+  parent: BaseStationTile
   name: tiles-dark-floor-mono
   sprite: /Textures/Tiles/dark_mono.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemDarkMono
-  heatCapacity: 10000
 
 - type: tile
   id: FloorDarkPavementVertical
+  parent: BaseStationTile
   name: tiles-dark-floor-pavement-vertical
   sprite: /Textures/Tiles/dark_pavement_vertical.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemDarkPavementVertical
-  heatCapacity: 10000
 
 - type: tile
   id: FloorDarkHerringbone
+  parent: BaseStationTile
   name: tiles-dark-floor-herringbone
   sprite: /Textures/Tiles/dark_herringbone.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemDarkHerringbone
-  heatCapacity: 10000
 
 - type: tile
   id: FloorDarkDiagonalMini
+  parent: BaseStationTile
   name: tiles-dark-floor-diagonal-mini
   sprite: /Textures/Tiles/dark_diagonal_mini.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemDarkDiagonalMini
-  heatCapacity: 10000
 
 - type: tile
   id: FloorDarkPlastic
+  parent: BaseStationTile
   name: tiles-plastic-dark-floor
   sprite: /Textures/Tiles/dark_plastic.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemDark
-  heatCapacity: 10000
 
 - type: tile
   id: FloorTechMaint
+  parent: BaseStationTile
   name: tiles-techmaint-floor
   sprite: /Textures/Tiles/tech_maint.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepHull
   itemDrop: FloorTileItemTechmaint
-  heatCapacity: 10000
 
 - type: tile
   id: FloorTechMaintDark
+  parent: BaseStationTile
   name: tiles-techmaint-floor-dark
   sprite: /Textures/Tiles/tech_maint_dark.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepHull
   itemDrop: FloorTileItemTechmaintDark
-  heatCapacity: 10000
 
 - type: tile
   id: FloorReinforced
+  parent: BaseStationTile
   name: tiles-reinforced-floor
   sprite: /Textures/Tiles/reinforced.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepHull
   itemDrop: PartRodMetal1
-  heatCapacity: 10000
 
 - type: tile
   id: FloorMono
+  parent: BaseStationTile
   name: tiles-mono-floor
   sprite: /Textures/Tiles/mono.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemMono
-  heatCapacity: 10000
 
 - type: tile
   id: FloorLino
+  parent: BaseStationTile
   name: tiles-linoleum-floor
   sprite: /Textures/Tiles/lino.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemLino
-  heatCapacity: 10000
 
 - type: tile
   id: FloorSteelDirty
+  parent: BaseStationTile
   name: tiles-dirty-steel-floor
   sprite: /Textures/Tiles/steel_dirty.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepPlating
   itemDrop: FloorTileItemDirty
-  heatCapacity: 10000
 
 - type: tile
   id: FloorElevatorShaft
+  parent: BaseStationTile
   name: tiles-elevator-shaft
   sprite: /Textures/Tiles/elevator_shaft.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepHull
   itemDrop: FloorTileItemElevatorShaft
-  heatCapacity: 10000
 
 - type: tile
   id: FloorMetalDiamond
+  parent: BaseStationTile
   name: tiles-diamond-plate-floor
   sprite: /Textures/Tiles/metaldiamond.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepHull
   itemDrop: FloorTileItemMetalDiamond
-  heatCapacity: 10000
 
 - type: tile
   id: FloorRockVault
+  parent: BaseStationTile
   name: tiles-rock-floor
   sprite: /Textures/Tiles/rock_vault.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepAsteroid
   itemDrop: FloorTileItemRockVault
-  heatCapacity: 10000
 
 - type: tile
   id: FloorBlue
+  parent: BaseStationTile
   name: tiles-blue-tile
   sprite: /Textures/Tiles/blue.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemBlue
-  heatCapacity: 10000
 
 - type: tile
   id: FloorSteelLime
+  parent: BaseStationTile
   name: tiles-lime-floor
   sprite: /Textures/Tiles/lime.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemLime
-  heatCapacity: 10000
 
 - type: tile
   id: FloorMining
+  parent: BaseStationTile
   name: tiles-mining-tile
   sprite: /Textures/Tiles/mining_floor.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemMining
-  heatCapacity: 10000
 
 - type: tile
   id: FloorMiningDark
+  parent: BaseStationTile
   name: tiles-mining-dark-tile
   sprite: /Textures/Tiles/mining_floor_dark.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemMiningDark
-  heatCapacity: 10000
 
 - type: tile
   id: FloorMiningLight
+  parent: BaseStationTile
   name: tiles-mining-light-tile
   sprite: /Textures/Tiles/mining_floor_light.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemMiningLight
-  heatCapacity: 10000
 
 # Departmental
 - type: tile
   id: FloorFreezer
+  parent: BaseStationTile
   name: tiles-freezer
   sprite: /Textures/Tiles/freezer.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepHull
   itemDrop: FloorTileItemFreezer
-  heatCapacity: 10000
 
 - type: tile
   id: FloorShowroom
+  parent: BaseStationTile
   name: tiles-showroom-floor
   sprite: /Textures/Tiles/showroom.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemShowroom
-  heatCapacity: 10000
 
 - type: tile
   id: FloorHydro
+  parent: BaseStationTile
   name: tiles-hydro-floor
   sprite: /Textures/Tiles/hydro.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemHydro
-  heatCapacity: 10000
 
 - type: tile
   id: FloorBar
+  parent: BaseStationTile
   name: tiles-bar-floor
   sprite: /Textures/Tiles/bar.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemBar
-  heatCapacity: 10000
 
 - type: tile
   id: FloorClown
+  parent: BaseStationTile
   name: tiles-clown-floor
   sprite: /Textures/Tiles/clown.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemClown
-  heatCapacity: 10000
 
 - type: tile
   id: FloorMime
+  parent: BaseStationTile
   name: tiles-mime-floor
   sprite: /Textures/Tiles/mime.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemMime
-  heatCapacity: 10000
 
 - type: tile
   id: FloorKitchen
+  parent: BaseStationTile
   name: tiles-kitchen-floor
-  sprite: /Textures/Tiles/kitchen.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
+  sprite: /Textures/Tiles/kitchen.png
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemKitchen
-  heatCapacity: 10000
 
 - type: tile
   id: FloorLaundry
+  parent: BaseStationTile
   name: tiles-laundry-floor
   sprite: /Textures/Tiles/laundry.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemLaundry
-  heatCapacity: 10000
 
 - type: tile
   id: FloorSteelDamaged
+  parent: BaseStationTile
   name: tiles-steel-floor
   sprite: /Textures/Tiles/steel_damaged.png
   variants: 5
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemSteel #This should probably be made null when it becomes possible to make it such, in SS13 prying destroyed tiles wouldn't give you anything.
-  heatCapacity: 10000
 
 # Concrete
 - type: tile
   id: FloorConcrete
+  parent: BaseStationTile
   name: tiles-concrete-tile
   sprite: /Textures/Tiles/Planet/Concrete/concrete.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemConcrete
-  heatCapacity: 10000
 
 - type: tile
   id: FloorConcreteMono
+  parent: BaseStationTile
   name: tiles-concrete-slab
   sprite: /Textures/Tiles/Planet/Concrete/concrete_mono.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemConcreteMono
-  heatCapacity: 10000
 
 - type: tile
   id: FloorConcreteSmooth
+  parent: BaseStationTile
   name: tiles-concrete-smooth
   sprite: /Textures/Tiles/Planet/Concrete/concrete_smooth.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemConcreteSmooth
-  heatCapacity: 10000
 
 - type: tile
   id: FloorGrayConcrete
+  parent: BaseStationTile
   name: tiles-gray-concrete-tile
   sprite: /Textures/Tiles/Planet/Concrete/grayconcrete.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemGrayConcrete
-  heatCapacity: 10000
 
 - type: tile
   id: FloorGrayConcreteMono
+  parent: BaseStationTile
   name: tiles-gray-concrete-slab
   sprite: /Textures/Tiles/Planet/Concrete/grayconcrete_mono.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemGrayConcreteMono
-  heatCapacity: 10000
 
 - type: tile
   id: FloorGrayConcreteSmooth
+  parent: BaseStationTile
   name: tiles-gray-concrete-smooth
   sprite: /Textures/Tiles/Planet/Concrete/grayconcrete_smooth.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemGrayConcreteSmooth
-  heatCapacity: 10000
 
 - type: tile
   id: FloorOldConcrete
+  parent: BaseStationTile
   name: tiles-old-concrete-tile
   sprite: /Textures/Tiles/Planet/Concrete/oldconcrete.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemOldConcrete
-  heatCapacity: 10000
 
 - type: tile
   id: FloorOldConcreteMono
+  parent: BaseStationTile
   name: tiles-old-concrete-slab
   sprite: /Textures/Tiles/Planet/Concrete/oldconcrete_mono.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemOldConcreteMono
-  heatCapacity: 10000
 
 - type: tile
   id: FloorOldConcreteSmooth
+  parent: BaseStationTile
   name: tiles-old-concrete-smooth
   sprite: /Textures/Tiles/Planet/Concrete/oldconcrete_smooth.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemOldConcreteSmooth
-  heatCapacity: 10000
 
 # Carpets (non smoothing)
 - type: tile
   id: FloorArcadeBlue
+  parent: BaseStationTile
   name: tiles-blue-arcade-floor
   sprite: /Textures/Tiles/arcadeblue.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepCarpet
   barestepSounds:
     collection: BarestepCarpet
   friction: 1.25
   itemDrop: FloorTileItemArcadeBlue
-  heatCapacity: 10000
 
 - type: tile
   id: FloorArcadeBlue2
+  parent: BaseStationTile
   name: tiles-blue-arcade-floor
   sprite: /Textures/Tiles/arcadeblue2.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepCarpet
   barestepSounds:
     collection: BarestepCarpet
   friction: 1.25
   itemDrop: FloorTileItemArcadeBlue2
-  heatCapacity: 10000
 
 - type: tile
   id: FloorArcadeRed
+  parent: BaseStationTile
   name: tiles-red-arcade-floor
   sprite: /Textures/Tiles/arcadered.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepCarpet
   barestepSounds:
     collection: BarestepCarpet
   friction: 1.25
   itemDrop: FloorTileItemArcadeRed
-  heatCapacity: 10000
 
 - type: tile
   id: FloorEighties
+  parent: BaseStationTile
   name: tiles-eighties-floor
   sprite: /Textures/Tiles/eighties.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepCarpet
   barestepSounds:
     collection: BarestepCarpet
   friction: 1.25
   itemDrop: FloorTileItemEighties
-  heatCapacity: 10000
 
 - type: tile
   id: FloorCarpetClown
+  parent: BaseStationTile
   name: tiles-clown-carpet-floor
   sprite: /Textures/Tiles/carpetclown.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepCarpet
   barestepSounds:
     collection: BarestepCarpet
   friction: 1.25
   itemDrop: FloorTileItemCarpetClown
-  heatCapacity: 10000
 
 - type: tile
   id: FloorCarpetOffice
+  parent: BaseStationTile
   name: tiles-office-carpet-floor
   sprite: /Textures/Tiles/carpetoffice.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepCarpet
   barestepSounds:
     collection: BarestepCarpet
   friction: 1.25
   itemDrop: FloorTileItemCarpetOffice
-  heatCapacity: 10000
 
 - type: tile
   id: FloorBoxing
+  parent: BaseStationTile
   name: tiles-boxing-ring-floor
   sprite: /Textures/Tiles/boxing.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   friction: 1.25
   itemDrop: FloorTileItemBoxing
-  heatCapacity: 10000
 
 - type: tile
   id: FloorGym
+  parent: BaseStationTile
   name: tiles-gym-floor
   sprite: /Textures/Tiles/gym.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   friction: 1.25
   itemDrop: FloorTileItemGym
-  heatCapacity: 10000
 
 # Shuttle
 - type: tile
   id: FloorShuttleWhite
+  parent: BaseStationTile
   name: tiles-white-shuttle-floor
   sprite: /Textures/Tiles/shuttlewhite.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemShuttleWhite
-  heatCapacity: 10000
 
 - type: tile
   id: FloorShuttleGrey
+  parent: BaseStationTile
   name: tiles-grey-shuttle-floor
   sprite: /Textures/Tiles/shuttlegrey.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemShuttleGrey
-  heatCapacity: 10000
 
 - type: tile
   id: FloorShuttleBlack
+  parent: BaseStationTile
   name: tiles-black-shuttle-floor
   sprite: /Textures/Tiles/shuttleblack.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemShuttleBlack
-  heatCapacity: 10000
 
 - type: tile
   id: FloorShuttleBlue
+  parent: BaseStationTile
   name: tiles-blue-shuttle-floor
   sprite: /Textures/Tiles/shuttleblue.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemShuttleBlue
-  heatCapacity: 10000
 
 - type: tile
   id: FloorShuttleOrange
+  parent: BaseStationTile
   name: tiles-orange-shuttle-floor
   sprite: /Textures/Tiles/shuttleorange.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemShuttleOrange
-  heatCapacity: 10000
 
 - type: tile
   id: FloorShuttlePurple
+  parent: BaseStationTile
   name: tiles-purple-shuttle-floor
   sprite: /Textures/Tiles/shuttlepurple.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemShuttlePurple
-  heatCapacity: 10000
 
 - type: tile
   id: FloorShuttleRed
+  parent: BaseStationTile
   name: tiles-red-shuttle-floor
   sprite: /Textures/Tiles/shuttlered.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemShuttleRed
-  heatCapacity: 10000
 
 
 # Materials
 - type: tile
   id: FloorGold
+  parent: BaseStationTile
   name: tiles-gold-tile
   sprite: /Textures/Tiles/gold.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemGold
-  heatCapacity: 10000
 
 - type: tile
   id: FloorSilver
+  parent: BaseStationTile
   name: tiles-silver-tile
   sprite: /Textures/Tiles/silver.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemSilver
-  heatCapacity: 10000
 
 - type: tile
   id: FloorGlass
+  parent: BaseStationTile
   name: tiles-glass-floor
   sprite: /Textures/Tiles/glass.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: SheetGlass1
-  heatCapacity: 10000
 
 - type: tile
   id: FloorRGlass
+  parent: BaseStationTile
   name: tiles-reinforced-glass-floor
   sprite: /Textures/Tiles/rglass.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: SheetRGlass1
-  heatCapacity: 10000
 
 - type: tile
   id: FloorMetalFoam
+  parent: BaseStationTile
   name: tiles-metal-foam
   sprite: /Textures/Tiles/foammetal.png
   variants: 1
   placementVariants:
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepHull
   itemDrop: SheetSteel1
-  heatCapacity: 10000
 
 # Circuits
 - type: tile
   id: FloorGreenCircuit
+  parent: BaseStationTile
   name: tiles-green-circuit-floor
   sprite: /Textures/Tiles/green_circuit.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepHull
   itemDrop: FloorTileItemGCircuit
-  heatCapacity: 10000
 
 - type: tile
   id: FloorBlueCircuit
+  parent: BaseStationTile
   name: tiles-blue-circuit-floor
   sprite: /Textures/Tiles/blue_circuit.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepHull
   itemDrop: FloorTileItemBCircuit
-  heatCapacity: 10000
 
 - type: tile
   id: FloorRedCircuit
+  parent: BaseStationTile
   name: tiles-red-circuit-floor
   sprite: /Textures/Tiles/red_circuit.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepHull
   itemDrop: FloorTileItemRCircuit
-  heatCapacity: 10000
 
 # Terrain
 - type: tile
   sprite: /Textures/Tiles/Asteroid/asteroid.png
   variants: 13
   placementVariants:
-  - 0.8
-  - 0.0166 #Should be roughly 20%.... I think??? I don't know dude, I'm just a YAML monkey.
-  - 0.0166
-  - 0.0166
-  - 0.0166
-  - 0.0166
-  - 0.0166
-  - 0.0166
-  - 0.0166
-  - 0.0166
-  - 0.0166
-  - 0.0116
-  - 0.0116
+    - 0.8
+    - 0.0166 #Should be roughly 20%.... I think??? I don't know dude, I'm just a YAML monkey.
+    - 0.0166
+    - 0.0166
+    - 0.0166
+    - 0.0166
+    - 0.0166
+    - 0.0166
+    - 0.0166
+    - 0.0166
+    - 0.0166
+    - 0.0116
+    - 0.0116
   baseTurf: Space
   isSubfloor: true
   footstepSounds:
 
 - type: tile
   id: FloorFlesh
+  parent: BaseStationTile
   name: tiles-flesh-floor
   sprite: /Textures/Tiles/meat.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepBlood
   itemDrop: FloorTileItemFlesh
   friction: 0.25 #slippy
-  heatCapacity: 10000
 
 - type: tile
   id: FloorTechMaint2
+  parent: BaseStationTile
   name: tiles-techmaint2-floor
   sprite: /Textures/Tiles/steel_maint.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepHull
   itemDrop: FloorTileItemSteelMaint
-  heatCapacity: 10000
 
 - type: tile
   id: FloorTechMaint3
+  parent: BaseStationTile
   name: tiles-techmaint3-floor
   sprite: /Textures/Tiles/grating_maint.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepHull
   itemDrop: FloorTileItemGratingMaint
-  heatCapacity: 10000
 
 - type: tile
   id: FloorWoodTile
+  parent: BaseStationTile
   name: tiles-wood2
   sprite: /Textures/Tiles/wood_tile.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepWood
   barestepSounds:
     collection: BarestepWood
   itemDrop: FloorTileItemWoodPattern
-  heatCapacity: 10000
 
 - type: tile
   id: FloorBrokenWood
+  parent: BaseStationTile
   name: tiles-wood3
   sprite: /Textures/Tiles/wood_broken.png
   variants: 7
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepWood
   barestepSounds:
     collection: BarestepWood
   itemDrop: MaterialWoodPlank1
-  heatCapacity: 10000
 
 - type: tile
   id: FloorWebTile
+  parent: BaseStationTile
   name: tiles-web
   sprite: /Textures/Tiles/Misc/Web/web_tile.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepCarpet
   barestepSounds:
     collection: BarestepCarpet
   itemDrop: FloorTileItemWeb
-  heatCapacity: 10000
 
 - type: tile
   id: FloorChromite
 #Hull tiles
 - type: tile
   id: FloorHull
+  parent: BaseStationTile
   name: tiles-hull
   sprite: /Textures/Tiles/hull.png
-  baseTurf: Plating
-  isSubfloor: false
   footstepSounds:
     collection: FootstepHull
   itemDrop: FloorTileItemSteel #probably should not be normally obtainable, but the game shits itself and dies when you try to put null here
-  heatCapacity: 10000
 
 - type: tile
   id: FloorHullReinforced
+  parent: BaseStationTile
   name: tiles-hull-reinforced
   sprite: /Textures/Tiles/hull_reinforced.png
-  baseTurf: Plating
-  isSubfloor: false
   footstepSounds:
     collection: FootstepHull
   itemDrop: FloorTileItemSteel
 
 - type: tile
   id: FloorReinforcedHardened
+  parent: BaseStationTile
   name: tiles-super-reinforced-floor
   sprite: /Textures/Tiles/super_reinforced.png
-  baseTurf: Plating
-  isSubfloor: false
   footstepSounds:
     collection: FootstepHull
   itemDrop: PartRodMetal1 #same case as FloorHull
 # Grass
 - type: tile
   id: FloorAstroGrass
+  parent: BaseStationTile
   name: tiles-astro-grass
   sprite: /Textures/Tiles/Planet/Grass/grass.png
   variants: 4
     East: /Textures/Tiles/Planet/Grass/double_edge.png
     North: /Textures/Tiles/Planet/Grass/double_edge.png
     West: /Textures/Tiles/Planet/Grass/double_edge.png
-  baseTurf: Plating
-  isSubfloor: false
   deconstructTools: [ Cutting ]
   footstepSounds:
     collection: FootstepGrass
   itemDrop: FloorTileItemAstroGrass
-  heatCapacity: 10000
 
 - type: tile
   id: FloorMowedAstroGrass
+  parent: [ BaseStationTile, FloorGrass ]
   name: tiles-mowed-astro-grass
-  parent: FloorGrass
-  baseTurf: Plating
   isSubfloor: false
   deconstructTools: [ Cutting ]
   itemDrop: FloorTileItemMowedAstroGrass
 
 - type: tile
   id: FloorJungleAstroGrass
+  parent: [ BaseStationTile, FloorGrassJungle ]
   name: tiles-jungle-astro-grass
-  parent: FloorGrassJungle
-  baseTurf: Plating
   isSubfloor: false
   deconstructTools: [ Cutting ]
   itemDrop: FloorTileItemJungleAstroGrass
 # Ice
 - type: tile
   id: FloorAstroIce
+  parent: BaseStationTile
   name: tiles-astro-ice
   sprite: /Textures/Tiles/Planet/Snow/ice.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   friction: 0.05
-  heatCapacity: 10000
   mobFriction: 0.05
   mobAcceleration: 0.1
   itemDrop: FloorTileItemAstroIce
 
 - type: tile
   id: FloorAstroSnow
+  parent: [ BaseStationTile, FloorSnow ]
   name: tiles-astro-snow
-  parent: FloorSnow
-  baseTurf: Plating
   isSubfloor: false
   deconstructTools: [ Prying ]
   itemDrop: FloorTileItemAstroSnow
 # Asteroid Sand
 - type: tile
   id: FloorAstroAsteroidSand
+  parent: [ BaseStationTile, FloorAsteroidSand ]
   name: tiles-astro-asteroid-sand
-  parent: FloorAsteroidSand
-  baseTurf: Plating
   isSubfloor: false
   deconstructTools: [ Prying ]
   itemDrop: FloorTileItemAstroAsteroidSand
 
 - type: tile
   id: FloorAstroAsteroidSandBorderless
+  parent: [ BaseStationTile, FloorAsteroidSandBorderless ]
   name: tiles-astro-asteroid-sand-borderless
-  parent: FloorAsteroidSandBorderless
-  baseTurf: Plating
   isSubfloor: false
   deconstructTools: [ Prying ]
   itemDrop: FloorTileItemAstroAsteroidSand
 
 - type: tile
   id: FloorWoodLarge
+  parent: BaseStationTile
   name: tiles-wood-large
   sprite: /Textures/Tiles/wood_large.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepWood
   barestepSounds:
     collection: BarestepWood
   itemDrop: FloorTileItemWoodLarge
-  heatCapacity: 10000
 
 - type: tile
   id: FloorXenoborg
 
 - type: tile
   id: FloorXeno
+  parent: BaseStationTile
   name: tiles-xeno-floor
   sprite: /Textures/Tiles/xeno_flooring.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepBlood
   itemDrop: FloorTileItemXeno
-  heatCapacity: 10000
 
 - type: tile
   id: FloorXenoSteel
+  parent: BaseStationTile
   name: tiles-xeno-steel
   sprite: /Textures/Tiles/xeno_steel.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemXenoSteel
-  heatCapacity: 10000
   allowRotationMirror: true
 
 - type: tile
   id: FloorXenoSteelCorner
+  parent: BaseStationTile
   name: tiles-xeno-steel-corner
   sprite: /Textures/Tiles/xeno_steel_corner.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   itemDrop: FloorTileItemXenoSteelCorner
-  heatCapacity: 10000
   allowRotationMirror: true
 
 - type: tile
   id: FloorDarkSquiggly
+  parent: BaseStationTile
   name: tiles-dark-squiggly
   sprite: /Textures/Tiles/dark_squiggly.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
-  footstepSounds:
-    collection: FootstepFloor
   itemDrop: FloorTileItemDarkSquiggly
-  heatCapacity: 10000
   allowRotationMirror: true
 
 - type: tile
   id: FloorXenoMaint
+  parent: BaseStationTile
   name: tiles-xeno-maint
   sprite: /Textures/Tiles/xeno_maint.png
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepHull
   itemDrop: FloorTileItemXenoMaint
-  heatCapacity: 10000
 
 - type: tile
   id: FloorWhiteMarble
+  parent: BaseStationTile
   name: tiles-white-marble
   sprite: /Textures/Tiles/white_marble.png
   variants: 8
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   friction: 0.8
   itemDrop: FloorTileItemWhiteMarble
-  heatCapacity: 10000
 
 - type: tile
   id: FloorDarkMarble
+  parent: BaseStationTile
   name: tiles-dark-marble
   sprite: /Textures/Tiles/dark_marble.png
   variants: 8
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   friction: 0.8
   itemDrop: FloorTileItemDarkMarble
-  heatCapacity: 10000
 
 - type: tile
   id: FloorPlasmaMarble
+  parent: BaseStationTile
   name: tiles-plasma-marble
   sprite: /Textures/Tiles/plasmarble.png
   variants: 8
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   friction: 0.8
   itemDrop: FloorTileItemPlasmaMarble
-  heatCapacity: 10000
 
 - type: tile
   id: FloorUraniumMarble
+  parent: BaseStationTile
   name: tiles-uranium-marble
   sprite: /Textures/Tiles/uranium_marble.png
   variants: 8
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Plating
-  isSubfloor: false
-  deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepTile
   friction: 0.8
   itemDrop: FloorTileItemUraniumMarble
-  heatCapacity: 10000
index f0ef0f13463e11f6e4ace1785b981736064c4191..15134868b5f7d2766d1a203acca9b1c655e63bb6 100644 (file)
@@ -1,5 +1,16 @@
+- type: tile
+  id: BaseFloorPlanet
+  abstract: true
+  heatCapacity: 10000
+  isSubfloor: true
+  footstepSounds:
+    collection: FootstepAsteroid
+  weather: true
+  indestructible: true
+
 - type: tile
   id: FloorPlanetDirt
+  parent: BaseFloorPlanet
   name: tiles-dirt-planet-floor
   sprite: /Textures/Tiles/Planet/dirt.rsi/dirt.png
   variants: 4
   - 1.0
   - 1.0
   - 1.0
-  isSubfloor: true
-  footstepSounds:
-    collection: FootstepAsteroid
-  heatCapacity: 10000
-  weather: true
-  indestructible: true
 
 # Desert
 - type: tile
   id: FloorDesert
+  parent: BaseFloorPlanet
   name: tiles-desert-floor
   sprite: /Textures/Tiles/Planet/Desert/desert.png
   variants: 6
   - 1.0
   - 1.0
   - 1.0
-  isSubfloor: true
-  footstepSounds:
-    collection: FootstepAsteroid
-  heatCapacity: 10000
-  weather: true
-  indestructible: true
 
 - type: tile
   id: FloorLowDesert
   - 1.0
   - 1.0
   - 1.0
-  isSubfloor: true
-  footstepSounds:
-    collection: FootstepAsteroid
-  heatCapacity: 10000
-  weather: true
-  indestructible: true
 
 # Grass
 - type: tile
   id: FloorPlanetGrass
+  parent: BaseFloorPlanet
   name: tiles-grass-planet-floor
   sprite: /Textures/Tiles/Planet/Grass/grass.png
   variants: 4
     North: /Textures/Tiles/Planet/Grass/double_edge.png
     West: /Textures/Tiles/Planet/Grass/double_edge.png
   baseTurf: FloorPlanetDirt
-  isSubfloor: true
   footstepSounds:
     collection: FootstepGrass
-  itemDrop: FloorTileItemGrass
-  heatCapacity: 10000
-  weather: true
-  indestructible: true
 
 # Lava
 - type: tile
   id: FloorBasalt
   name: tiles-basalt-floor
+  parent: BaseFloorPlanet
   sprite: /Textures/Tiles/Planet/basalt.png
-  isSubfloor: true
-  footstepSounds:
-    collection: FootstepAsteroid
-  heatCapacity: 10000
-  weather: true
-  indestructible: true
 
 # Snow
 - type: tile
   id: FloorSnow
+  parent: BaseFloorPlanet
   name: tiles-snow
   sprite: /Textures/Tiles/Planet/Snow/snow.png
   variants: 13
     East: /Textures/Tiles/Planet/Snow/snow_double_edge_east.png
     North: /Textures/Tiles/Planet/Snow/snow_double_edge_north.png
     West: /Textures/Tiles/Planet/Snow/snow_double_edge_west.png
-  isSubfloor: true
   footstepSounds:
     collection: FootstepSnow
-  heatCapacity: 10000
-  weather: true
-  indestructible: true
 
 # Ice
 - type: tile
 # Dug snow
 - type: tile
   id: FloorSnowDug
+  parent: BaseFloorPlanet
   name: tiles-snow-dug
   sprite: /Textures/Tiles/Planet/Snow/snow_dug.png
   edgeSpritePriority: 1
     East: /Textures/Tiles/Planet/Snow/snow_dug_double_edge_east.png
     North: /Textures/Tiles/Planet/Snow/snow_dug_double_edge_north.png
     West: /Textures/Tiles/Planet/Snow/snow_dug_double_edge_west.png
-  isSubfloor: true
   footstepSounds:
     collection: FootstepSnow
-  heatCapacity: 10000
-  weather: true
-  indestructible: true
 
 # Wasteland
index 2a85222c3ab91cacd4780593c3db5fe1f597981f..910f941bee2cf8676ce22de971bab6ec8803f89a 100644 (file)
@@ -1,16 +1,24 @@
 - type: tile
-  id: Plating
-  name: tiles-plating
-  sprite: /Textures/Tiles/plating.png
-  baseTurf: Lattice
+  id: BasePlating
+  abstract: true
+  friction: 1.5
+  heatCapacity: 10000
   isSubfloor: true
   footstepSounds:
     collection: FootstepPlating
-  friction: 1.5
-  heatCapacity: 10000
+  baseTurf: Lattice
+  baseWhitelist:
+    - TrainLattice
+
+- type: tile
+  id: Plating
+  parent: BasePlating
+  name: tiles-plating
+  sprite: /Textures/Tiles/plating.png
 
 - type: tile
   id: PlatingDamaged
+  parent: BasePlating
   name: tiles-plating
   sprite: /Textures/Tiles/plating_damaged.png
   variants: 3
   - 1.0
   - 1.0
   - 1.0
-  baseTurf: Lattice
-  isSubfloor: true
-  footstepSounds:
-    collection: FootstepPlating
-  friction: 1.5
-  heatCapacity: 10000
 
 - type: tile
   id: PlatingAsteroid
+  parent: BasePlating
   name: tiles-asteroid-plating
   sprite: /Textures/Tiles/Asteroid/asteroid_plating.png
-  baseTurf: Lattice
-  isSubfloor: true
-  footstepSounds:
-    collection: FootstepPlating
-  friction: 1.5
-  heatCapacity: 10000
 
 - type: tile
   id: PlatingBrass
+  parent: BasePlating
   name: tiles-brass-plating
   sprite: /Textures/Tiles/Misc/clockwork/clockwork_floor.png
-  baseTurf: Lattice
-  isSubfloor: true
-  footstepSounds:
-    collection: FootstepPlating
-  friction: 1.5
-  heatCapacity: 10000
 
 - type: tile
   id: PlatingSnow
   name: tiles-snow-plating
+  parent: BasePlating
   sprite: /Textures/Tiles/snow_plating.png #Not in the snow planet RSI because it doesn't have any metadata. Should probably be moved to its own folder later.
-  baseTurf: Lattice
-  isSubfloor: true
-  footstepSounds:
-    collection: FootstepPlating
   friction: 0.75 #a little less then actual snow
-  heatCapacity: 10000
 
 - type: tile
   id: PlatingIronsand
 
 - type: tile
   id: TrainLattice
+  parent: Lattice
   name: tiles-lattice-train
   sprite: /Textures/Tiles/latticeTrain.png
-  baseTurf: Space
-  isSubfloor: true
-  deconstructTools: [ Cutting ]
-  weather: true
   footstepSounds:
     collection: FootstepPlating
-  friction: 1.5
-  isSpace: true
-  itemDrop: PartRodMetal1
-  heatCapacity: 10000
-  mass: 200