Revert "Allow station tiles to be placed on solid ground and other platings. (#38898)"

This reverts commit 418b2b70b0.

Maintainer vote decided to vote this out due to code quality issues. The original contributor is aware and will get help from a maintainer to reintroduce it
This commit is contained in:
Vasilis The Pikachu
2026-01-18 21:49:19 +01:00
parent e247ea5850
commit ca07d6be49
15 changed files with 624 additions and 727 deletions

View 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(Plating, FromServer(pEast));
await AssertTile(Lattice, FromServer(pEast));
// Check that the cost of the deconstruction was subtracted from the current charges.
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));

View File

@@ -12,7 +12,6 @@ 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";

View File

@@ -100,25 +100,4 @@ 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));
}
}

View File

@@ -1,66 +0,0 @@
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);
}
}
}
}

View File

@@ -517,39 +517,17 @@ 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;
ContentTileDefinition? newDef = null;
// does this have a base-turf that we can break it down to?
if (string.IsNullOrEmpty(tileDef.BaseTurf))
break;
// 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 (newDef == null)
if (_tileDefinitionManager[tileDef.BaseTurf] is not ContentTileDefinition newDef)
break;
if (newDef.MapAtmosphere && !canCreateVacuum)

View File

@@ -1,74 +0,0 @@
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);
}
}

View File

@@ -409,13 +409,4 @@ 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);
}

View File

@@ -8,7 +8,6 @@ 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
@@ -42,13 +41,7 @@ namespace Content.Shared.Maps
[DataField("isSubfloor")] public bool IsSubFloor { get; private set; }
[DataField("baseTurf")]
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();
public string BaseTurf { get; private set; } = string.Empty;
[DataField]
public PrototypeFlags<ToolQualityPrototype> DeconstructTools { get; set; } = new();

View File

@@ -1,125 +0,0 @@
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;
}
}

View File

@@ -1,16 +1,10 @@
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;
@@ -20,85 +14,12 @@ 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.
@@ -164,7 +85,7 @@ public sealed class TileSystem : EntitySystem
return PryTile(tileRef);
}
public bool PryTile(TileRef tileRef)
public bool PryTile(TileRef tileRef)
{
return PryTile(tileRef, false);
}
@@ -176,7 +97,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;
@@ -191,73 +112,33 @@ public sealed class TileSystem : EntitySystem
return ReplaceTile(tileref, replacementTile, tileref.GridUid, grid);
}
public bool ReplaceTile(TileRef tileref, ContentTileDefinition replacementTile, EntityUid grid, MapGridComponent? component = null, byte? variant = null)
public bool ReplaceTile(TileRef tileref, ContentTileDefinition replacementTile, EntityUid grid, MapGridComponent? component = 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);
}
}
variant ??= PickVariant(replacementTile);
var 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.Value));
_maps.SetTile(grid, component, tileref.GridIndices, new Tile(replacementTile.TileId, 0, variant));
return true;
}
public bool DeconstructTile(TileRef tileRef, bool spawnItem = true)
public bool DeconstructTile(TileRef tileRef)
{
if (tileRef.Tile.IsEmpty)
return false;
var tileDef = (ContentTileDefinition)_tileDefinitionManager[tileRef.Tile.TypeId];
var tileDef = (ContentTileDefinition) _tileDefinitionManager[tileRef.Tile.TypeId];
//Can't deconstruct anything that doesn't have a base turf.
if (tileDef.BaseTurf == null)
if (string.IsNullOrEmpty(tileDef.BaseTurf))
return false;
var gridUid = tileRef.GridUid;
@@ -271,68 +152,20 @@ public sealed class TileSystem : EntitySystem
(_robustRandom.NextFloat() - 0.5f) * bounds,
(_robustRandom.NextFloat() - 0.5f) * bounds));
var historyComp = EnsureComp<TileHistoryComponent>(gridUid);
ProtoId<ContentTileDefinition> previousTileId;
//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 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
// 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);
}
//Replace tile with the one it was placed on
var previousDef = (ContentTileDefinition)_tileDefinitionManager[previousTileId];
_maps.SetTile(gridUid, mapGrid, indices, new Tile(previousDef.TileId));
var plating = _tileDefinitionManager[tileDef.BaseTurf];
_maps.SetTile(gridUid, mapGrid, tileRef.GridIndices, new Tile(plating.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
}
}

View File

@@ -38,7 +38,6 @@ 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!;
@@ -561,9 +560,10 @@ public sealed class RCDSystem : EntitySystem
if (target == null)
{
// 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");
// 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");
}
else
{

View File

@@ -16,7 +16,6 @@ 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;
@@ -143,7 +142,7 @@ public sealed class FloorTileSystem : EntitySystem
var baseTurf = (ContentTileDefinition) _tileDefinitionManager[tile.Tile.TypeId];
if (CanPlaceOn(currentTileDefinition, baseTurf.ID))
if (HasBaseTurf(currentTileDefinition, baseTurf.ID))
{
if (!_stackSystem.TryUse((uid, stack), 1))
continue;
@@ -153,7 +152,7 @@ public sealed class FloorTileSystem : EntitySystem
return;
}
}
else if (HasBaseTurf(currentTileDefinition, new ProtoId<ContentTileDefinition>(ContentTileDefinition.SpaceID)))
else if (HasBaseTurf(currentTileDefinition, ContentTileDefinition.SpaceID))
{
if (!_stackSystem.TryUse((uid, stack), 1))
continue;
@@ -172,35 +171,19 @@ public sealed class FloorTileSystem : EntitySystem
}
}
public bool HasBaseTurf(ContentTileDefinition tileDef, ProtoId<ContentTileDefinition> baseTurf)
public bool HasBaseTurf(ContentTileDefinition tileDef, string 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 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);
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));
_audio.PlayPredicted(placeSound, location, user);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,5 @@
- 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
@@ -19,11 +8,16 @@
- 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
@@ -34,6 +28,12 @@
- 1.0
- 1.0
- 1.0
isSubfloor: true
footstepSounds:
collection: FootstepAsteroid
heatCapacity: 10000
weather: true
indestructible: true
- type: tile
id: FloorLowDesert
@@ -47,11 +47,16 @@
- 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
@@ -71,20 +76,29 @@
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
@@ -108,8 +122,12 @@
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
@@ -126,7 +144,6 @@
# Dug snow
- type: tile
id: FloorSnowDug
parent: BaseFloorPlanet
name: tiles-snow-dug
sprite: /Textures/Tiles/Planet/Snow/snow_dug.png
edgeSpritePriority: 1
@@ -135,7 +152,11 @@
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

View File

@@ -1,24 +1,16 @@
- type: tile
id: BasePlating
abstract: true
friction: 1.5
heatCapacity: 10000
id: Plating
name: tiles-plating
sprite: /Textures/Tiles/plating.png
baseTurf: Lattice
isSubfloor: true
footstepSounds:
collection: FootstepPlating
baseTurf: Lattice
baseWhitelist:
- TrainLattice
- type: tile
id: Plating
parent: BasePlating
name: tiles-plating
sprite: /Textures/Tiles/plating.png
friction: 1.5
heatCapacity: 10000
- type: tile
id: PlatingDamaged
parent: BasePlating
name: tiles-plating
sprite: /Textures/Tiles/plating_damaged.png
variants: 3
@@ -26,25 +18,45 @@
- 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
@@ -75,8 +87,16 @@
- 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