mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-02-14 21:55:13 +01:00
merge remote wizden/stable
This commit is contained in:
@@ -38,9 +38,9 @@ public sealed class RCDTest : InteractionTest
|
||||
pEast = Transform.WithEntityId(pEast, MapData.Grid);
|
||||
pWest = Transform.WithEntityId(pWest, MapData.Grid);
|
||||
|
||||
await SetTile(PlatingRCD, SEntMan.GetNetCoordinates(pNorth), MapData.Grid);
|
||||
await SetTile(PlatingRCD, SEntMan.GetNetCoordinates(pSouth), MapData.Grid);
|
||||
await SetTile(PlatingRCD, SEntMan.GetNetCoordinates(pEast), MapData.Grid);
|
||||
await SetTile(Plating, SEntMan.GetNetCoordinates(pNorth), MapData.Grid);
|
||||
await SetTile(Plating, SEntMan.GetNetCoordinates(pSouth), MapData.Grid);
|
||||
await SetTile(Plating, SEntMan.GetNetCoordinates(pEast), MapData.Grid);
|
||||
await SetTile(Lattice, SEntMan.GetNetCoordinates(pWest), MapData.Grid);
|
||||
|
||||
Assert.That(ProtoMan.TryIndex(RCDSettingWall, out var settingWall), $"RCDPrototype not found: {RCDSettingWall}.");
|
||||
@@ -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(PlatingRCD, 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));
|
||||
|
||||
@@ -11,9 +11,7 @@ public abstract partial class InteractionTest
|
||||
protected const string Floor = "FloorSteel";
|
||||
protected const string FloorItem = "FloorTileItemSteel";
|
||||
protected const string Plating = "Plating";
|
||||
protected const string PlatingRCD = "PlatingRCD";
|
||||
protected const string Lattice = "Lattice";
|
||||
protected const string PlatingBrass = "PlatingBrass";
|
||||
|
||||
// Structures
|
||||
protected const string Airlock = "Airlock";
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,12 +44,6 @@ public sealed partial class RCDPrototype : IPrototype
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public string? Prototype { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, allows placing the entity once per direction (North, West, South and East)
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public bool AllowMultiDirection { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of charges consumed when the operation is completed
|
||||
/// </summary>
|
||||
|
||||
@@ -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!;
|
||||
@@ -146,7 +145,7 @@ public sealed class RCDSystem : EntitySystem
|
||||
var tile = _mapSystem.GetTileRef(gridUid.Value, mapGrid, location);
|
||||
var position = _mapSystem.TileIndicesFor(gridUid.Value, mapGrid, location);
|
||||
|
||||
if (!IsRCDOperationStillValid(uid, component, gridUid.Value, mapGrid, tile, position, component.ConstructionDirection, args.Target, args.User))
|
||||
if (!IsRCDOperationStillValid(uid, component, gridUid.Value, mapGrid, tile, position, args.Target, args.User))
|
||||
return;
|
||||
|
||||
if (!_net.IsServer)
|
||||
@@ -254,7 +253,7 @@ public sealed class RCDSystem : EntitySystem
|
||||
var tile = _mapSystem.GetTileRef(gridUid.Value, mapGrid, location);
|
||||
var position = _mapSystem.TileIndicesFor(gridUid.Value, mapGrid, location);
|
||||
|
||||
if (!IsRCDOperationStillValid(uid, component, gridUid.Value, mapGrid, tile, position, args.Event.Direction, args.Event.Target, args.Event.User))
|
||||
if (!IsRCDOperationStillValid(uid, component, gridUid.Value, mapGrid, tile, position, args.Event.Target, args.Event.User))
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
@@ -284,7 +283,7 @@ public sealed class RCDSystem : EntitySystem
|
||||
var position = _mapSystem.TileIndicesFor(gridUid.Value, mapGrid, location);
|
||||
|
||||
// Ensure the RCD operation is still valid
|
||||
if (!IsRCDOperationStillValid(uid, component, gridUid.Value, mapGrid, tile, position, args.Direction, args.Target, args.User))
|
||||
if (!IsRCDOperationStillValid(uid, component, gridUid.Value, mapGrid, tile, position, args.Target, args.User))
|
||||
return;
|
||||
|
||||
// Finalize the operation (this should handle prediction properly)
|
||||
@@ -319,11 +318,6 @@ public sealed class RCDSystem : EntitySystem
|
||||
#region Entity construction/deconstruction rule checks
|
||||
|
||||
public bool IsRCDOperationStillValid(EntityUid uid, RCDComponent component, EntityUid gridUid, MapGridComponent mapGrid, TileRef tile, Vector2i position, EntityUid? target, EntityUid user, bool popMsgs = true)
|
||||
{
|
||||
return IsRCDOperationStillValid(uid, component, gridUid, mapGrid, tile, position, component.ConstructionDirection, target, user, popMsgs);
|
||||
}
|
||||
|
||||
public bool IsRCDOperationStillValid(EntityUid uid, RCDComponent component, EntityUid gridUid, MapGridComponent mapGrid, TileRef tile, Vector2i position, Direction direction, EntityUid? target, EntityUid user, bool popMsgs = true)
|
||||
{
|
||||
var prototype = _protoManager.Index(component.ProtoId);
|
||||
|
||||
@@ -360,7 +354,7 @@ public sealed class RCDSystem : EntitySystem
|
||||
{
|
||||
case RcdMode.ConstructTile:
|
||||
case RcdMode.ConstructObject:
|
||||
return IsConstructionLocationValid(uid, component, gridUid, mapGrid, tile, position, direction, user, popMsgs);
|
||||
return IsConstructionLocationValid(uid, component, gridUid, mapGrid, tile, position, user, popMsgs);
|
||||
case RcdMode.Deconstruct:
|
||||
return IsDeconstructionStillValid(uid, tile, target, user, popMsgs);
|
||||
}
|
||||
@@ -368,7 +362,7 @@ public sealed class RCDSystem : EntitySystem
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsConstructionLocationValid(EntityUid uid, RCDComponent component, EntityUid gridUid, MapGridComponent mapGrid, TileRef tile, Vector2i position, Direction direction, EntityUid user, bool popMsgs = true)
|
||||
private bool IsConstructionLocationValid(EntityUid uid, RCDComponent component, EntityUid gridUid, MapGridComponent mapGrid, TileRef tile, Vector2i position, EntityUid user, bool popMsgs = true)
|
||||
{
|
||||
var prototype = _protoManager.Index(component.ProtoId);
|
||||
|
||||
@@ -411,24 +405,8 @@ public sealed class RCDSystem : EntitySystem
|
||||
return false;
|
||||
}
|
||||
|
||||
var tileDef = _turf.GetContentTileDefinition(tile);
|
||||
|
||||
// Check rule: Respect baseTurf and baseWhitelist
|
||||
if (prototype.Prototype != null && _tileDefMan.TryGetDefinition(prototype.Prototype, out var replacementDef))
|
||||
{
|
||||
var replacementContentDef = (ContentTileDefinition) replacementDef;
|
||||
|
||||
if (replacementContentDef.BaseTurf != tileDef.ID && !replacementContentDef.BaseWhitelist.Contains(tileDef.ID))
|
||||
{
|
||||
if (popMsgs)
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-cannot-build-on-empty-tile-message"), uid, user);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check rule: Tiles can't be identical
|
||||
if (tileDef.ID == prototype.Prototype)
|
||||
if (_turf.GetContentTileDefinition(tile).ID == prototype.Prototype)
|
||||
{
|
||||
if (popMsgs)
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-cannot-build-identical-tile"), uid, user);
|
||||
@@ -451,28 +429,6 @@ public sealed class RCDSystem : EntitySystem
|
||||
|
||||
foreach (var ent in _intersectingEntities)
|
||||
{
|
||||
// If the entity is the exact same prototype as what we are trying to build, then block it.
|
||||
// This is to prevent spamming objects on the same tile (e.g. lights)
|
||||
if (prototype.Prototype != null && MetaData(ent).EntityPrototype?.ID == prototype.Prototype)
|
||||
{
|
||||
var isIdentical = true;
|
||||
|
||||
if (prototype.AllowMultiDirection)
|
||||
{
|
||||
var entDirection = Transform(ent).LocalRotation.GetCardinalDir();
|
||||
if (entDirection != direction)
|
||||
isIdentical = false;
|
||||
}
|
||||
|
||||
if (isIdentical)
|
||||
{
|
||||
if (popMsgs)
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-cannot-build-identical-entity"), uid, user);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isWindow && HasComp<SharedCanBuildWindowOnTopComponent>(ent))
|
||||
continue;
|
||||
|
||||
@@ -577,10 +533,7 @@ public sealed class RCDSystem : EntitySystem
|
||||
switch (prototype.Mode)
|
||||
{
|
||||
case RcdMode.ConstructTile:
|
||||
if (!_tileDefMan.TryGetDefinition(prototype.Prototype, out var tileDef))
|
||||
return;
|
||||
|
||||
_tile.ReplaceTile(tile, (ContentTileDefinition) tileDef, gridUid, mapGrid);
|
||||
_mapSystem.SetTile(gridUid, mapGrid, position, new Tile(_tileDefMan[prototype.Prototype].TileId));
|
||||
_adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(user):user} used RCD to set grid: {gridUid} {position} to {prototype.Prototype}");
|
||||
break;
|
||||
|
||||
@@ -607,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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -3684,14 +3684,6 @@ Entries:
|
||||
id: 9385
|
||||
time: '2026-01-13T13:44:00.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/42334
|
||||
- author: Velken, Murphyneko
|
||||
changes:
|
||||
- message: Station tiles and floors can now be placed on planets, asteroids and
|
||||
different platings.
|
||||
type: Tweak
|
||||
id: 9386
|
||||
time: '2026-01-13T14:05:28.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/38898
|
||||
- author: ScarKy0
|
||||
changes:
|
||||
- message: The throwing knives bundle now comes with 8 knives instead of 4.
|
||||
@@ -3797,14 +3789,6 @@ Entries:
|
||||
id: 9398
|
||||
time: '2026-01-14T21:40:38.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/42289
|
||||
- author: TriviaSolari
|
||||
changes:
|
||||
- message: Entities that are airtight on all sides (such as full-tile walls, airlocks,
|
||||
and windows) now skip rotational checks for airtightness when initialized.
|
||||
type: Tweak
|
||||
id: 9399
|
||||
time: '2026-01-15T02:46:30.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/42390
|
||||
- author: ArtisticRoomba
|
||||
changes:
|
||||
- message: The TEG now produces 75% more power. This is to counteract tritium fires
|
||||
@@ -3867,49 +3851,3 @@ Entries:
|
||||
id: 9407
|
||||
time: '2026-01-15T20:01:06.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/42040
|
||||
- author: Velken
|
||||
changes:
|
||||
- message: RCD can no longer spam lights in the same spot.
|
||||
type: Fix
|
||||
- message: RCD can no longer be used to destroy indestructible tiles.
|
||||
type: Fix
|
||||
id: 9408
|
||||
time: '2026-01-15T20:39:02.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/42432
|
||||
- author: B_Kirill
|
||||
changes:
|
||||
- message: Added camera map. Cutting MAP wire hides camera from map while keeping
|
||||
it accessible in camera list.
|
||||
type: Add
|
||||
id: 9409
|
||||
time: '2026-01-15T21:37:19.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/39684
|
||||
- author: ScarKy0
|
||||
changes:
|
||||
- message: Added Makeshift and Regular Mortar&Pestle! It can be found in the nutrimax
|
||||
and dinnerware vendors or crafted. It works slower and can only process 1 item
|
||||
at a time.
|
||||
type: Add
|
||||
- message: Added Makeshift and Regular Handheld Juicer! It can be found in the nutrimax
|
||||
and dinnerware vendors or crafted. It works slower and can only process 1 item
|
||||
at a time.
|
||||
type: Add
|
||||
id: 9410
|
||||
time: '2026-01-16T00:35:32.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/42019
|
||||
- author: EmoGarbage404
|
||||
changes:
|
||||
- message: Fixed flatpackers ignoring material costs for certain machine board requirements
|
||||
and being able to print unlatheable items for flatpacks.
|
||||
type: Fix
|
||||
id: 9411
|
||||
time: '2026-01-16T00:52:35.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/42445
|
||||
- author: sowelipililimute
|
||||
changes:
|
||||
- message: Using a core pinpointer piece on another piece no longer causes you to
|
||||
start an unintended construction recipe.
|
||||
type: Fix
|
||||
id: 9412
|
||||
time: '2026-01-16T02:09:01.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/42446
|
||||
|
||||
@@ -29,7 +29,6 @@ rcd-component-must-build-on-subfloor-message = You can only build that on expose
|
||||
rcd-component-cannot-build-on-subfloor-message = You can't build that on exposed subfloor!
|
||||
rcd-component-cannot-build-on-occupied-tile-message = You can't build here, the space is already occupied!
|
||||
rcd-component-cannot-build-identical-tile = That tile already exists there!
|
||||
rcd-component-cannot-build-identical-entity = That already exists there!
|
||||
|
||||
|
||||
### Category names
|
||||
|
||||
@@ -1749,6 +1749,9 @@
|
||||
state: generic_panel_open
|
||||
# - type: Computer
|
||||
# board: SyndicateCommsComputerCircuitboard
|
||||
- type: Anchorable
|
||||
flags:
|
||||
- Anchorable # No taking the console along!
|
||||
- type: PointLight
|
||||
radius: 1.5
|
||||
energy: 1.6
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
category: WallsAndFlooring
|
||||
sprite: /Textures/Interface/Radial/RCD/plating.png
|
||||
mode: ConstructTile
|
||||
prototype: PlatingRCD
|
||||
prototype: Plating
|
||||
cost: 1
|
||||
delay: 1
|
||||
collisionMask: InteractImpassable
|
||||
@@ -128,7 +128,6 @@
|
||||
- IsWindow
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct1
|
||||
allowMultiDirection: true
|
||||
|
||||
- type: rcd
|
||||
id: ReinforcedWindow
|
||||
@@ -158,7 +157,6 @@
|
||||
- IsWindow
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct2
|
||||
allowMultiDirection: true
|
||||
|
||||
# Airlocks
|
||||
- type: rcd
|
||||
@@ -210,7 +208,6 @@
|
||||
collisionBounds: "-0.23,-0.49,0.23,-0.36"
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct1
|
||||
allowMultiDirection: true
|
||||
|
||||
- type: rcd
|
||||
id: BulbLight
|
||||
@@ -224,7 +221,6 @@
|
||||
collisionBounds: "-0.23,-0.49,0.23,-0.36"
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct1
|
||||
allowMultiDirection: true
|
||||
|
||||
# Electrical
|
||||
- type: rcd
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -36,6 +30,12 @@
|
||||
- 0.06
|
||||
- 0.01
|
||||
# Corvax-Mapping-End
|
||||
isSubfloor: true
|
||||
footstepSounds:
|
||||
collection: FootstepAsteroid
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
indestructible: true
|
||||
|
||||
- type: tile
|
||||
id: FloorLowDesert
|
||||
@@ -49,11 +49,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
|
||||
@@ -73,20 +78,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
|
||||
@@ -110,8 +124,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
|
||||
@@ -128,7 +146,6 @@
|
||||
# Dug snow
|
||||
- type: tile
|
||||
id: FloorSnowDug
|
||||
parent: BaseFloorPlanet
|
||||
name: tiles-snow-dug
|
||||
sprite: /Textures/Tiles/Planet/Snow/snow_dug.png
|
||||
edgeSpritePriority: 1
|
||||
@@ -137,7 +154,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
|
||||
|
||||
@@ -1,52 +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
|
||||
|
||||
- type: tile
|
||||
id: PlatingRCD
|
||||
parent: Plating
|
||||
baseWhitelist:
|
||||
- TrainLattice
|
||||
- FloorPlanetDirt
|
||||
- FloorDesert
|
||||
- FloorLowDesert
|
||||
- FloorPlanetGrass
|
||||
- FloorSnow
|
||||
- FloorDirt
|
||||
- FloorAsteroidIronsand
|
||||
- FloorAsteroidSand
|
||||
- FloorAsteroidSandBorderless
|
||||
- FloorAsteroidIronsandBorderless
|
||||
- FloorAsteroidSandRedBorderless
|
||||
|
||||
- type: tile
|
||||
id: FloorHullReinforced
|
||||
parent: BasePlating
|
||||
name: tiles-hull-reinforced
|
||||
sprite: /Textures/Tiles/hull_reinforced.png
|
||||
footstepSounds:
|
||||
collection: FootstepHull
|
||||
itemDrop: FloorTileItemSteel
|
||||
heatCapacity: 100000 #/tg/ has this set as "INFINITY." I don't know if that exists here so I've just added an extra 0
|
||||
indestructible: true
|
||||
friction: 1.5
|
||||
heatCapacity: 10000
|
||||
|
||||
- type: tile
|
||||
id: PlatingDamaged
|
||||
parent: BasePlating
|
||||
name: tiles-plating
|
||||
sprite: /Textures/Tiles/plating_damaged.png
|
||||
variants: 3
|
||||
@@ -54,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
|
||||
@@ -103,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
|
||||
|
||||
Reference in New Issue
Block a user