Layering for atmospheric pipes (#36124)

Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
Co-authored-by: Kevin Zheng <kevinz5000@gmail.com>
This commit is contained in:
chromiumboy
2025-06-02 00:01:43 -05:00
committed by GitHub
parent aa5ca90a3b
commit 45012cbe1d
134 changed files with 2671 additions and 306 deletions

View File

@@ -0,0 +1,203 @@
using Content.Client.Construction;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Construction.Prototypes;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Placement;
using Robust.Client.Placement.Modes;
using Robust.Client.Utility;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using System.Numerics;
using static Robust.Client.Placement.PlacementManager;
namespace Content.Client.Atmos;
/// <summary>
/// Allows users to place atmos pipes on different layers depending on how the mouse cursor is positioned within a grid tile.
/// </summary>
/// <remarks>
/// This placement mode is not on the engine because it is content specific.
/// </remarks>
public sealed class AlignAtmosPipeLayers : SnapgridCenter
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
private readonly SharedMapSystem _mapSystem;
private readonly SharedTransformSystem _transformSystem;
private readonly SharedAtmosPipeLayersSystem _pipeLayersSystem;
private readonly SpriteSystem _spriteSystem;
private const float SearchBoxSize = 2f;
private EntityCoordinates _unalignedMouseCoords = default;
private const float MouseDeadzoneRadius = 0.25f;
private Color _guideColor = new Color(0, 0, 0.5785f);
private const float GuideRadius = 0.1f;
private const float GuideOffset = 0.21875f;
public AlignAtmosPipeLayers(PlacementManager pMan) : base(pMan)
{
IoCManager.InjectDependencies(this);
_mapSystem = _entityManager.System<SharedMapSystem>();
_transformSystem = _entityManager.System<SharedTransformSystem>();
_pipeLayersSystem = _entityManager.System<SharedAtmosPipeLayersSystem>();
_spriteSystem = _entityManager.System<SpriteSystem>();
}
/// <inheritdoc/>
public override void Render(in OverlayDrawArgs args)
{
var gridUid = _entityManager.System<SharedTransformSystem>().GetGrid(MouseCoords);
if (gridUid == null || Grid == null)
return;
// Draw guide circles for each pipe layer if we are not in line/grid placing mode
if (pManager.PlacementType == PlacementTypes.None)
{
var gridRotation = _transformSystem.GetWorldRotation(gridUid.Value);
var worldPosition = _mapSystem.LocalToWorld(gridUid.Value, Grid, MouseCoords.Position);
var direction = (_eyeManager.CurrentEye.Rotation + gridRotation + Math.PI / 2).GetCardinalDir();
var multi = (direction == Direction.North || direction == Direction.South) ? -1f : 1f;
args.WorldHandle.DrawCircle(worldPosition, GuideRadius, _guideColor);
args.WorldHandle.DrawCircle(worldPosition + gridRotation.RotateVec(new Vector2(multi * GuideOffset, GuideOffset)), GuideRadius, _guideColor);
args.WorldHandle.DrawCircle(worldPosition - gridRotation.RotateVec(new Vector2(multi * GuideOffset, GuideOffset)), GuideRadius, _guideColor);
}
base.Render(args);
}
/// <inheritdoc/>
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
{
_unalignedMouseCoords = ScreenToCursorGrid(mouseScreen);
base.AlignPlacementMode(mouseScreen);
// Exit early if we are in line/grid placing mode
if (pManager.PlacementType != PlacementTypes.None)
return;
MouseCoords = _unalignedMouseCoords.AlignWithClosestGridTile(SearchBoxSize, _entityManager, _mapManager);
var gridId = _transformSystem.GetGrid(MouseCoords);
if (!_entityManager.TryGetComponent<MapGridComponent>(gridId, out var mapGrid))
return;
var gridRotation = _transformSystem.GetWorldRotation(gridId.Value);
CurrentTile = _mapSystem.GetTileRef(gridId.Value, mapGrid, MouseCoords);
float tileSize = mapGrid.TileSize;
GridDistancing = tileSize;
MouseCoords = new EntityCoordinates(MouseCoords.EntityId, new Vector2(CurrentTile.X + tileSize / 2 + pManager.PlacementOffset.X,
CurrentTile.Y + tileSize / 2 + pManager.PlacementOffset.Y));
// Calculate the position of the mouse cursor with respect to the center of the tile to determine which layer to use
var mouseCoordsDiff = _unalignedMouseCoords.Position - MouseCoords.Position;
var layer = AtmosPipeLayer.Primary;
if (mouseCoordsDiff.Length() > MouseDeadzoneRadius)
{
// Determine the direction of the mouse is relative to the center of the tile, adjusting for the player eye and grid rotation
var direction = (new Angle(mouseCoordsDiff) + _eyeManager.CurrentEye.Rotation + gridRotation + Math.PI / 2).GetCardinalDir();
layer = (direction == Direction.North || direction == Direction.East) ? AtmosPipeLayer.Secondary : AtmosPipeLayer.Tertiary;
}
// Update the construction menu placer
if (pManager.Hijack != null)
UpdateHijackedPlacer(layer, mouseScreen);
// Otherwise update the debug placer
else
UpdatePlacer(layer);
}
private void UpdateHijackedPlacer(AtmosPipeLayer layer, ScreenCoordinates mouseScreen)
{
// Try to get alternative prototypes from the construction prototype
var constructionSystem = (pManager.Hijack as ConstructionPlacementHijack)?.CurrentConstructionSystem;
var altPrototypes = (pManager.Hijack as ConstructionPlacementHijack)?.CurrentPrototype?.AlternativePrototypes;
if (constructionSystem == null || altPrototypes == null || (int)layer >= altPrototypes.Length)
return;
var newProtoId = altPrototypes[(int)layer];
if (!_protoManager.TryIndex(newProtoId, out var newProto))
return;
if (newProto.Type != ConstructionType.Structure)
{
pManager.Clear();
return;
}
if (newProto.ID == (pManager.Hijack as ConstructionPlacementHijack)?.CurrentPrototype?.ID)
return;
// Start placing
pManager.BeginPlacing(new PlacementInformation()
{
IsTile = false,
PlacementOption = newProto.PlacementMode,
}, new ConstructionPlacementHijack(constructionSystem, newProto));
if (pManager.CurrentMode is AlignAtmosPipeLayers { } newMode)
newMode.RefreshGrid(mouseScreen);
// Update construction guide
constructionSystem.GetGuide(newProto);
}
private void UpdatePlacer(AtmosPipeLayer layer)
{
// Try to get alternative prototypes from the entity atmos pipe layer component
if (pManager.CurrentPermission?.EntityType == null)
return;
if (!_protoManager.TryIndex<EntityPrototype>(pManager.CurrentPermission.EntityType, out var currentProto))
return;
if (!currentProto.TryGetComponent<AtmosPipeLayersComponent>(out var atmosPipeLayers, _entityManager.ComponentFactory))
return;
if (!_pipeLayersSystem.TryGetAlternativePrototype(atmosPipeLayers, layer, out var newProtoId))
return;
if (_protoManager.TryIndex<EntityPrototype>(newProtoId, out var newProto))
{
// Update the placed prototype
pManager.CurrentPermission.EntityType = newProtoId;
// Update the appearance of the ghost sprite
if (newProto.TryGetComponent<SpriteComponent>(out var sprite, _entityManager.ComponentFactory))
{
var textures = new List<IDirectionalTextureProvider>();
foreach (var spriteLayer in sprite.AllLayers)
{
if (spriteLayer.ActualRsi?.Path != null && spriteLayer.RsiState.Name != null)
textures.Add(_spriteSystem.RsiStateLike(new SpriteSpecifier.Rsi(spriteLayer.ActualRsi.Path, spriteLayer.RsiState.Name)));
}
pManager.CurrentTextures = textures;
}
}
}
private void RefreshGrid(ScreenCoordinates mouseScreen)
{
base.AlignPlacementMode(mouseScreen);
}
}

View File

@@ -17,6 +17,10 @@ public sealed partial class AtmosMonitoringConsoleNavMapControl : NavMapControl
public int? FocusNetId = null;
private const int ChunkSize = 4;
private const float ScaleModifier = 4f;
private readonly float[] _layerFraction = { 0.5f, 0.75f, 0.25f };
private const float LineThickness = 0.05f;
private readonly Color _basePipeNetColor = Color.LightGray;
private readonly Color _unfocusedPipeNetColor = Color.DimGray;
@@ -95,23 +99,23 @@ public sealed partial class AtmosMonitoringConsoleNavMapControl : NavMapControl
foreach (var chunkedLine in atmosPipeNetwork)
{
var leftTop = ScalePosition(new Vector2
(Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - 0.1f,
Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - 0.1f)
(Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - LineThickness,
Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - LineThickness)
- offset);
var rightTop = ScalePosition(new Vector2
(Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + 0.1f,
Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - 0.1f)
(Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + LineThickness,
Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - LineThickness)
- offset);
var leftBottom = ScalePosition(new Vector2
(Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - 0.1f,
Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + 0.1f)
(Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - LineThickness,
Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + LineThickness)
- offset);
var rightBottom = ScalePosition(new Vector2
(Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + 0.1f,
Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + 0.1f)
(Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + LineThickness,
Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + LineThickness)
- offset);
if (!pipeVertexUVs.TryGetValue(chunkedLine.Color, out var pipeVertexUV))
@@ -158,7 +162,7 @@ public sealed partial class AtmosMonitoringConsoleNavMapControl : NavMapControl
{
var list = new List<AtmosMonitoringConsoleLine>();
foreach (var ((netId, hexColor), atmosPipeData) in chunk.AtmosPipeData)
foreach (var ((netId, layer, hexColor), atmosPipeData) in chunk.AtmosPipeData)
{
// Determine the correct coloration for the pipe
var color = Color.FromHex(hexColor) * _basePipeNetColor;
@@ -191,6 +195,9 @@ public sealed partial class AtmosMonitoringConsoleNavMapControl : NavMapControl
_vertLinesReversed[color] = vertLinesReversed;
}
var layerFraction = _layerFraction[(int)layer];
var origin = new Vector2(grid.TileSize * layerFraction, -grid.TileSize * layerFraction);
// Loop over the chunk
for (var tileIdx = 0; tileIdx < ChunkSize * ChunkSize; tileIdx++)
{
@@ -208,21 +215,22 @@ public sealed partial class AtmosMonitoringConsoleNavMapControl : NavMapControl
// Calculate the draw point offsets
var vertLineOrigin = (atmosPipeData & northMask << tileIdx * SharedNavMapSystem.Directions) > 0 ?
new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 1f) : new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f);
new Vector2(grid.TileSize * layerFraction, -grid.TileSize * 1f) : origin;
var vertLineTerminus = (atmosPipeData & southMask << tileIdx * SharedNavMapSystem.Directions) > 0 ?
new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0f) : new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f);
new Vector2(grid.TileSize * layerFraction, -grid.TileSize * 0f) : origin;
var horizLineOrigin = (atmosPipeData & eastMask << tileIdx * SharedNavMapSystem.Directions) > 0 ?
new Vector2(grid.TileSize * 1f, -grid.TileSize * 0.5f) : new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f);
new Vector2(grid.TileSize * 1f, -grid.TileSize * layerFraction) : origin;
var horizLineTerminus = (atmosPipeData & westMask << tileIdx * SharedNavMapSystem.Directions) > 0 ?
new Vector2(grid.TileSize * 0f, -grid.TileSize * 0.5f) : new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f);
new Vector2(grid.TileSize * 0f, -grid.TileSize * layerFraction) : origin;
// Since we can have pipe lines that have a length of a half tile,
// double the vectors and convert to vector2i so we can merge them
AddOrUpdateNavMapLine(ConvertVector2ToVector2i(tile + horizLineOrigin, 2), ConvertVector2ToVector2i(tile + horizLineTerminus, 2), horizLines, horizLinesReversed);
AddOrUpdateNavMapLine(ConvertVector2ToVector2i(tile + vertLineOrigin, 2), ConvertVector2ToVector2i(tile + vertLineTerminus, 2), vertLines, vertLinesReversed);
// Scale up the vectors and convert to vector2i so we can merge them
AddOrUpdateNavMapLine(ConvertVector2ToVector2i(tile + horizLineOrigin, ScaleModifier),
ConvertVector2ToVector2i(tile + horizLineTerminus, ScaleModifier), horizLines, horizLinesReversed);
AddOrUpdateNavMapLine(ConvertVector2ToVector2i(tile + vertLineOrigin, ScaleModifier),
ConvertVector2ToVector2i(tile + vertLineTerminus, ScaleModifier), vertLines, vertLinesReversed);
}
}
}
@@ -235,7 +243,7 @@ public sealed partial class AtmosMonitoringConsoleNavMapControl : NavMapControl
foreach (var (origin, terminal) in horizLines)
decodedOutput.Add(new AtmosMonitoringConsoleLine
(ConvertVector2iToVector2(origin, 0.5f), ConvertVector2iToVector2(terminal, 0.5f), sRGB));
(ConvertVector2iToVector2(origin, 1f / ScaleModifier), ConvertVector2iToVector2(terminal, 1f / ScaleModifier), sRGB));
}
foreach (var (color, vertLines) in _vertLines)
@@ -245,7 +253,7 @@ public sealed partial class AtmosMonitoringConsoleNavMapControl : NavMapControl
foreach (var (origin, terminal) in vertLines)
decodedOutput.Add(new AtmosMonitoringConsoleLine
(ConvertVector2iToVector2(origin, 0.5f), ConvertVector2iToVector2(terminal, 0.5f), sRGB));
(ConvertVector2iToVector2(origin, 1f / ScaleModifier), ConvertVector2iToVector2(terminal, 1f / ScaleModifier), sRGB));
}
return decodedOutput;

View File

@@ -15,7 +15,7 @@ public sealed class AtmosMonitoringConsoleSystem : SharedAtmosMonitoringConsoleS
private void OnHandleState(EntityUid uid, AtmosMonitoringConsoleComponent component, ref ComponentHandleState args)
{
Dictionary<Vector2i, Dictionary<(int, string), ulong>> modifiedChunks;
Dictionary<Vector2i, Dictionary<AtmosMonitoringConsoleSubnet, ulong>> modifiedChunks;
Dictionary<NetEntity, AtmosDeviceNavMapData> atmosDevices;
switch (args.Current)
@@ -54,7 +54,7 @@ public sealed class AtmosMonitoringConsoleSystem : SharedAtmosMonitoringConsoleS
foreach (var (origin, chunk) in modifiedChunks)
{
var newChunk = new AtmosPipeChunk(origin);
newChunk.AtmosPipeData = new Dictionary<(int, string), ulong>(chunk);
newChunk.AtmosPipeData = new Dictionary<AtmosMonitoringConsoleSubnet, ulong>(chunk);
component.AtmosPipeChunks[origin] = newChunk;
}

View File

@@ -13,6 +13,7 @@ using Robust.Shared.Timing;
using Robust.Shared.Utility;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
namespace Content.Client.Atmos.Consoles;
@@ -33,6 +34,8 @@ public sealed partial class AtmosMonitoringConsoleWindow : FancyWindow
private ProtoId<NavMapBlipPrototype> _navMapConsoleProtoId = "NavMapConsole";
private ProtoId<NavMapBlipPrototype> _gasPipeSensorProtoId = "GasPipeSensor";
private readonly Vector2[] _pipeLayerOffsets = { new Vector2(0f, 0f), new Vector2(0.25f, 0.25f), new Vector2(-0.25f, -0.25f) };
public AtmosMonitoringConsoleWindow(AtmosMonitoringConsoleBoundUserInterface userInterface, EntityUid? owner)
{
RobustXamlLoader.Load(this);
@@ -238,6 +241,10 @@ public sealed partial class AtmosMonitoringConsoleWindow : FancyWindow
var blinks = proto.Blinks || _focusEntity == metaData.NetEntity;
var coords = _entManager.GetCoordinates(metaData.NetCoordinates);
if (proto.Placement == NavMapBlipPlacement.Offset && metaData.PipeLayer > 0)
coords = coords.Offset(_pipeLayerOffsets[(int)metaData.PipeLayer]);
var blip = new NavMapBlip(coords, _spriteSystem.Frame0(new SpriteSpecifier.Texture(texture)), color, blinks, proto.Selectable, proto.Scale);
NavMap.TrackedEntities[metaData.NetEntity] = blip;
}

View File

@@ -1,6 +1,7 @@
using Content.Client.SubFloor;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Atmos.Piping;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
@@ -8,7 +9,7 @@ using Robust.Client.GameObjects;
namespace Content.Client.Atmos.EntitySystems;
[UsedImplicitly]
public sealed class AtmosPipeAppearanceSystem : EntitySystem
public sealed partial class AtmosPipeAppearanceSystem : SharedAtmosPipeAppearanceSystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
@@ -26,28 +27,39 @@ public sealed class AtmosPipeAppearanceSystem : EntitySystem
if (!TryComp(uid, out SpriteComponent? sprite))
return;
var numberOfPipeLayers = GetNumberOfPipeLayers(uid, out _);
foreach (var layerKey in Enum.GetValues<PipeConnectionLayer>())
{
var layer = _sprite.LayerMapReserve((uid, sprite), layerKey);
_sprite.LayerSetRsi((uid, sprite), layer, component.Sprite.RsiPath);
_sprite.LayerSetRsiState((uid, sprite), layer, component.Sprite.RsiState);
for (byte i = 0; i < numberOfPipeLayers; i++)
{
var layerName = layerKey.ToString() + i.ToString();
var layer = _sprite.LayerMapReserve((uid, sprite), layerName);
_sprite.LayerSetRsi((uid, sprite), layer, component.Sprite[i].RsiPath);
_sprite.LayerSetRsiState((uid, sprite), layer, component.Sprite[i].RsiState);
_sprite.LayerSetDirOffset((uid, sprite), layer, ToOffset(layerKey));
}
}
}
private void HideAllPipeConnection(Entity<SpriteComponent> entity)
private void HideAllPipeConnection(Entity<SpriteComponent> entity, AtmosPipeLayersComponent? atmosPipeLayers, int numberOfPipeLayers)
{
var sprite = entity.Comp;
foreach (var layerKey in Enum.GetValues<PipeConnectionLayer>())
{
if (!_sprite.LayerMapTryGet(entity.AsNullable(), layerKey, out var key, false))
for (byte i = 0; i < numberOfPipeLayers; i++)
{
var layerName = layerKey.ToString() + i.ToString();
if (!_sprite.LayerMapTryGet(entity.AsNullable(), layerName, out var key, false))
continue;
var layer = sprite[key];
layer.Visible = false;
}
}
}
private void OnAppearanceChanged(EntityUid uid, PipeAppearanceComponent component, ref AppearanceChangeEvent args)
{
@@ -61,21 +73,32 @@ public sealed class AtmosPipeAppearanceSystem : EntitySystem
return;
}
if (!_appearance.TryGetData<PipeDirection>(uid, PipeVisuals.VisualState, out var worldConnectedDirections, args.Component))
var numberOfPipeLayers = GetNumberOfPipeLayers(uid, out var atmosPipeLayers);
if (!_appearance.TryGetData<int>(uid, PipeVisuals.VisualState, out var worldConnectedDirections, args.Component))
{
HideAllPipeConnection((uid, args.Sprite));
HideAllPipeConnection((uid, args.Sprite), atmosPipeLayers, numberOfPipeLayers);
return;
}
if (!_appearance.TryGetData<Color>(uid, PipeColorVisuals.Color, out var color, args.Component))
color = Color.White;
// transform connected directions to local-coordinates
var connectedDirections = worldConnectedDirections.RotatePipeDirection(-Transform(uid).LocalRotation);
for (byte i = 0; i < numberOfPipeLayers; i++)
{
// Extract the cardinal pipe orientations for the current pipe layer
// '15' is the four bit mask that is used to extract the pipe orientations of interest from 'worldConnectedDirections'
// Fun fact: a collection of four bits is called a 'nibble'! They aren't natively supported :(
var pipeLayerConnectedDirections = (PipeDirection)(15 & (worldConnectedDirections >> (PipeDirectionHelpers.PipeDirections * i)));
// Transform the connected directions to local-coordinates
var connectedDirections = pipeLayerConnectedDirections.RotatePipeDirection(-Transform(uid).LocalRotation);
foreach (var layerKey in Enum.GetValues<PipeConnectionLayer>())
{
if (!_sprite.LayerMapTryGet((uid, args.Sprite), layerKey, out var key, false))
var layerName = layerKey.ToString() + i.ToString();
if (!_sprite.LayerMapTryGet((uid, args.Sprite), layerName, out var key, false))
continue;
var layer = args.Sprite[key];
@@ -90,6 +113,7 @@ public sealed class AtmosPipeAppearanceSystem : EntitySystem
layer.Color = color;
}
}
}
private SpriteComponent.DirectionOffset ToOffset(PipeConnectionLayer layer)
{

View File

@@ -0,0 +1,56 @@
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization.TypeSerializers.Implementations;
using Robust.Shared.Utility;
using System.Diagnostics.CodeAnalysis;
namespace Content.Client.Atmos.EntitySystems;
/// <summary>
/// The system responsible for updating the appearance of layered gas pipe
/// </summary>
public sealed partial class AtmosPipeLayersSystem : SharedAtmosPipeLayersSystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly IReflectionManager _reflection = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AtmosPipeLayersComponent, AppearanceChangeEvent>(OnAppearanceChange);
}
private void OnAppearanceChange(Entity<AtmosPipeLayersComponent> ent, ref AppearanceChangeEvent ev)
{
if (!TryComp<SpriteComponent>(ent, out var sprite))
return;
if (_appearance.TryGetData<string>(ent, AtmosPipeLayerVisuals.Sprite, out var spriteRsi) &&
_resourceCache.TryGetResource(SpriteSpecifierSerializer.TextureRoot / spriteRsi, out RSIResource? resource))
{
_sprite.SetBaseRsi((ent, sprite), resource.RSI);
}
if (_appearance.TryGetData<Dictionary<string, string>>(ent, AtmosPipeLayerVisuals.SpriteLayers, out var pipeState))
{
foreach (var (layerKey, rsiPath) in pipeState)
{
if (TryParseKey(layerKey, out var @enum))
_sprite.LayerSetRsi((ent, sprite), @enum, new ResPath(rsiPath));
else
_sprite.LayerSetRsi((ent, sprite), layerKey, new ResPath(rsiPath));
}
}
}
private bool TryParseKey(string keyString, [NotNullWhen(true)] out Enum? @enum)
{
return _reflection.TryParseEnumReference(keyString, out @enum);
}
}

View File

@@ -136,6 +136,7 @@ namespace Content.Client.Atmos.UI
else
{
// oh shit of fuck its more than 4 this ui isn't gonna look pretty anymore
CDeviceMixes.RemoveAllChildren();
for (var i = 1; i < msg.NodeGasMixes.Length; i++)
{
GenerateGasDisplay(msg.NodeGasMixes[i], CDeviceMixes);

View File

@@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using Content.Shared.Construction.Prototypes;
using Robust.Client.GameObjects;
using Robust.Client.Placement;
@@ -13,6 +13,9 @@ namespace Content.Client.Construction
private readonly ConstructionSystem _constructionSystem;
private readonly ConstructionPrototype? _prototype;
public ConstructionSystem? CurrentConstructionSystem { get { return _constructionSystem; } }
public ConstructionPrototype? CurrentPrototype { get { return _prototype; } }
public override bool CanRotate { get; }
public ConstructionPlacementHijack(ConstructionSystem constructionSystem, ConstructionPrototype? prototype)

View File

@@ -282,16 +282,11 @@ public sealed class AtmosMonitoringConsoleSystem : SharedAtmosMonitoringConsoleS
return false;
var direction = xform.LocalRotation.GetCardinalDir();
var netId = TryGettingFirstPipeNode(uid, out var _, out var firstNetId) ? firstNetId : -1;
var color = TryComp<AtmosPipeColorComponent>(uid, out var atmosPipeColor) ? atmosPipeColor.Color : Color.White;
var layer = TryComp<AtmosPipeLayersComponent>(uid, out var atmosPipeLayers) ? atmosPipeLayers.CurrentPipeLayer : AtmosPipeLayer.Primary;
if (!TryGettingFirstPipeNode(uid, out var _, out var netId))
netId = -1;
var color = Color.White;
if (TryComp<AtmosPipeColorComponent>(uid, out var atmosPipeColor))
color = atmosPipeColor.Color;
device = new AtmosDeviceNavMapData(GetNetEntity(uid), GetNetCoordinates(xform.Coordinates), netId.Value, component.NavMapBlip.Value, direction, color);
device = new AtmosDeviceNavMapData(GetNetEntity(uid), GetNetCoordinates(xform.Coordinates), netId.Value, component.NavMapBlip.Value, direction, color, layer);
return true;
}
@@ -371,7 +366,9 @@ public sealed class AtmosMonitoringConsoleSystem : SharedAtmosMonitoringConsoleS
if (!TryComp<NodeContainerComponent>(ent, out var entNodeContainer))
continue;
UpdateAtmosPipeChunk(ent, entNodeContainer, entAtmosPipeColor, tileIdx, ref chunk);
var showAbsentConnections = TryComp<AtmosMonitoringConsoleDeviceComponent>(ent, out var device) ? device.ShowAbsentConnections : true;
UpdateAtmosPipeChunk(ent, entNodeContainer, entAtmosPipeColor, tileIdx, ref chunk, showAbsentConnections);
}
// Add or update the chunk on the associated grid
@@ -393,7 +390,13 @@ public sealed class AtmosMonitoringConsoleSystem : SharedAtmosMonitoringConsoleS
}
}
private void UpdateAtmosPipeChunk(EntityUid uid, NodeContainerComponent nodeContainer, AtmosPipeColorComponent pipeColor, int tileIdx, ref AtmosPipeChunk chunk)
private void UpdateAtmosPipeChunk
(EntityUid uid,
NodeContainerComponent nodeContainer,
AtmosPipeColorComponent pipeColor,
int tileIdx,
ref AtmosPipeChunk chunk,
bool showAbsentConnections = true)
{
// Entities that are actively being deleted are not to be drawn
if (MetaData(uid).EntityLifeStage >= EntityLifeStage.Terminating)
@@ -401,16 +404,19 @@ public sealed class AtmosMonitoringConsoleSystem : SharedAtmosMonitoringConsoleS
foreach ((var id, var node) in nodeContainer.Nodes)
{
if (node is not PipeNode)
if (node is not PipeNode { } pipeNode)
continue;
if (!showAbsentConnections && !pipeNode.ReachableNodes.Any(x => x.Owner != uid))
continue;
var pipeNode = (PipeNode)node;
var netId = GetPipeNodeNetId(pipeNode);
var subnet = new AtmosMonitoringConsoleSubnet(netId, pipeNode.CurrentPipeLayer, pipeColor.Color.ToHex());
var pipeDirection = pipeNode.CurrentPipeDirection;
chunk.AtmosPipeData.TryGetValue((netId, pipeColor.Color.ToHex()), out var atmosPipeData);
chunk.AtmosPipeData.TryGetValue(subnet, out var atmosPipeData);
atmosPipeData |= (ulong)pipeDirection << tileIdx * SharedNavMapSystem.Directions;
chunk.AtmosPipeData[(netId, pipeColor.Color.ToHex())] = atmosPipeData;
chunk.AtmosPipeData[subnet] = atmosPipeData;
}
}

View File

@@ -0,0 +1,77 @@
using Content.Server.Atmos.Components;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.NodeGroups;
using Content.Server.NodeContainer.Nodes;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Construction.Components;
using Content.Shared.NodeContainer;
using Content.Shared.Popups;
namespace Content.Server.Atmos.EntitySystems;
/// <summary>
/// The system responsible for checking and adjusting the connection layering of gas pipes
/// </summary>
public sealed partial class AtmosPipeLayersSystem : SharedAtmosPipeLayersSystem
{
[Dependency] private readonly NodeGroupSystem _nodeGroup = default!;
[Dependency] private readonly PipeRestrictOverlapSystem _pipeRestrictOverlap = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedTransformSystem _xform = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AtmosPipeLayersComponent, ComponentInit>(OnInit);
}
private void OnInit(Entity<AtmosPipeLayersComponent> ent, ref ComponentInit args)
{
SetPipeLayer(ent, ent.Comp.CurrentPipeLayer);
}
/// <inheritdoc/>
public override void SetPipeLayer(Entity<AtmosPipeLayersComponent> ent, AtmosPipeLayer layer, EntityUid? user = null, EntityUid? used = null)
{
if (ent.Comp.PipeLayersLocked)
return;
base.SetPipeLayer(ent, layer);
if (!TryComp<NodeContainerComponent>(ent, out var nodeContainer))
return;
// Update the layer values of all pipe nodes associated with the entity
foreach (var (id, node) in nodeContainer.Nodes)
{
if (node is not PipeNode { } pipeNode)
continue;
if (pipeNode.CurrentPipeLayer == ent.Comp.CurrentPipeLayer)
continue;
pipeNode.CurrentPipeLayer = ent.Comp.CurrentPipeLayer;
if (pipeNode.NodeGroup != null)
_nodeGroup.QueueRemakeGroup((BaseNodeGroup)pipeNode.NodeGroup);
}
// If a user wasn't responsible for unanchoring the pipe, leave it be
if (user == null || used == null)
return;
// Unanchor the pipe if its new layer overlaps with another pipe
var xform = Transform(ent);
if (!HasComp<PipeRestrictOverlapComponent>(ent) || !_pipeRestrictOverlap.CheckOverlap((ent, nodeContainer, xform)))
return;
RaiseLocalEvent(ent, new BeforeUnanchoredEvent(user.Value, used.Value));
_xform.Unanchor(ent, xform);
RaiseLocalEvent(ent, new UserUnanchoredEvent(user.Value, used.Value));
_popup.PopupEntity(Loc.GetString("pipe-restrict-overlap-popup-blocked", ("pipe", ent)), ent, user.Value);
}
}

View File

@@ -4,6 +4,7 @@ using Content.Server.NodeContainer;
using Content.Server.NodeContainer.Nodes;
using Content.Server.Popups;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Construction.Components;
using Content.Shared.NodeContainer;
using JetBrains.Annotations;
@@ -97,27 +98,27 @@ public sealed class PipeRestrictOverlapSystem : EntitySystem
public bool PipeNodesOverlap(Entity<NodeContainerComponent, TransformComponent> ent, Entity<NodeContainerComponent, TransformComponent> other)
{
var entDirs = GetAllDirections(ent).ToList();
var otherDirs = GetAllDirections(other).ToList();
var entDirsAndLayers = GetAllDirectionsAndLayers(ent).ToList();
var otherDirsAndLayers = GetAllDirectionsAndLayers(other).ToList();
foreach (var dir in entDirs)
foreach (var (dir, layer) in entDirsAndLayers)
{
foreach (var otherDir in otherDirs)
foreach (var (otherDir, otherLayer) in otherDirsAndLayers)
{
if ((dir & otherDir) != 0)
if ((dir & otherDir) != 0 && layer == otherLayer)
return true;
}
}
return false;
IEnumerable<PipeDirection> GetAllDirections(Entity<NodeContainerComponent, TransformComponent> pipe)
IEnumerable<(PipeDirection, AtmosPipeLayer)> GetAllDirectionsAndLayers(Entity<NodeContainerComponent, TransformComponent> pipe)
{
foreach (var node in pipe.Comp1.Nodes.Values)
{
// we need to rotate the pipe manually like this because the rotation doesn't update for pipes that are unanchored.
if (node is PipeNode pipeNode)
yield return pipeNode.OriginalPipeDirection.RotatePipeDirection(pipe.Comp2.LocalRotation);
yield return (pipeNode.OriginalPipeDirection.RotatePipeDirection(pipe.Comp2.LocalRotation), pipeNode.CurrentPipeLayer);
}
}
}

View File

@@ -0,0 +1,11 @@
namespace Content.Server.Atmos.Piping.Components;
[RegisterComponent]
public sealed partial class GasPipeManifoldComponent : Component
{
[DataField("inlets")]
public HashSet<string> InletNames { get; set; } = new() { "south0", "south1", "south2" };
[DataField("outlets")]
public HashSet<string> OutletNames { get; set; } = new() { "north0", "north1", "north2" };
}

View File

@@ -1,14 +1,14 @@
using Content.Server.NodeContainer;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.NodeContainer;
using Robust.Shared.Map.Components;
namespace Content.Server.Atmos.Piping.EntitySystems;
public sealed class AtmosPipeAppearanceSystem : EntitySystem
public sealed partial class AtmosPipeAppearanceSystem : SharedAtmosPipeAppearanceSystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
@@ -34,9 +34,12 @@ public sealed class AtmosPipeAppearanceSystem : EntitySystem
if (!TryComp<MapGridComponent>(xform.GridUid, out var grid))
return;
var numberOfPipeLayers = GetNumberOfPipeLayers(uid, out var atmosPipeLayers);
// get connected entities
var anyPipeNodes = false;
HashSet<EntityUid> connected = new();
HashSet<(EntityUid, AtmosPipeLayer)> connected = new();
foreach (var node in container.Nodes.Values)
{
if (node is not PipeNode)
@@ -46,8 +49,8 @@ public sealed class AtmosPipeAppearanceSystem : EntitySystem
foreach (var connectedNode in node.ReachableNodes)
{
if (connectedNode is PipeNode)
connected.Add(connectedNode.Owner);
if (connectedNode is PipeNode { } pipeNode)
connected.Add((connectedNode.Owner, pipeNode.CurrentPipeLayer));
}
}
@@ -55,14 +58,22 @@ public sealed class AtmosPipeAppearanceSystem : EntitySystem
return;
// find the cardinal directions of any connected entities
var netConnectedDirections = PipeDirection.None;
var tile = _map.TileIndicesFor((xform.GridUid.Value, grid), xform.Coordinates);
foreach (var neighbour in connected)
{
// TODO z-levels, pipes across grids - we shouldn't assume that the neighboring tile's transform is on the same grid
var otherTile = _map.TileIndicesFor((xform.GridUid.Value, grid), Transform(neighbour).Coordinates);
var connectedDirections = new PipeDirection[numberOfPipeLayers];
Array.Fill(connectedDirections, PipeDirection.None);
netConnectedDirections |= (otherTile - tile) switch
var tile = _map.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates);
foreach (var (neighbour, pipeLayer) in connected)
{
var pipeIndex = (int)pipeLayer;
if (pipeIndex >= numberOfPipeLayers)
continue;
var otherTile = _map.TileIndicesFor(xform.GridUid.Value, grid, Transform(neighbour).Coordinates);
var pipeLayerDirections = connectedDirections[pipeIndex];
pipeLayerDirections |= (otherTile - tile) switch
{
(0, 1) => PipeDirection.North,
(0, -1) => PipeDirection.South,
@@ -70,8 +81,16 @@ public sealed class AtmosPipeAppearanceSystem : EntitySystem
(-1, 0) => PipeDirection.West,
_ => PipeDirection.None
};
connectedDirections[pipeIndex] = pipeLayerDirections;
}
// Convert the pipe direction array into a single int for serialization
var netConnectedDirections = 0;
for (var i = numberOfPipeLayers - 1; i >= 0; i--)
netConnectedDirections += (int)connectedDirections[i] << (PipeDirectionHelpers.PipeDirections * i);
_appearance.SetData(uid, PipeVisuals.VisualState, netConnectedDirections, appearance);
}
}

View File

@@ -0,0 +1,68 @@
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Piping.Components;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Shared.Atmos;
using Content.Shared.NodeContainer;
using System.Linq;
namespace Content.Server.Atmos.Piping.EntitySystems;
public sealed partial class GasPipeManifoldSystem : EntitySystem
{
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasPipeManifoldComponent, ComponentInit>(OnCompInit);
SubscribeLocalEvent<GasPipeManifoldComponent, GasAnalyzerScanEvent>(OnAnalyzed);
}
private void OnCompInit(Entity<GasPipeManifoldComponent> ent, ref ComponentInit args)
{
if (!TryComp<NodeContainerComponent>(ent, out var nodeContainer))
return;
foreach (var inletName in ent.Comp.InletNames)
{
if (!_nodeContainer.TryGetNode(nodeContainer, inletName, out PipeNode? inlet))
continue;
foreach (var outletName in ent.Comp.OutletNames)
{
if (!_nodeContainer.TryGetNode(nodeContainer, outletName, out PipeNode? outlet))
continue;
inlet.AddAlwaysReachable(outlet);
outlet.AddAlwaysReachable(inlet);
}
}
}
private void OnAnalyzed(Entity<GasPipeManifoldComponent> ent, ref GasAnalyzerScanEvent args)
{
// All inlets and outlets have the same gas mixture
args.GasMixtures = new List<(string, GasMixture?)>();
if (!TryComp<NodeContainerComponent>(ent, out var nodeContainer))
return;
var pipeNames = ent.Comp.InletNames.Union(ent.Comp.OutletNames);
foreach (var pipeName in pipeNames)
{
if (!_nodeContainer.TryGetNode(nodeContainer, pipeName, out PipeNode? pipe))
continue;
var pipeLocal = pipe.Air.Clone();
pipeLocal.Multiply(pipe.Volume / pipe.Air.Volume);
pipeLocal.Volume = pipe.Volume;
args.GasMixtures.Add((Name(ent), pipeLocal));
break;
}
}
}

View File

@@ -1,10 +1,8 @@
using Content.Server.Atmos;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.NodeGroups;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.NodeContainer;
using Content.Shared.NodeContainer.NodeGroups;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Utility;
@@ -12,7 +10,7 @@ namespace Content.Server.NodeContainer.Nodes
{
/// <summary>
/// Connects with other <see cref="PipeNode"/>s whose <see cref="PipeDirection"/>
/// correctly correspond.
/// and <see cref="CurrentPipeLayer"/> correctly correspond.
/// </summary>
[DataDefinition]
[Virtual]
@@ -24,6 +22,12 @@ namespace Content.Server.NodeContainer.Nodes
[DataField("pipeDirection")]
public PipeDirection OriginalPipeDirection;
/// <summary>
/// The *current* layer to which the pipe node is assigned.
/// </summary>
[DataField("pipeLayer")]
public AtmosPipeLayer CurrentPipeLayer = AtmosPipeLayer.Primary;
/// <summary>
/// The *current* pipe directions (accounting for rotation)
/// Used to check if this pipe can connect to another pipe in a given direction.
@@ -204,6 +208,7 @@ namespace Content.Server.NodeContainer.Nodes
foreach (var pipe in PipesInDirection(pos, pipeDir, grid, nodeQuery))
{
if (pipe.NodeGroupID == NodeGroupID
&& pipe.CurrentPipeLayer == CurrentPipeLayer
&& pipe.CurrentPipeDirection.HasDirection(pipeDir.GetOpposite()))
{
yield return pipe;

View File

@@ -0,0 +1,122 @@
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.DoAfter;
using Content.Shared.Tools;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared.Atmos.Components;
/// <summary>
/// Contains layer data for atmos pipes. Layers allow multiple atmos pipes with the
/// same orientation to be anchored to the same tile without their contents mixing.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SharedAtmosPipeLayersSystem))]
public sealed partial class AtmosPipeLayersComponent : Component
{
/// <summary>
/// The number of pipe layers this entity supports.
/// Must be equal to or less than the number of values
/// in <see cref="AtmosPipeLayer"/>.
/// </summary>
[DataField]
public byte NumberOfPipeLayers = 3;
/// <summary>
/// Determines which layer the pipe is currently assigned.
/// Only pipes on the same layer can connect with each other.
/// </summary>
[DataField("pipeLayer"), AutoNetworkedField]
[ViewVariables(VVAccess.ReadOnly)]
public AtmosPipeLayer CurrentPipeLayer = AtmosPipeLayer.Primary;
/// <summary>
/// The RSI paths that the entity will use to update its sprite when its pipe layer changes;
/// if empty, the entity sprite will not update when it pipe layer changes.
/// If you want to set specific sprite layers to update when the pipe layer changes, use
/// <see cref="SpriteLayersRsiPaths"/> instead.
/// </summary>
/// <remarks>
/// If the dictionary is not empty there should be an entry for each atmos pipe layer.
/// </remarks>
[DataField]
public Dictionary<AtmosPipeLayer, string> SpriteRsiPaths = new();
/// <summary>
/// Used to update specific sprite layers when the entity's pipe layer changes.
/// The dictionary key is the name of the sprite layer to be updated, and its value is
/// a second dictionary which contains the RSI paths indexed by pipe layer.
/// If you want to change the default RSI path used by the entity, use
/// <see cref="SpriteRsiPaths"/> instead.
/// </summary>
/// <remarks>
/// If an dictionary is not empty there should be an entry for each pipe layer.
/// </remarks>
[DataField]
public Dictionary<string, Dictionary<AtmosPipeLayer, string>> SpriteLayersRsiPaths = new();
/// <summary>
/// Entity prototypes that will be used to replace the current one when using
/// position dependent entity placement via AlignAtmosPipeLayers.
/// </summary>
/// <remarks>
/// If the dictionary is not empty there should be an entry for each atmos pipe layer.
/// </remarks>
[DataField]
public Dictionary<AtmosPipeLayer, EntProtoId> AlternativePrototypes = new();
/// <summary>
/// The pipe layers of this entity cannot be changed when this value is true.
/// </summary>
[DataField]
public bool PipeLayersLocked;
/// <summary>
/// Tool quality required to cause a pipe to change layers
/// </summary>
[DataField]
public ProtoId<ToolQualityPrototype> Tool = "Screwing";
/// <summary>
/// The base delay to use for changing layers.
/// </summary>
[DataField]
public float Delay = 1f;
}
/// <summary>
/// Raised when a player attempts to cycle a pipe to its next layer
/// </summary>
[Serializable, NetSerializable]
public sealed partial class TrySetNextPipeLayerCompletedEvent : SimpleDoAfterEvent;
/// <summary>
/// Raised when a player attempts to set a pipe a specified layer
/// </summary>
[Serializable, NetSerializable]
public sealed partial class TrySettingPipeLayerCompletedEvent : SimpleDoAfterEvent
{
public AtmosPipeLayer PipeLayer;
public TrySettingPipeLayerCompletedEvent(AtmosPipeLayer pipeLayer)
{
PipeLayer = pipeLayer;
}
}
[Serializable, NetSerializable]
public enum AtmosPipeLayerVisuals
{
Sprite,
SpriteLayers,
DrawDepth,
}
[Serializable, NetSerializable]
public enum AtmosPipeLayer
{
Primary,
Secondary,
Tertiary,
}

View File

@@ -5,6 +5,8 @@ namespace Content.Shared.Atmos.Components;
[RegisterComponent]
public sealed partial class PipeAppearanceComponent : Component
{
[DataField("sprite")]
public SpriteSpecifier.Rsi Sprite = new(new("Structures/Piping/Atmospherics/pipe.rsi"), "pipeConnector");
[DataField]
public SpriteSpecifier.Rsi[] Sprite = [new(new("Structures/Piping/Atmospherics/pipe.rsi"), "pipeConnector"),
new(new("Structures/Piping/Atmospherics/pipe_alt1.rsi"), "pipeConnector"),
new(new("Structures/Piping/Atmospherics/pipe_alt2.rsi"), "pipeConnector")];
}

View File

@@ -64,10 +64,10 @@ public struct AtmosPipeChunk(Vector2i origin)
/// <summary>
/// Bitmask look up for atmos pipes, 1 for occupied and 0 for empty.
/// Indexed by the color hexcode of the pipe
/// Indexed by the net ID, layer and color hexcode of the pipe
/// </summary>
[ViewVariables]
public Dictionary<(int, string), ulong> AtmosPipeData = new();
public Dictionary<AtmosMonitoringConsoleSubnet, ulong> AtmosPipeData = new();
/// <summary>
/// The last game tick that the chunk was updated
@@ -109,10 +109,21 @@ public struct AtmosDeviceNavMapData
/// </summary>
public Color PipeColor;
/// <summary>
/// The pipe layer the entity is on
/// </summary>
public AtmosPipeLayer PipeLayer;
/// <summary>
/// Populate the atmos monitoring console nav map with a single entity
/// </summary>
public AtmosDeviceNavMapData(NetEntity netEntity, NetCoordinates netCoordinates, int netId, ProtoId<NavMapBlipPrototype> navMapBlip, Direction direction, Color pipeColor)
public AtmosDeviceNavMapData(NetEntity netEntity,
NetCoordinates netCoordinates,
int netId,
ProtoId<NavMapBlipPrototype> navMapBlip,
Direction direction,
Color pipeColor,
AtmosPipeLayer pipeLayer)
{
NetEntity = netEntity;
NetCoordinates = netCoordinates;
@@ -120,6 +131,7 @@ public struct AtmosDeviceNavMapData
NavMapBlip = navMapBlip;
Direction = direction;
PipeColor = pipeColor;
PipeLayer = pipeLayer;
}
}
@@ -216,6 +228,16 @@ public struct AtmosMonitoringConsoleEntry
}
}
/// <summary>
/// Used to group atmos pipe chunks into subnets based on their properties and
/// improve the efficiency of rendering these chunks on the atmos monitoring console.
/// </summary>
/// <param name="NetId">The associated network ID.</param>
/// <param name="PipeLayer">The associated pipe layer.</param>
/// <param name="HexCode">The color of the pipe.</param>
[Serializable, NetSerializable]
public record AtmosMonitoringConsoleSubnet(int NetId, AtmosPipeLayer PipeLayer, string HexCode);
public enum AtmosPipeChunkDataFacing : byte
{
// Values represent bit shift offsets when retrieving data in the tile array.

View File

@@ -16,6 +16,14 @@ public sealed partial class AtmosMonitoringConsoleDeviceComponent : Component
/// entity on the atmos monitoring console nav map.
/// If null, no blip is drawn (i.e., null for pipes)
/// </summary>
[DataField, ViewVariables]
[DataField]
public ProtoId<NavMapBlipPrototype>? NavMapBlip = null;
/// <summary>
/// Sets whether attached atmos pipes will always be rendered
/// on the atmos monitoring console nav map, even if these
/// pipes are not connected to any pipes in a neighboring tile.
/// </summary>
[DataField]
public bool ShowAbsentConnections = true;
}

View File

@@ -15,7 +15,7 @@ public abstract class SharedAtmosMonitoringConsoleSystem : EntitySystem
private void OnGetState(EntityUid uid, AtmosMonitoringConsoleComponent component, ref ComponentGetState args)
{
Dictionary<Vector2i, Dictionary<(int, string), ulong>> chunks;
Dictionary<Vector2i, Dictionary<AtmosMonitoringConsoleSubnet, ulong>> chunks;
// Should this be a full component state or a delta-state?
if (args.FromTick <= component.CreationTick || component.ForceFullUpdate)
@@ -52,22 +52,22 @@ public abstract class SharedAtmosMonitoringConsoleSystem : EntitySystem
[Serializable, NetSerializable]
protected sealed class AtmosMonitoringConsoleState(
Dictionary<Vector2i, Dictionary<(int, string), ulong>> chunks,
Dictionary<Vector2i, Dictionary<AtmosMonitoringConsoleSubnet, ulong>> chunks,
Dictionary<NetEntity, AtmosDeviceNavMapData> atmosDevices)
: ComponentState
{
public Dictionary<Vector2i, Dictionary<(int, string), ulong>> Chunks = chunks;
public Dictionary<Vector2i, Dictionary<AtmosMonitoringConsoleSubnet, ulong>> Chunks = chunks;
public Dictionary<NetEntity, AtmosDeviceNavMapData> AtmosDevices = atmosDevices;
}
[Serializable, NetSerializable]
protected sealed class AtmosMonitoringConsoleDeltaState(
Dictionary<Vector2i, Dictionary<(int, string), ulong>> modifiedChunks,
Dictionary<Vector2i, Dictionary<AtmosMonitoringConsoleSubnet, ulong>> modifiedChunks,
Dictionary<NetEntity, AtmosDeviceNavMapData> atmosDevices,
HashSet<Vector2i> allChunks)
: ComponentState, IComponentDeltaState<AtmosMonitoringConsoleState>
{
public Dictionary<Vector2i, Dictionary<(int, string), ulong>> ModifiedChunks = modifiedChunks;
public Dictionary<Vector2i, Dictionary<AtmosMonitoringConsoleSubnet, ulong>> ModifiedChunks = modifiedChunks;
public Dictionary<NetEntity, AtmosDeviceNavMapData> AtmosDevices = atmosDevices;
public HashSet<Vector2i> AllChunks = allChunks;
@@ -81,7 +81,7 @@ public abstract class SharedAtmosMonitoringConsoleSystem : EntitySystem
foreach (var (index, data) in ModifiedChunks)
{
state.Chunks[index] = new Dictionary<(int, string), ulong>(data);
state.Chunks[index] = new Dictionary<AtmosMonitoringConsoleSubnet, ulong>(data);
}
state.AtmosDevices.Clear();
@@ -93,7 +93,7 @@ public abstract class SharedAtmosMonitoringConsoleSystem : EntitySystem
public AtmosMonitoringConsoleState CreateNewFullState(AtmosMonitoringConsoleState state)
{
var chunks = new Dictionary<Vector2i, Dictionary<(int, string), ulong>>(state.Chunks.Count);
var chunks = new Dictionary<Vector2i, Dictionary<AtmosMonitoringConsoleSubnet, ulong>>(state.Chunks.Count);
foreach (var (index, data) in state.Chunks)
{
@@ -101,10 +101,10 @@ public abstract class SharedAtmosMonitoringConsoleSystem : EntitySystem
continue;
if (ModifiedChunks.ContainsKey(index))
chunks[index] = new Dictionary<(int, string), ulong>(ModifiedChunks[index]);
chunks[index] = new Dictionary<AtmosMonitoringConsoleSubnet, ulong>(ModifiedChunks[index]);
else
chunks[index] = new Dictionary<(int, string), ulong>(state.Chunks[index]);
chunks[index] = new Dictionary<AtmosMonitoringConsoleSubnet, ulong>(state.Chunks[index]);
}
return new AtmosMonitoringConsoleState(chunks, new(AtmosDevices));

View File

@@ -0,0 +1,18 @@
using Content.Shared.Atmos.Components;
namespace Content.Shared.Atmos.EntitySystems;
public abstract partial class SharedAtmosPipeAppearanceSystem : EntitySystem
{
/// <summary>
/// Returns the max number of pipe layers supported by a entity.
/// </summary>
/// <param name="uid">The entity being checked.</param>
/// <param name="atmosPipeLayers">The entity's <see cref="AtmosPipeLayersComponent"/>, if available.</param>
/// <returns>Returns <see cref="AtmosPipeLayersComponent.NumberOfPipeLayers"/>
/// if the entity has the component, or 1 if it does not.</returns>
protected int GetNumberOfPipeLayers(EntityUid uid, out AtmosPipeLayersComponent? atmosPipeLayers)
{
return TryComp(uid, out atmosPipeLayers) ? atmosPipeLayers.NumberOfPipeLayers : 1;
}
}

View File

@@ -0,0 +1,264 @@
using Content.Shared.Atmos.Components;
using Content.Shared.Database;
using Content.Shared.Examine;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Popups;
using Content.Shared.SubFloor;
using Content.Shared.Tools;
using Content.Shared.Tools.Components;
using Content.Shared.Tools.Systems;
using Content.Shared.Verbs;
using Robust.Shared.Prototypes;
using System.Diagnostics.CodeAnalysis;
namespace Content.Shared.Atmos.EntitySystems;
/// <summary>
/// The system responsible for checking and adjusting the connection layering of gas pipes
/// </summary>
public abstract partial class SharedAtmosPipeLayersSystem : EntitySystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly SharedToolSystem _tool = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AtmosPipeLayersComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<AtmosPipeLayersComponent, GetVerbsEvent<Verb>>(OnGetVerb);
SubscribeLocalEvent<AtmosPipeLayersComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<AtmosPipeLayersComponent, UseInHandEvent>(OnUseInHandEvent);
SubscribeLocalEvent<AtmosPipeLayersComponent, TrySetNextPipeLayerCompletedEvent>(OnSetNextPipeLayerCompleted);
SubscribeLocalEvent<AtmosPipeLayersComponent, TrySettingPipeLayerCompletedEvent>(OnSettingPipeLayerCompleted);
}
private void OnExamined(Entity<AtmosPipeLayersComponent> ent, ref ExaminedEvent args)
{
var layerName = GetPipeLayerName(ent.Comp.CurrentPipeLayer);
args.PushMarkup(Loc.GetString("atmos-pipe-layers-component-current-layer", ("layerName", layerName)));
}
private void OnGetVerb(Entity<AtmosPipeLayersComponent> ent, ref GetVerbsEvent<Verb> args)
{
if (!args.CanAccess || !args.CanInteract || !args.CanComplexInteract)
return;
if (ent.Comp.NumberOfPipeLayers <= 1 || ent.Comp.PipeLayersLocked)
return;
if (!_protoManager.TryIndex(ent.Comp.Tool, out var toolProto))
return;
var user = args.User;
if (TryComp<SubFloorHideComponent>(ent, out var subFloorHide) && subFloorHide.IsUnderCover)
{
var v = new Verb
{
Priority = 1,
Category = VerbCategory.Adjust,
Text = Loc.GetString("atmos-pipe-layers-component-pipes-are-covered"),
Disabled = true,
Impact = LogImpact.Low,
DoContactInteraction = true,
};
args.Verbs.Add(v);
}
else if (!TryGetHeldTool(user, ent.Comp.Tool, out var tool))
{
var v = new Verb
{
Priority = 1,
Category = VerbCategory.Adjust,
Text = Loc.GetString("atmos-pipe-layers-component-tool-missing", ("toolName", Loc.GetString(toolProto.ToolName).ToLower())),
Disabled = true,
Impact = LogImpact.Low,
DoContactInteraction = true,
};
args.Verbs.Add(v);
}
else
{
for (var i = 0; i < ent.Comp.NumberOfPipeLayers; i++)
{
var index = i;
var layerName = GetPipeLayerName((AtmosPipeLayer)index);
var label = Loc.GetString("atmos-pipe-layers-component-select-layer", ("layerName", layerName));
var v = new Verb
{
Priority = 1,
Category = VerbCategory.Adjust,
Text = label,
Disabled = index == (int)ent.Comp.CurrentPipeLayer,
Impact = LogImpact.Low,
DoContactInteraction = true,
Act = () =>
{
_tool.UseTool(tool.Value, user, ent, ent.Comp.Delay, tool.Value.Comp.Qualities, new TrySettingPipeLayerCompletedEvent((AtmosPipeLayer)index));
}
};
args.Verbs.Add(v);
}
}
}
private void OnInteractUsing(Entity<AtmosPipeLayersComponent> ent, ref InteractUsingEvent args)
{
if (ent.Comp.NumberOfPipeLayers <= 1 || ent.Comp.PipeLayersLocked)
return;
if (TryComp<SubFloorHideComponent>(ent, out var subFloorHide) && subFloorHide.IsUnderCover)
{
_popup.PopupPredicted(Loc.GetString("atmos-pipe-layers-component-cannot-adjust-pipes"), ent, args.User);
return;
}
if (TryComp<ToolComponent>(args.Used, out var tool) && _tool.HasQuality(args.Used, ent.Comp.Tool, tool))
_tool.UseTool(args.Used, args.User, ent, ent.Comp.Delay, tool.Qualities, new TrySetNextPipeLayerCompletedEvent());
}
private void OnUseInHandEvent(Entity<AtmosPipeLayersComponent> ent, ref UseInHandEvent args)
{
if (ent.Comp.NumberOfPipeLayers <= 1 || ent.Comp.PipeLayersLocked)
return;
if (!TryGetHeldTool(args.User, ent.Comp.Tool, out var tool))
{
if (_protoManager.TryIndex(ent.Comp.Tool, out var toolProto))
{
var toolName = Loc.GetString(toolProto.ToolName).ToLower();
var message = Loc.GetString("atmos-pipe-layers-component-tool-missing", ("toolName", toolName));
_popup.PopupPredicted(message, ent, args.User);
}
return;
}
_tool.UseTool(tool.Value, args.User, ent, ent.Comp.Delay, tool.Value.Comp.Qualities, new TrySetNextPipeLayerCompletedEvent());
}
private void OnSetNextPipeLayerCompleted(Entity<AtmosPipeLayersComponent> ent, ref TrySetNextPipeLayerCompletedEvent args)
{
if (args.Cancelled)
return;
SetNextPipeLayer(ent, args.User, args.Used);
}
private void OnSettingPipeLayerCompleted(Entity<AtmosPipeLayersComponent> ent, ref TrySettingPipeLayerCompletedEvent args)
{
if (args.Cancelled)
return;
SetPipeLayer(ent, args.PipeLayer, args.User, args.Used);
}
/// <summary>
/// Increments an entity's pipe layer by 1, wrapping around to 0 if the max pipe layer is reached
/// </summary>
/// <param name="ent">The pipe entity</param>
/// <param name="user">The player entity who adjusting the pipe layer</param>
/// <param name="used">The tool used to adjust the pipe layer</param>
public void SetNextPipeLayer(Entity<AtmosPipeLayersComponent> ent, EntityUid? user = null, EntityUid? used = null)
{
var newLayer = ((int)ent.Comp.CurrentPipeLayer + 1) % ent.Comp.NumberOfPipeLayers;
SetPipeLayer(ent, (AtmosPipeLayer)newLayer, user, used);
}
/// <summary>
/// Sets an entity's pipe layer to a specified value
/// </summary>
/// <param name="ent">The pipe entity</param>
/// <param name="layer">The new layer value</param>
/// <param name="user">The player entity who adjusting the pipe layer</param>
/// <param name="used">The tool used to adjust the pipe layer</param>
public virtual void SetPipeLayer(Entity<AtmosPipeLayersComponent> ent, AtmosPipeLayer layer, EntityUid? user = null, EntityUid? used = null)
{
if (ent.Comp.PipeLayersLocked)
return;
ent.Comp.CurrentPipeLayer = (AtmosPipeLayer)Math.Clamp((int)layer, 0, ent.Comp.NumberOfPipeLayers - 1);
Dirty(ent);
if (TryComp<AppearanceComponent>(ent, out var appearance))
{
if (ent.Comp.SpriteRsiPaths.TryGetValue(ent.Comp.CurrentPipeLayer, out var path))
_appearance.SetData(ent, AtmosPipeLayerVisuals.Sprite, path, appearance);
if (ent.Comp.SpriteLayersRsiPaths.Count > 0)
{
var data = new Dictionary<string, string>();
foreach (var (layerKey, rsiPaths) in ent.Comp.SpriteLayersRsiPaths)
{
if (rsiPaths.TryGetValue(ent.Comp.CurrentPipeLayer, out path))
data.TryAdd(layerKey, path);
}
_appearance.SetData(ent, AtmosPipeLayerVisuals.SpriteLayers, data, appearance);
}
}
if (user != null)
{
var layerName = GetPipeLayerName(ent.Comp.CurrentPipeLayer);
var message = Loc.GetString("atmos-pipe-layers-component-change-layer", ("layerName", layerName));
_popup.PopupPredicted(message, ent, user);
}
}
/// <summary>
/// Try to find an entity prototype associated with a specified <see cref="AtmosPipeLayer"/>.
/// </summary>
/// <param name="component">The <see cref="AtmosPipeLayersComponent"/> with the alternative prototypes data.</param>
/// <param name="layer">The atmos pipe layer associated with the entity prototype.</param>
/// <param name="proto">The returned entity prototype.</param>
/// <returns>True if there was an entity prototype associated with the layer.</returns>
public bool TryGetAlternativePrototype(AtmosPipeLayersComponent component, AtmosPipeLayer layer, out EntProtoId proto)
{
return component.AlternativePrototypes.TryGetValue(layer, out proto);
}
/// <summary>
/// Checks a player entity's hands to see if they are holding a tool with a specified quality
/// </summary>
/// <param name="user">The player entity</param>
/// <param name="toolQuality">The tool quality being checked for</param>
/// <param name="heldTool">A tool with the specified tool quality</param>
/// <returns>True if an appropriate tool was found</returns>
private bool TryGetHeldTool(EntityUid user, ProtoId<ToolQualityPrototype> toolQuality, [NotNullWhen(true)] out Entity<ToolComponent>? heldTool)
{
heldTool = null;
foreach (var heldItem in _hands.EnumerateHeld(user))
{
if (TryComp<ToolComponent>(heldItem, out var tool) &&
_tool.HasQuality(heldItem, toolQuality, tool))
{
heldTool = new Entity<ToolComponent>(heldItem, tool);
return true;
}
}
return false;
}
private string GetPipeLayerName(AtmosPipeLayer layer)
{
return Loc.GetString("atmos-pipe-layers-component-layer-" + layer.ToString().ToLower());
}
}

View File

@@ -85,6 +85,12 @@ public sealed partial class ConstructionPrototype : IPrototype
[DataField]
public ProtoId<ConstructionPrototype>? Mirror { get; private set; }
/// <summary>
/// Possible constructions to replace this one with as determined by the placement mode
/// </summary>
[DataField]
public ProtoId<ConstructionPrototype>[] AlternativePrototypes = [];
public IReadOnlyList<IConstructionCondition> Conditions => _conditions;
}

View File

@@ -9,13 +9,15 @@ namespace Content.Shared.DrawDepth
/// <summary>
/// This is for sub-floors, the floors you see after prying off a tile.
/// </summary>
LowFloors = DrawDepthTag.Default - 18,
LowFloors = DrawDepthTag.Default - 20,
// various entity types that require different
// draw depths, as to avoid hiding
#region SubfloorEntities
ThickPipe = DrawDepthTag.Default - 17,
ThickWire = DrawDepthTag.Default - 16,
ThickPipe = DrawDepthTag.Default - 19,
ThickWire = DrawDepthTag.Default - 18,
ThinPipeAlt2 = DrawDepthTag.Default - 17,
ThinPipeAlt1 = DrawDepthTag.Default - 16,
ThinPipe = DrawDepthTag.Default - 15,
ThinWire = DrawDepthTag.Default - 14,
#endregion

View File

@@ -39,4 +39,17 @@ public sealed partial class NavMapBlipPrototype : IPrototype
/// </summary>
[DataField]
public float Scale { get; private set; } = 1f;
/// <summary>
/// Describes how the blip should be positioned.
/// It's up to the individual system to enforce this
/// </summary>
[DataField]
public NavMapBlipPlacement Placement { get; private set; } = NavMapBlipPlacement.Centered;
}
public enum NavMapBlipPlacement
{
Centered, // The blip appears in the center of the tile
Offset // The blip is offset from the center of the tile (determined by the system using the blips)
}

View File

@@ -225,6 +225,7 @@ namespace Content.Shared.SubFloor
ScannerRevealed,
}
[Serializable, NetSerializable]
public enum SubfloorLayers : byte
{
FirstLayer

View File

@@ -85,5 +85,8 @@ namespace Content.Shared.Verbs
public static readonly VerbCategory SelectType = new("verb-categories-select-type", null);
public static readonly VerbCategory PowerLevel = new("verb-categories-power-level", null);
public static readonly VerbCategory Adjust =
new("verb-categories-adjust", "/Textures/Interface/VerbIcons/screwdriver.png");
}
}

View File

@@ -0,0 +1,10 @@
atmos-pipe-layers-component-layer-primary = primary
atmos-pipe-layers-component-layer-secondary = secondary
atmos-pipe-layers-component-layer-tertiary = tertiary
atmos-pipe-layers-component-change-layer = Adjusted to its {$layerName} configuration.
atmos-pipe-layers-component-current-layer = It is in its {$layerName} configuration.
atmos-pipe-layers-component-select-layer = {CAPITALIZE($layerName)} configuration
atmos-pipe-layers-component-tool-missing = Requires {INDEFINITE($toolName)} {$toolName}
atmos-pipe-layers-component-pipes-are-covered = The pipes are covered
atmos-pipe-layers-component-cannot-adjust-pipes = You need to uncover the pipes before they can be adjusted.

View File

@@ -28,6 +28,7 @@ verb-categories-lever = Lever
verb-categories-select-type = Select Type
verb-categories-fax = Set Destination
verb-categories-power-level = Power Level
verb-categories-adjust = Adjust
verb-common-toggle-light = Toggle light
verb-common-close = Close

View File

@@ -13,17 +13,18 @@
color: "#ffcd00"
texturePaths:
- "/Textures/Interface/NavMap/beveled_star.png"
placement: Offset
- type: navMapBlip
id: GasVentOpening
scale: 0.6667
scale: 0.75
color: LightGray
texturePaths:
- "/Textures/Interface/NavMap/beveled_square.png"
- type: navMapBlip
id: GasVentScrubber
scale: 0.6667
scale: 0.75
color: LightGray
texturePaths:
- "/Textures/Interface/NavMap/beveled_circle.png"
@@ -37,6 +38,7 @@
- "/Textures/Interface/NavMap/beveled_arrow_east.png"
- "/Textures/Interface/NavMap/beveled_arrow_north.png"
- "/Textures/Interface/NavMap/beveled_arrow_west.png"
placement: Offset
- type: navMapBlip
id: GasValve
@@ -47,10 +49,21 @@
- "/Textures/Interface/NavMap/beveled_diamond_east_west.png"
- "/Textures/Interface/NavMap/beveled_diamond_north_south.png"
- "/Textures/Interface/NavMap/beveled_diamond_east_west.png"
placement: Offset
- type: navMapBlip
id: Thermoregulator
scale: 0.6667
scale: 0.75
color: LightGray
texturePaths:
- "/Textures/Interface/NavMap/beveled_hexagon.png"
- type: navMapBlip
id: GasPipeManifold
scale: 0.9
color: LightGray
texturePaths:
- "/Textures/Interface/NavMap/beveled_rectangle_east_west.png"
- "/Textures/Interface/NavMap/beveled_rectangle_north_south.png"
- "/Textures/Interface/NavMap/beveled_rectangle_east_west.png"
- "/Textures/Interface/NavMap/beveled_rectangle_north_south.png"

View File

@@ -0,0 +1,133 @@
## This file contains duplicated pipe prototypes with
## different layer offsets to faciliate mapping
# Layer 1
- type: entity
abstract: true
id: GasPipeLayerAlt1
components:
- type: AtmosPipeLayers
pipeLayer: 1
# Layer 2
- type: entity
abstract: true
id: GasPipeLayerAlt2
components:
- type: AtmosPipeLayers
pipeLayer: 2
# GasPipeStraight
- type: entity
parent: [GasPipeLayerAlt1, GasPipeStraight]
id: GasPipeStraightAlt1
categories: [ HideSpawnMenu ]
components:
- type: Sprite
drawdepth: ThinPipeAlt1
sprite: Structures/Piping/Atmospherics/pipe_alt1.rsi
- type: Construction
node: straightAlt1
- type: entity
parent: [GasPipeLayerAlt2, GasPipeStraight]
id: GasPipeStraightAlt2
categories: [ HideSpawnMenu ]
components:
- type: Sprite
drawdepth: ThinPipeAlt2
sprite: Structures/Piping/Atmospherics/pipe_alt2.rsi
- type: Construction
node: straightAlt2
# GasPipeHalf
- type: entity
parent: [GasPipeLayerAlt1, GasPipeHalf]
id: GasPipeHalfAlt1
categories: [ HideSpawnMenu ]
components:
- type: Sprite
drawdepth: ThinPipeAlt1
sprite: Structures/Piping/Atmospherics/pipe_alt1.rsi
- type: Construction
node: halfAlt1
- type: entity
parent: [GasPipeLayerAlt2, GasPipeHalf]
id: GasPipeHalfAlt2
categories: [ HideSpawnMenu ]
components:
- type: Sprite
drawdepth: ThinPipeAlt2
sprite: Structures/Piping/Atmospherics/pipe_alt2.rsi
- type: Construction
node: halfAlt2
# GasPipeBend
- type: entity
parent: [GasPipeLayerAlt1, GasPipeBend]
id: GasPipeBendAlt1
categories: [ HideSpawnMenu ]
components:
- type: Sprite
drawdepth: ThinPipeAlt1
sprite: Structures/Piping/Atmospherics/pipe_alt1.rsi
- type: Construction
node: bendAlt1
- type: entity
parent: [GasPipeLayerAlt2, GasPipeBend]
id: GasPipeBendAlt2
categories: [ HideSpawnMenu ]
components:
- type: Sprite
drawdepth: ThinPipeAlt2
sprite: Structures/Piping/Atmospherics/pipe_alt2.rsi
- type: Construction
node: bendAlt2
# GasPipeTJunction
- type: entity
parent: [GasPipeLayerAlt1, GasPipeTJunction]
id: GasPipeTJunctionAlt1
categories: [ HideSpawnMenu ]
components:
- type: Sprite
drawdepth: ThinPipeAlt1
sprite: Structures/Piping/Atmospherics/pipe_alt1.rsi
- type: Construction
node: tjunctionAlt1
- type: entity
parent: [GasPipeLayerAlt2, GasPipeTJunction]
id: GasPipeTJunctionAlt2
categories: [ HideSpawnMenu ]
components:
- type: Sprite
drawdepth: ThinPipeAlt2
sprite: Structures/Piping/Atmospherics/pipe_alt2.rsi
- type: Construction
node: tjunctionAlt2
# GasPipeFourway
- type: entity
parent: [GasPipeLayerAlt1, GasPipeFourway]
id: GasPipeFourwayAlt1
categories: [ HideSpawnMenu ]
components:
- type: Sprite
drawdepth: ThinPipeAlt1
sprite: Structures/Piping/Atmospherics/pipe_alt1.rsi
- type: Construction
node: fourwayAlt1
- type: entity
parent: [GasPipeLayerAlt2, GasPipeFourway]
id: GasPipeFourwayAlt2
categories: [ HideSpawnMenu ]
components:
- type: Sprite
drawdepth: ThinPipeAlt2
sprite: Structures/Piping/Atmospherics/pipe_alt2.rsi
- type: Construction
node: fourwayAlt2

View File

@@ -22,6 +22,11 @@
!type:PipeNode
nodeGroupID: Pipe
pipeDirection: South
- type: AtmosPipeLayers
spriteRsiPaths:
Primary: Structures/Piping/Atmospherics/pump.rsi
Secondary: Structures/Piping/Atmospherics/pump_alt1.rsi
Tertiary: Structures/Piping/Atmospherics/pump_alt2.rsi
- type: entity
parent: GasBinaryBase
@@ -325,10 +330,12 @@
sprite: Structures/Piping/Atmospherics/gascanisterport.rsi
layers:
- sprite: Structures/Piping/Atmospherics/pipe.rsi
state: pipeHalf
state: pipeUnaryConnectors
map: [ "enum.PipeVisualLayers.Pipe" ]
- state: gasCanisterPort
map: [ "enum.SubfloorLayers.FirstLayer" ]
- type: AtmosPipeLayers
spriteRsiPaths: {}
- type: Appearance
- type: PipeColorVisuals
- type: GasPort
@@ -363,10 +370,12 @@
sprite: Structures/Piping/Atmospherics/vent.rsi
layers:
- sprite: Structures/Piping/Atmospherics/pipe.rsi
state: pipeStraight
state: pipeBinaryConnectors
map: [ "enum.PipeVisualLayers.Pipe" ]
- state: vent_off
map: [ "enabled", "enum.SubfloorLayers.FirstLayer" ]
- type: AtmosPipeLayers
spriteRsiPaths: {}
- type: GenericVisualizer
visuals:
enum.VentPumpVisuals.State:
@@ -423,18 +432,24 @@
sprite: Structures/Machines/gasrecycler.rsi
layers:
- sprite: Structures/Piping/Atmospherics/pipe.rsi
state: pipeStraight
state: pipeBinaryConnectors
map: [ "enum.PipeVisualLayers.Pipe" ]
- state: running
- state: unlit
shader: unshaded
- type: AtmosPipeLayers
spriteLayersRsiPaths:
enum.PipeVisualLayers.Pipe:
Primary: Structures/Piping/Atmospherics/pipe.rsi
Secondary: Structures/Piping/Atmospherics/pipe_alt1.rsi
Tertiary: Structures/Piping/Atmospherics/pipe_alt2.rsi
- type: Appearance
- type: GenericVisualizer
visuals:
enum.PumpVisuals.Enabled:
enabled:
True: { state: running }
False: { state: unlit }
- type: Appearance
- type: PipeColorVisuals
- type: Rotatable
- type: GasRecycler
@@ -494,6 +509,9 @@
map: [ "enum.PipeVisualLayers.Pipe" ]
- state: heStraight
map: [ "enum.SubfloorLayers.FirstLayer" ]
- type: AtmosPipeLayers
spriteRsiPaths: {}
numberOfPipeLayers: 1
- type: SubFloorHide
visibleLayers:
- enum.SubfloorLayers.FirstLayer

View File

@@ -1,5 +1,5 @@
- type: entity
parent: [AirSensorBase, GasPipeBase]
parent: [AirSensorBase, GasBinaryBase]
id: GasPipeSensor
name: gas pipe sensor
description: Reports on the status of the gas in the attached pipe network.
@@ -18,6 +18,11 @@
- map: [ "enum.PowerDeviceVisualLayers.Powered" ]
state: lights
shader: unshaded
- type: AtmosPipeLayers
spriteRsiPaths:
Primary: Structures/Piping/Atmospherics/gas_pipe_sensor.rsi
Secondary: Structures/Piping/Atmospherics/gas_pipe_sensor_alt1.rsi
Tertiary: Structures/Piping/Atmospherics/gas_pipe_sensor_alt2.rsi
- type: Appearance
- type: GenericVisualizer
visuals:

View File

@@ -1,7 +1,7 @@
- type: entity
abstract: true
id: GasPipeBase
parent: BaseItem
id: GasPipeSansLayers
name: gas pipe
description: Holds gas.
placement:
@@ -18,7 +18,6 @@
damageModifierSet: Metallic
- type: SubFloorHide
- type: CollideOnAnchor
- type: PipeAppearance
- type: Anchorable
- type: Rotatable
- type: Destructible
@@ -51,6 +50,7 @@
drawdepth: ThinPipe
visible: false
- type: Appearance
- type: PipeAppearance
- type: PipeColorVisuals
- type: NodeContainer
- type: PipeRestrictOverlap
@@ -66,12 +66,26 @@
- type: StaticPrice
price: 30
- type: entity
abstract: true
parent: GasPipeSansLayers
id: GasPipeBase
components:
- type: AtmosPipeLayers
spriteLayersRsiPaths:
enum.PipeVisualLayers.Pipe:
Primary: Structures/Piping/Atmospherics/pipe.rsi
Secondary: Structures/Piping/Atmospherics/pipe_alt1.rsi
Tertiary: Structures/Piping/Atmospherics/pipe_alt2.rsi
#Note: The PipeDirection of the PipeNode should be the south-facing version, because the entity starts at an angle of 0 (south)
- type: entity
parent: GasPipeBase
id: GasPipeHalf
suffix: Half
placement:
mode: AlignAtmosPipeLayers
components:
- type: NodeContainer
nodes:
@@ -83,9 +97,19 @@
layers:
- state: pipeHalf
map: [ "enum.PipeVisualLayers.Pipe" ]
- type: AtmosPipeLayers
alternativePrototypes:
Primary: GasPipeHalf
Secondary: GasPipeHalfAlt1
Tertiary: GasPipeHalfAlt2
- type: Construction
graph: GasPipe
node: half
- type: Item
size: Small
storedSprite:
sprite: Structures/Piping/Atmospherics/pipe.rsi
state: storageHalf
- type: GuideHelp
guides:
- Pipes
@@ -95,6 +119,8 @@
parent: GasPipeBase
id: GasPipeStraight
suffix: Straight
placement:
mode: AlignAtmosPipeLayers
components:
- type: NodeContainer
nodes:
@@ -106,11 +132,15 @@
layers:
- state: pipeStraight
map: [ "enum.PipeVisualLayers.Pipe" ]
- type: AtmosPipeLayers
alternativePrototypes:
Primary: GasPipeStraight
Secondary: GasPipeStraightAlt1
Tertiary: GasPipeStraightAlt2
- type: Construction
graph: GasPipe
node: straight
- type: Item
size: Normal
storedSprite:
sprite: Structures/Piping/Atmospherics/pipe.rsi
state: storageStraight
@@ -132,6 +162,8 @@
parent: GasPipeBase
id: GasPipeBend
suffix: Bend
placement:
mode: AlignAtmosPipeLayers
components:
- type: NodeContainer
nodes:
@@ -143,11 +175,15 @@
layers:
- state: pipeBend
map: [ "enum.PipeVisualLayers.Pipe" ]
- type: AtmosPipeLayers
alternativePrototypes:
Primary: GasPipeBend
Secondary: GasPipeBendAlt1
Tertiary: GasPipeBendAlt2
- type: Construction
graph: GasPipe
node: bend
- type: Item
size: Small
shape:
- 0,0,1,0
- 1,1,1,1
@@ -175,6 +211,8 @@
- type: entity
parent: GasPipeBase
id: GasPipeTJunction
placement:
mode: AlignAtmosPipeLayers
suffix: TJunction
components:
- type: NodeContainer
@@ -187,14 +225,15 @@
layers:
- state: pipeTJunction
map: [ "enum.PipeVisualLayers.Pipe" ]
- type: AtmosPipeLayers
alternativePrototypes:
Primary: GasPipeTJunction
Secondary: GasPipeTJunctionAlt1
Tertiary: GasPipeTJunctionAlt2
- type: Construction
graph: GasPipe
node: tjunction
- type: Item
size: Normal
shape:
- 0,0,2,0
- 1,1,1,1
heldPrefix: TJunction
storedSprite:
sprite: Structures/Piping/Atmospherics/pipe.rsi
@@ -216,6 +255,8 @@
parent: GasPipeBase
id: GasPipeFourway
suffix: Fourway
placement:
mode: AlignAtmosPipeLayers
components:
- type: Transform
noRot: true
@@ -229,15 +270,19 @@
layers:
- state: pipeFourway
map: [ "enum.PipeVisualLayers.Pipe" ]
- type: AtmosPipeLayers
alternativePrototypes:
Primary: GasPipeFourway
Secondary: GasPipeFourwayAlt1
Tertiary: GasPipeFourwayAlt2
- type: Construction
graph: GasPipe
node: fourway
- type: Item
size: Normal
shape:
- 1,0,1,2
- 0,1,2,1
heldPrefix: Fourway
storedSprite:
sprite: Structures/Piping/Atmospherics/pipe.rsi
state: storageFourway
- type: MeleeWeapon
wideAnimationRotation: 90
attackRate: 0.75
@@ -264,9 +309,13 @@
layers:
- state: pipeBroken
map: [ "enum.PipeVisualLayers.Pipe" ]
- type: AtmosPipeLayers
numberOfPipeLayers: 1
- type: Construction
graph: GasPipe
node: broken
- type: Item
size: Small
- type: Destructible
thresholds: # override parent to avoid recursive destruction
- trigger:
@@ -279,3 +328,74 @@
guides:
- Pipes
- PipeNetworks
- type: entity
parent: GasPipeSansLayers
id: GasPipeManifold
name: gas pipe manifold
description: Allows gas pipes of different configurations to be connected together.
placement:
mode: SnapgridCenter
components:
- type: Sprite
sprite: Structures/Piping/Atmospherics/manifold.rsi
layers:
- state: pipeManifold
map: [ "enum.PipeVisualLayers.Pipe" ]
- type: AtmosPipeLayers
pipeLayersLocked: true
- type: PipeAppearance
sprite:
- { sprite: Structures/Piping/Atmospherics/manifold.rsi, state: pipeConnector }
- { sprite: Structures/Piping/Atmospherics/manifold.rsi, state: pipeConnector_alt1 }
- { sprite: Structures/Piping/Atmospherics/manifold.rsi, state: pipeConnector_alt2 }
- type: Construction
graph: GasPipe
node: manifold
- type: Item
size: Small
shape:
- 0,0,1,0
storedSprite:
sprite: Structures/Piping/Atmospherics/manifold.rsi
state: storageManifold
- type: NodeContainer
nodes:
south0:
!type:PipeNode
nodeGroupID: Pipe
pipeDirection: South
pipeLayer: 0
south1:
!type:PipeNode
nodeGroupID: Pipe
pipeDirection: South
pipeLayer: 1
south2:
!type:PipeNode
nodeGroupID: Pipe
pipeDirection: South
pipeLayer: 2
north0:
!type:PipeNode
nodeGroupID: Pipe
pipeDirection: North
pipeLayer: 0
north1:
!type:PipeNode
nodeGroupID: Pipe
pipeDirection: North
pipeLayer: 1
north2:
!type:PipeNode
nodeGroupID: Pipe
pipeDirection: North
pipeLayer: 2
- type: GasPipeManifold
- type: AtmosMonitoringConsoleDevice
navMapBlip: GasPipeManifold
showAbsentConnections: false
- type: AtmosDevice
- type: Tag
tags:
- Unstackable

View File

@@ -44,7 +44,7 @@
sprite: Structures/Piping/Atmospherics/vent.rsi
layers:
- sprite: Structures/Piping/Atmospherics/pipe.rsi
state: pipeHalf
state: pipeUnaryConnectors
map: [ "enum.PipeVisualLayers.Pipe" ]
- state: vent_off
map: [ "enabled", "enum.SubfloorLayers.FirstLayer" ]
@@ -94,7 +94,7 @@
sprite: Structures/Piping/Atmospherics/vent.rsi
layers:
- sprite: Structures/Piping/Atmospherics/pipe.rsi
state: pipeHalf
state: pipeUnaryConnectors
map: [ "enum.PipeVisualLayers.Pipe" ]
- state: vent_passive
map: [ "enum.SubfloorLayers.FirstLayer" ]
@@ -134,7 +134,7 @@
sprite: Structures/Piping/Atmospherics/scrubber.rsi
layers:
- sprite: Structures/Piping/Atmospherics/pipe.rsi
state: pipeHalf
state: pipeUnaryConnectors
map: [ "enum.PipeVisualLayers.Pipe" ]
- state: scrub_off
map: [ "enabled", "enum.SubfloorLayers.FirstLayer" ]
@@ -180,7 +180,7 @@
drawdepth: FloorObjects
sprite: Structures/Piping/Atmospherics/outletinjector.rsi
layers:
- state: pipeHalf
- state: pipeUnaryConnectors
sprite: Structures/Piping/Atmospherics/pipe.rsi
map: [ "enum.PipeVisualLayers.Pipe" ]
- state: injector
@@ -189,6 +189,7 @@
shader: unshaded
map: [ "enum.LightLayers.Unshaded" ]
color: "#990000"
- type: Appearance
- type: GenericVisualizer
visuals:
# toggle color of the unshaded light:
@@ -196,7 +197,6 @@
enum.LightLayers.Unshaded:
True: { color: "#5eff5e" }
False: { color: "#990000" }
- type: Appearance
- type: PipeColorVisuals
- type: GasOutletInjector
- type: Construction
@@ -228,6 +228,12 @@
- type: Rotatable
- type: GasThermoMachine
- type: AtmosPipeColor
- type: AtmosPipeLayers
spriteLayersRsiPaths:
enum.PipeVisualLayers.Pipe:
Primary: Structures/Piping/Atmospherics/thermomachine.rsi
Secondary: Structures/Piping/Atmospherics/thermomachine_alt1.rsi
Tertiary: Structures/Piping/Atmospherics/thermomachine_alt2.rsi
- type: AtmosDevice
- type: UserInterface
interfaces:
@@ -405,6 +411,7 @@
map: ["enum.SolutionContainerLayers.Fill"]
visible: false
- state: trans
- type: Appearance
- type: GenericVisualizer
visuals:
enum.PowerDeviceVisuals.Powered:
@@ -414,11 +421,16 @@
- type: SolutionContainerVisuals
maxFillLevels: 7
fillBaseName: fill-
- type: Appearance
- type: PipeColorVisuals
- type: Rotatable
- type: GasCondenser
- type: AtmosPipeColor
- type: AtmosPipeLayers
spriteLayersRsiPaths:
enum.PipeVisualLayers.Pipe:
Primary: Structures/Piping/Atmospherics/condenser.rsi
Secondary: Structures/Piping/Atmospherics/condenser_alt1.rsi
Tertiary: Structures/Piping/Atmospherics/condenser_alt2.rsi
- type: AtmosDevice
- type: PipeRestrictOverlap
- type: ApcPowerReceiver
@@ -470,3 +482,4 @@
- type: GuideHelp
guides:
- GasCondensing

View File

@@ -10,30 +10,96 @@
amount: 1
doAfter: 1
- to: halfAlt1
steps:
- material: Steel
amount: 1
doAfter: 1
- to: halfAlt2
steps:
- material: Steel
amount: 1
doAfter: 1
- to: straight
steps:
- material: Steel
amount: 1
doAfter: 1
- to: straightAlt1
steps:
- material: Steel
amount: 1
doAfter: 1
- to: straightAlt2
steps:
- material: Steel
amount: 1
doAfter: 1
- to: bend
steps:
- material: Steel
amount: 1
doAfter: 1
- to: bendAlt1
steps:
- material: Steel
amount: 1
doAfter: 1
- to: bendAlt2
steps:
- material: Steel
amount: 1
doAfter: 1
- to: tjunction
steps:
- material: Steel
amount: 1
doAfter: 1
- to: tjunctionAlt1
steps:
- material: Steel
amount: 1
doAfter: 1
- to: tjunctionAlt2
steps:
- material: Steel
amount: 1
doAfter: 1
- to: fourway
steps:
- material: Steel
amount: 1
doAfter: 1
- to: fourwayAlt1
steps:
- material: Steel
amount: 1
doAfter: 1
- to: fourwayAlt2
steps:
- material: Steel
amount: 1
doAfter: 1
- to: manifold
steps:
- material: Steel
amount: 2
doAfter: 1
- node: half
entity: GasPipeHalf
edges:
@@ -50,6 +116,38 @@
- tool: Welding
doAfter: 1
- node: halfAlt1
entity: GasPipeHalfAlt1
edges:
- to: start
conditions:
- !type:EntityAnchored
anchored: false
completed:
- !type:SpawnPrototype
prototype: SheetSteel1
amount: 1
- !type:DeleteEntity
steps:
- tool: Welding
doAfter: 1
- node: halfAlt2
entity: GasPipeHalfAlt2
edges:
- to: start
conditions:
- !type:EntityAnchored
anchored: false
completed:
- !type:SpawnPrototype
prototype: SheetSteel1
amount: 1
- !type:DeleteEntity
steps:
- tool: Welding
doAfter: 1
- node: straight
entity: GasPipeStraight
edges:
@@ -66,6 +164,38 @@
- tool: Welding
doAfter: 1
- node: straightAlt1
entity: GasPipeStraightAlt1
edges:
- to: start
conditions:
- !type:EntityAnchored
anchored: false
completed:
- !type:SpawnPrototype
prototype: SheetSteel1
amount: 1
- !type:DeleteEntity
steps:
- tool: Welding
doAfter: 1
- node: straightAlt2
entity: GasPipeStraightAlt2
edges:
- to: start
conditions:
- !type:EntityAnchored
anchored: false
completed:
- !type:SpawnPrototype
prototype: SheetSteel1
amount: 1
- !type:DeleteEntity
steps:
- tool: Welding
doAfter: 1
- node: bend
entity: GasPipeBend
edges:
@@ -82,6 +212,38 @@
- tool: Welding
doAfter: 1
- node: bendAlt1
entity: GasPipeBendAlt1
edges:
- to: start
conditions:
- !type:EntityAnchored
anchored: false
completed:
- !type:SpawnPrototype
prototype: SheetSteel1
amount: 1
- !type:DeleteEntity
steps:
- tool: Welding
doAfter: 1
- node: bendAlt2
entity: GasPipeBendAlt2
edges:
- to: start
conditions:
- !type:EntityAnchored
anchored: false
completed:
- !type:SpawnPrototype
prototype: SheetSteel1
amount: 1
- !type:DeleteEntity
steps:
- tool: Welding
doAfter: 1
- node: tjunction
entity: GasPipeTJunction
edges:
@@ -98,6 +260,38 @@
- tool: Welding
doAfter: 1
- node: tjunctionAlt1
entity: GasPipeTJunctionAlt1
edges:
- to: start
conditions:
- !type:EntityAnchored
anchored: false
completed:
- !type:SpawnPrototype
prototype: SheetSteel1
amount: 1
- !type:DeleteEntity
steps:
- tool: Welding
doAfter: 1
- node: tjunctionAlt2
entity: GasPipeTJunctionAlt2
edges:
- to: start
conditions:
- !type:EntityAnchored
anchored: false
completed:
- !type:SpawnPrototype
prototype: SheetSteel1
amount: 1
- !type:DeleteEntity
steps:
- tool: Welding
doAfter: 1
- node: fourway
entity: GasPipeFourway
edges:
@@ -114,6 +308,38 @@
- tool: Welding
doAfter: 1
- node: fourwayAlt1
entity: GasPipeFourwayAlt1
edges:
- to: start
conditions:
- !type:EntityAnchored
anchored: false
completed:
- !type:SpawnPrototype
prototype: SheetSteel1
amount: 1
- !type:DeleteEntity
steps:
- tool: Welding
doAfter: 1
- node: fourwayAlt2
entity: GasPipeFourwayAlt2
edges:
- to: start
conditions:
- !type:EntityAnchored
anchored: false
completed:
- !type:SpawnPrototype
prototype: SheetSteel1
amount: 1
- !type:DeleteEntity
steps:
- tool: Welding
doAfter: 1
- node: broken
entity: GasPipeBroken
edges:
@@ -129,3 +355,19 @@
steps:
- tool: Welding
doAfter: 1
- node: manifold
entity: GasPipeManifold
edges:
- to: start
conditions:
- !type:EntityAnchored
anchored: false
completed:
- !type:SpawnPrototype
prototype: SheetSteel1
amount: 2
- !type:DeleteEntity
steps:
- tool: Welding
doAfter: 1

View File

@@ -281,8 +281,40 @@
startNode: start
targetNode: half
category: construction-category-utilities
placementMode: SnapgridCenter
placementMode: AlignAtmosPipeLayers
canBuildInImpassable: true
alternativePrototypes:
- GasPipeHalf
- GasPipeHalfAlt1
- GasPipeHalfAlt2
- type: construction
id: GasPipeHalfAlt1
hide: true
graph: GasPipe
startNode: start
targetNode: halfAlt1
category: construction-category-utilities
placementMode: AlignAtmosPipeLayers
canBuildInImpassable: true
alternativePrototypes:
- GasPipeHalf
- GasPipeHalfAlt1
- GasPipeHalfAlt2
- type: construction
id: GasPipeHalfAlt2
hide: true
graph: GasPipe
startNode: start
targetNode: halfAlt2
category: construction-category-utilities
placementMode: AlignAtmosPipeLayers
canBuildInImpassable: true
alternativePrototypes:
- GasPipeHalf
- GasPipeHalfAlt1
- GasPipeHalfAlt2
- type: construction
id: GasPipeStraight
@@ -290,8 +322,40 @@
startNode: start
targetNode: straight
category: construction-category-utilities
placementMode: SnapgridCenter
placementMode: AlignAtmosPipeLayers
canBuildInImpassable: true
alternativePrototypes:
- GasPipeStraight
- GasPipeStraightAlt1
- GasPipeStraightAlt2
- type: construction
id: GasPipeStraightAlt1
hide: true
graph: GasPipe
startNode: start
targetNode: straightAlt1
category: construction-category-utilities
placementMode: AlignAtmosPipeLayers
canBuildInImpassable: true
alternativePrototypes:
- GasPipeStraight
- GasPipeStraightAlt1
- GasPipeStraightAlt2
- type: construction
id: GasPipeStraightAlt2
hide: true
graph: GasPipe
startNode: start
targetNode: straightAlt2
category: construction-category-utilities
placementMode: AlignAtmosPipeLayers
canBuildInImpassable: true
alternativePrototypes:
- GasPipeStraight
- GasPipeStraightAlt1
- GasPipeStraightAlt2
- type: construction
id: GasPipeBend
@@ -299,8 +363,40 @@
startNode: start
targetNode: bend
category: construction-category-utilities
placementMode: SnapgridCenter
placementMode: AlignAtmosPipeLayers
canBuildInImpassable: true
alternativePrototypes:
- GasPipeBend
- GasPipeBendAlt1
- GasPipeBendAlt2
- type: construction
id: GasPipeBendAlt1
hide: true
graph: GasPipe
startNode: start
targetNode: bendAlt1
category: construction-category-utilities
placementMode: AlignAtmosPipeLayers
canBuildInImpassable: true
alternativePrototypes:
- GasPipeBend
- GasPipeBendAlt1
- GasPipeBendAlt2
- type: construction
id: GasPipeBendAlt2
hide: true
graph: GasPipe
startNode: start
targetNode: bendAlt2
category: construction-category-utilities
placementMode: AlignAtmosPipeLayers
canBuildInImpassable: true
alternativePrototypes:
- GasPipeBend
- GasPipeBendAlt1
- GasPipeBendAlt2
- type: construction
id: GasPipeTJunction
@@ -308,8 +404,40 @@
startNode: start
targetNode: tjunction
category: construction-category-utilities
placementMode: SnapgridCenter
placementMode: AlignAtmosPipeLayers
canBuildInImpassable: true
alternativePrototypes:
- GasPipeTJunction
- GasPipeTJunctionAlt1
- GasPipeTJunctionAlt2
- type: construction
id: GasPipeTJunctionAlt1
hide: true
graph: GasPipe
startNode: start
targetNode: tjunctionAlt1
category: construction-category-utilities
placementMode: AlignAtmosPipeLayers
canBuildInImpassable: true
alternativePrototypes:
- GasPipeTJunction
- GasPipeTJunctionAlt1
- GasPipeTJunctionAlt2
- type: construction
id: GasPipeTJunctionAlt2
hide: true
graph: GasPipe
startNode: start
targetNode: tjunctionAlt2
category: construction-category-utilities
placementMode: AlignAtmosPipeLayers
canBuildInImpassable: true
alternativePrototypes:
- GasPipeTJunction
- GasPipeTJunctionAlt1
- GasPipeTJunctionAlt2
- type: construction
id: GasPipeFourway
@@ -317,6 +445,47 @@
startNode: start
targetNode: fourway
category: construction-category-utilities
placementMode: AlignAtmosPipeLayers
canBuildInImpassable: true
alternativePrototypes:
- GasPipeFourway
- GasPipeFourwayAlt1
- GasPipeFourwayAlt2
- type: construction
id: GasPipeFourwayAlt1
hide: true
graph: GasPipe
startNode: start
targetNode: fourwayAlt1
category: construction-category-utilities
placementMode: AlignAtmosPipeLayers
canBuildInImpassable: true
alternativePrototypes:
- GasPipeFourway
- GasPipeFourwayAlt1
- GasPipeFourwayAlt2
- type: construction
id: GasPipeFourwayAlt2
hide: true
graph: GasPipe
startNode: start
targetNode: fourwayAlt2
category: construction-category-utilities
placementMode: AlignAtmosPipeLayers
canBuildInImpassable: true
alternativePrototypes:
- GasPipeFourway
- GasPipeFourwayAlt1
- GasPipeFourwayAlt2
- type: construction
id: GasPipeManifold
graph: GasPipe
startNode: start
targetNode: manifold
category: construction-category-utilities
placementMode: SnapgridCenter
canBuildInImpassable: true

View File

@@ -43,6 +43,16 @@
copyright: "Created by chromiumboy"
source: "https://github.com/chromiumboy"
- files: ["beveled_rectangle_east_west.png"]
license: "CC-BY-SA-3.0"
copyright: "Created by chromiumboy"
source: "https://github.com/chromiumboy"
- files: ["beveled_rectangle_north_south.png"]
license: "CC-BY-SA-3.0"
copyright: "Created by chromiumboy"
source: "https://github.com/chromiumboy"
- files: ["beveled_square.png"]
license: "CC-BY-SA-3.0"
copyright: "Created by chromiumboy"

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

View File

@@ -0,0 +1,2 @@
sample:
filter: true

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B

View File

@@ -0,0 +1,2 @@
sample:
filter: true

View File

@@ -10,3 +10,6 @@ https://game-icons.net/1x1/delapouite/padlock-open.html
bubbles.svg by Lorc under CC BY 3.0
https://game-icons.net/1x1/lorc/bubbles.html
screwdriver.png by Lorc (edited by chromiumboy) under CC BY 3.0
https://game-icons.net/1x1/lorc/screwdriver.html

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

View File

@@ -0,0 +1,2 @@
sample:
filter: true

View File

@@ -1,21 +1,18 @@
{
"version": 1,
"size":
{
"size": {
"x": 32,
"y": 32
},
"copyright":"Created by EmoGarbage404 (github) for Space Station 14.",
"copyright": "Created by EmoGarbage404 (github) for Space Station 14, edited by chromiumboy.",
"license": "CC0-1.0",
"states":
[
"states": [
{
"name": "off"
},
{
"name": "on",
"delays":
[
"delays": [
[
0.05,
0.05,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 344 B

After

Width:  |  Height:  |  Size: 279 B

View File

@@ -0,0 +1,15 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"copyright": "Created by EmoGarbage404 (github) for Space Station 14, edited by chromiumboy.",
"license": "CC0-1.0",
"states": [
{
"name": "pipe",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 B

View File

@@ -0,0 +1,15 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"copyright": "Created by EmoGarbage404 (github) for Space Station 14, edited by chromiumboy.",
"license": "CC0-1.0",
"states": [
{
"name": "pipe",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 B

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 B

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -11,14 +11,28 @@
"name": "icon"
},
{
"name": "base"
"name": "base",
"directions": 4
},
{
"name": "blank"
},
{
"name": "lights",
"directions": 4,
"delays": [
[
1.0,
0.25
],
[
1.0,
0.25
],
[
1.0,
0.25
],
[
1.0,
0.25

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

View File

@@ -0,0 +1,43 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Created by chromiumboy (github) for SS14, based on the digital valve from /tg/, taken from https://github.com/tgstation/tgstation at commit 57cd1d59ca019dd0e7811ac451f295f818e573da.",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "base",
"directions": 4
},
{
"name": "blank"
},
{
"name": "lights",
"directions": 4,
"delays": [
[
1.0,
0.25
],
[
1.0,
0.25
],
[
1.0,
0.25
],
[
1.0,
0.25
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

View File

@@ -0,0 +1,43 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Created by chromiumboy (github) for SS14, based on the digital valve from /tg/, taken from https://github.com/tgstation/tgstation at commit 57cd1d59ca019dd0e7811ac451f295f818e573da.",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "base",
"directions": 4
},
{
"name": "blank"
},
{
"name": "lights",
"directions": 4,
"delays": [
[
1.0,
0.25
],
[
1.0,
0.25
],
[
1.0,
0.25
],
[
1.0,
0.25
]
]
}
]
}

View File

@@ -1,7 +1,10 @@
{
"version": 1,
"size":{"x":32,"y":32},
"copyright":"Base sprites taken from tgstation, split to display on two layers (machinebody/panel) by Menshin, and recolored and edited by EmoGarbage404 (github)",
"size": {
"x": 32,
"y": 32
},
"copyright": "Base sprites taken from tgstation, split to display on two layers (machinebody/panel) by Menshin, and recolored and edited by EmoGarbage404 (github) and chromiumboy (github)",
"license": "CC-BY-SA-3.0",
"states": [
{
@@ -15,7 +18,20 @@
{
"name": "freezerOn",
"directions": 1,
"delays":[ [ 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1] ]
"delays": [
[
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1
]
]
},
{
"name": "heaterOff",
@@ -28,7 +44,20 @@
{
"name": "heaterOn",
"directions": 1,
"delays":[ [ 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1] ]
"delays": [
[
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1
]
]
},
{
"name": "pipe",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 321 B

After

Width:  |  Height:  |  Size: 307 B

View File

@@ -0,0 +1,30 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"copyright": "Base sprites taken from tgstation at https://github.com/tgstation/tgstation/commit/662c08272acd7be79531550919f56f846726eabb and edited by chromiumboy (github)",
"license": "CC-BY-SA-3.0",
"states": [
{
"name": "pipeManifold",
"directions": 4
},
{
"name": "storageManifold"
},
{
"name": "pipeConnector",
"directions": 4
},
{
"name": "pipeConnector_alt1",
"directions": 4
},
{
"name": "pipeConnector_alt2",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -5,7 +5,7 @@
"y": 32
},
"license": "CC-BY-SA-3.0",
"copyright":"Inhand sprites by alzore_(discord) for SS14. pipeTrinaryConnectors made by Menshin for SS14 based on pipeTJunction, the rest is taken from https://github.com/tgstation/tgstation at commit 57cd1d59ca019dd0e7811ac451f295f818e573da.",
"copyright": "Inhand sprites by alzore_(discord) for SS14. pipeTrinaryConnectors made by Menshin for SS14 based on pipeTJunction, the rest is taken from https://github.com/tgstation/tgstation at commit 57cd1d59ca019dd0e7811ac451f295f818e573da, edited by chromiumboy.",
"states": [
{
"name": "inhand-left",
@@ -67,21 +67,32 @@
"name": "pipeConnector",
"directions": 4
},
{
"name": "pipeUnaryConnectors",
"directions": 4
},
{
"name": "pipeBinaryConnectors",
"directions": 4
},
{
"name": "pipeTrinaryConnectors",
"directions": 4
},
{
"name":"storageStraight",
"directions":4
"name": "storageStraight"
},
{
"name":"storageBend",
"directions":4
"name": "storageHalf"
},
{
"name":"storageTJunction",
"directions":4
"name": "storageBend"
},
{
"name": "storageTJunction"
},
{
"name": "storageFourway"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 B

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 503 B

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 509 B

After

Width:  |  Height:  |  Size: 333 B

View File

@@ -0,0 +1,51 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"license": "CC-BY-SA-3.0",
"copyright": "Inhand sprites by alzore_(discord) for SS14. pipeTrinaryConnectors made by Menshin for SS14 based on pipeTJunction, the rest is taken from https://github.com/tgstation/tgstation at commit 57cd1d59ca019dd0e7811ac451f295f818e573da, edited by chromiumboy.",
"states": [
{
"name": "pipeBroken",
"directions": 1
},
{
"name": "pipeTJunction",
"directions": 4
},
{
"name": "pipeHalf",
"directions": 4
},
{
"name": "pipeBend",
"directions": 4
},
{
"name": "pipeFourway",
"directions": 4
},
{
"name": "pipeStraight",
"directions": 4
},
{
"name": "pipeConnector",
"directions": 4
},
{
"name": "pipeUnaryConnectors",
"directions": 4
},
{
"name": "pipeBinaryConnectors",
"directions": 4
},
{
"name": "pipeTrinaryConnectors",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 912 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 972 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

View File

@@ -0,0 +1,51 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"license": "CC-BY-SA-3.0",
"copyright": "Inhand sprites by alzore_(discord) for SS14. pipeTrinaryConnectors made by Menshin for SS14 based on pipeTJunction, the rest is taken from https://github.com/tgstation/tgstation at commit 57cd1d59ca019dd0e7811ac451f295f818e573da, edited by chromiumboy.",
"states": [
{
"name": "pipeBroken",
"directions": 1
},
{
"name": "pipeTJunction",
"directions": 4
},
{
"name": "pipeHalf",
"directions": 4
},
{
"name": "pipeBend",
"directions": 4
},
{
"name": "pipeFourway",
"directions": 4
},
{
"name": "pipeStraight",
"directions": 4
},
{
"name": "pipeConnector",
"directions": 4
},
{
"name": "pipeUnaryConnectors",
"directions": 4
},
{
"name": "pipeBinaryConnectors",
"directions": 4
},
{
"name": "pipeTrinaryConnectors",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 877 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Some files were not shown because too many files have changed in this diff Show More