diff --git a/Content.Client/GameObjects/Components/Doors/AirlockVisualizer.cs b/Content.Client/GameObjects/Components/Doors/AirlockVisualizer.cs
index 2c0994a35e..4cc6278333 100644
--- a/Content.Client/GameObjects/Components/Doors/AirlockVisualizer.cs
+++ b/Content.Client/GameObjects/Components/Doors/AirlockVisualizer.cs
@@ -2,6 +2,7 @@
using Content.Client.GameObjects.Components.Wires;
using Content.Shared.Audio;
using Content.Shared.GameObjects.Components.Doors;
+using JetBrains.Annotations;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Client.GameObjects.Components.Animations;
@@ -12,6 +13,7 @@ using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Doors
{
+ [UsedImplicitly]
public class AirlockVisualizer : AppearanceVisualizer
{
private const string AnimationKey = "airlock_animation";
@@ -24,11 +26,17 @@ namespace Content.Client.GameObjects.Components.Doors
{
base.LoadData(node);
+ var delay = 0.8f;
+
var openSound = node.GetNode("open_sound").AsString();
var closeSound = node.GetNode("close_sound").AsString();
var denySound = node.GetNode("deny_sound").AsString();
+ if (node.TryGetNode("animation_time", out var yamlNode))
+ {
+ delay = yamlNode.AsFloat();
+ }
- CloseAnimation = new Animation {Length = TimeSpan.FromSeconds(0.8f)};
+ CloseAnimation = new Animation {Length = TimeSpan.FromSeconds(delay)};
{
var flick = new AnimationTrackSpriteFlick();
CloseAnimation.AnimationTracks.Add(flick);
@@ -50,7 +58,7 @@ namespace Content.Client.GameObjects.Components.Doors
sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(closeSound, 0));
}
- OpenAnimation = new Animation {Length = TimeSpan.FromSeconds(0.8f)};
+ OpenAnimation = new Animation {Length = TimeSpan.FromSeconds(delay)};
{
var flick = new AnimationTrackSpriteFlick();
OpenAnimation.AnimationTracks.Add(flick);
diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs
index 49f804ca64..c0fd55dfb4 100644
--- a/Content.Client/IgnoredComponents.cs
+++ b/Content.Client/IgnoredComponents.cs
@@ -176,6 +176,7 @@
"ExtinguisherCabinet",
"ExtinguisherCabinetFilled",
"FireExtinguisher",
+ "Firelock",
"AtmosPlaque",
"Spillable",
};
diff --git a/Content.IntegrationTests/Tests/Atmos/GasMixtureTest.cs b/Content.IntegrationTests/Tests/Atmos/GasMixtureTest.cs
new file mode 100644
index 0000000000..0b7f2f9e2d
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Atmos/GasMixtureTest.cs
@@ -0,0 +1,85 @@
+using System.Threading.Tasks;
+using Content.Server.Atmos;
+using Content.Shared.Atmos;
+using NUnit.Framework;
+
+namespace Content.IntegrationTests.Tests.Atmos
+{
+ [TestFixture]
+ [TestOf(typeof(GasMixture))]
+ public class GasMixtureTest : ContentIntegrationTest
+ {
+ [Test]
+ public async Task TestMerge()
+ {
+ var server = StartServerDummyTicker();
+
+ server.Assert(() =>
+ {
+ var a = new GasMixture(10f);
+ var b = new GasMixture(10f);
+
+ a.AdjustMoles(Gas.Oxygen, 50);
+ b.AdjustMoles(Gas.Nitrogen, 50);
+
+ // a now has 50 moles of oxygen
+ Assert.That(a.TotalMoles, Is.EqualTo(50));
+ Assert.That(a.GetMoles(Gas.Oxygen), Is.EqualTo(50));
+
+ // b now has 50 moles of nitrogen
+ Assert.That(b.TotalMoles, Is.EqualTo(50));
+ Assert.That(b.GetMoles(Gas.Nitrogen), Is.EqualTo(50));
+
+ b.Merge(a);
+
+ // b now has its contents and the contents of a
+ Assert.That(b.TotalMoles, Is.EqualTo(100));
+ Assert.That(b.GetMoles(Gas.Oxygen), Is.EqualTo(50));
+ Assert.That(b.GetMoles(Gas.Nitrogen), Is.EqualTo(50));
+
+ // a should be the same, however.
+ Assert.That(a.TotalMoles, Is.EqualTo(50));
+ Assert.That(a.GetMoles(Gas.Oxygen), Is.EqualTo(50));
+ });
+
+ await server.WaitIdleAsync();
+ }
+
+ [Test]
+ [TestCase(0.5f)]
+ [TestCase(0.25f)]
+ [TestCase(0.75f)]
+ [TestCase(1f)]
+ [TestCase(0f)]
+ [TestCase(Atmospherics.BreathPercentage)]
+ public async Task RemoveRatio(float ratio)
+ {
+ var server = StartServerDummyTicker();
+
+ server.Assert(() =>
+ {
+ var a = new GasMixture(10f);
+
+ a.AdjustMoles(Gas.Oxygen, 100);
+ a.AdjustMoles(Gas.Nitrogen, 100);
+
+ var origTotal = a.TotalMoles;
+
+ // we remove moles from the mixture with a ratio.
+ var b = a.RemoveRatio(ratio);
+
+ // check that the amount of moles in the original and the new mixture are correct.
+ Assert.That(b.TotalMoles, Is.EqualTo(origTotal * ratio));
+ Assert.That(a.TotalMoles, Is.EqualTo(origTotal - b.TotalMoles));
+
+ Assert.That(b.GetMoles(Gas.Oxygen), Is.EqualTo(100 * ratio));
+ Assert.That(b.GetMoles(Gas.Nitrogen), Is.EqualTo(100 * ratio));
+
+ Assert.That(a.GetMoles(Gas.Oxygen), Is.EqualTo(100 - b.GetMoles(Gas.Oxygen)));
+ Assert.That(a.GetMoles(Gas.Nitrogen), Is.EqualTo(100 - b.GetMoles(Gas.Nitrogen)));
+ });
+
+ await server.WaitIdleAsync();
+ }
+ }
+}
diff --git a/Content.Server/Atmos/GasMixture.cs b/Content.Server/Atmos/GasMixture.cs
index ddd834eceb..54bb5ad47c 100644
--- a/Content.Server/Atmos/GasMixture.cs
+++ b/Content.Server/Atmos/GasMixture.cs
@@ -545,6 +545,10 @@ namespace Content.Server.Atmos
serializer.DataField(ref _moles, "moles", new float[Atmospherics.TotalNumberOfGases]);
serializer.DataField(ref _molesArchived, "molesArchived", new float[Atmospherics.TotalNumberOfGases]);
serializer.DataField(ref _temperature, "temperature", Atmospherics.TCMB);
+
+ // The arrays MUST have a specific length.
+ Array.Resize(ref _moles, Atmospherics.TotalNumberOfGases);
+ Array.Resize(ref _molesArchived, Atmospherics.TotalNumberOfGases);
}
public override bool Equals(object? obj)
diff --git a/Content.Server/Atmos/IGridAtmosphereComponent.cs b/Content.Server/Atmos/IGridAtmosphereComponent.cs
index 1a8f76954c..0d7a827bf9 100644
--- a/Content.Server/Atmos/IGridAtmosphereComponent.cs
+++ b/Content.Server/Atmos/IGridAtmosphereComponent.cs
@@ -1,8 +1,11 @@
using System.Collections.Generic;
+using Content.Server.GameObjects.Components.Atmos;
using Content.Server.GameObjects.Components.Atmos.Piping;
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
+using Content.Shared.Atmos;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Map;
+using Robust.Shared.Maths;
namespace Content.Server.Atmos
{
@@ -42,6 +45,12 @@ namespace Content.Server.Atmos
///
void FixVacuum(MapIndices indices);
+ ///
+ /// Revalidates indices immediately.
+ ///
+ ///
+ void UpdateAdjacentBits(MapIndices indices);
+
///
/// Adds an active tile so it becomes processed every update until it becomes inactive.
/// Also makes the tile excited.
@@ -109,6 +118,7 @@ namespace Content.Server.Atmos
/// Returns a tile.
///
///
+ ///
///
TileAtmosphere GetTile(MapIndices indices, bool createSpace = true);
@@ -116,17 +126,19 @@ namespace Content.Server.Atmos
/// Returns a tile.
///
///
+ ///
///
TileAtmosphere GetTile(EntityCoordinates coordinates, bool createSpace = true);
///
/// Returns if the tile in question is air-blocked.
/// This could be due to a wall, an airlock, etc.
- /// Also see AirtightComponent.
+ ///
///
///
+ ///
///
- bool IsAirBlocked(MapIndices indices);
+ bool IsAirBlocked(MapIndices indices, AtmosDirection direction);
///
/// Returns if the tile in question is space.
@@ -142,6 +154,11 @@ namespace Content.Server.Atmos
///
float GetVolumeForCells(int cellCount);
+ ///
+ /// Returns a dictionary of adjacent TileAtmospheres.
+ ///
+ Dictionary GetAdjacentTiles(MapIndices indices, bool includeAirBlocked = false);
+
void Update(float frameTime);
void AddPipeNet(IPipeNet pipeNet);
diff --git a/Content.Server/Atmos/TileAtmosphere.cs b/Content.Server/Atmos/TileAtmosphere.cs
index 6b11195176..6361574211 100644
--- a/Content.Server/Atmos/TileAtmosphere.cs
+++ b/Content.Server/Atmos/TileAtmosphere.cs
@@ -6,6 +6,7 @@ using Content.Server.Atmos.Reactions;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.GameObjects.EntitySystems.Atmos;
using Content.Server.Interfaces;
+using Content.Server.Utility;
using Content.Shared.Atmos;
using Content.Shared.Audio;
using Content.Shared.Maps;
@@ -66,7 +67,7 @@ namespace Content.Server.Atmos
public float HeatCapacity { get; set; } = 1f;
[ViewVariables]
- public float ThermalConductivity => Tile?.Tile.GetContentTileDefinition().ThermalConductivity ?? 0.05f;
+ public float ThermalConductivity { get; set; } = 0.05f;
[ViewVariables]
public bool Excited { get; set; }
@@ -111,8 +112,13 @@ namespace Content.Server.Atmos
[ViewVariables]
public GasMixture Air { get; set; }
+ [ViewVariables, UsedImplicitly]
+ private int _blockedAirflow => (int)BlockedAirflow;
+
+ public AtmosDirection BlockedAirflow { get; set; } = AtmosDirection.Invalid;
+
[ViewVariables]
- public bool BlocksAir => _gridAtmosphereComponent.IsAirBlocked(GridIndices);
+ public bool BlocksAllAir => BlockedAirflow == AtmosDirection.All;
public TileAtmosphere(GridAtmosphereComponent atmosphereComponent, GridId gridIndex, MapIndices gridIndices, GasMixture mixture = null, bool immutable = false)
{
@@ -867,12 +873,12 @@ namespace Content.Server.Atmos
private void FinishSuperconduction()
{
// Conduct with air on my tile if I have it
- if (!BlocksAir)
+ if (!BlocksAllAir)
{
Temperature = Air.TemperatureShare(ThermalConductivity, Temperature, HeatCapacity);
}
- FinishSuperconduction(BlocksAir ? Temperature : Air.Temperature);
+ FinishSuperconduction(BlocksAllAir ? Temperature : Air.Temperature);
}
private void FinishSuperconduction(float temperature)
@@ -886,9 +892,9 @@ namespace Content.Server.Atmos
private void NeighborConductWithSource(TileAtmosphere other)
{
- if (BlocksAir)
+ if (BlocksAllAir)
{
- if (!other.BlocksAir)
+ if (!other.BlocksAllAir)
{
other.TemperatureShareOpenToSolid(this);
}
@@ -901,7 +907,7 @@ namespace Content.Server.Atmos
return;
}
- if (!other.BlocksAir)
+ if (!other.BlocksAllAir)
{
other.Air.TemperatureShare(Air, Atmospherics.WindowHeatTransferCoefficient);
}
@@ -952,7 +958,7 @@ namespace Content.Server.Atmos
public AtmosDirection ConductivityDirections()
{
- if(BlocksAir)
+ if(BlocksAllAir)
{
if(_archivedCycle < _gridAtmosphereComponent.UpdateCounter)
Archive(_gridAtmosphereComponent.UpdateCounter);
@@ -1087,7 +1093,25 @@ namespace Content.Server.Atmos
private void ConsiderFirelocks(TileAtmosphere other)
{
- // TODO ATMOS firelocks!
+ var reconsiderAdjacent = false;
+
+ foreach (var entity in GridIndices.GetEntitiesInTileFast(GridIndex, _gridAtmosphereComponent.GridTileLookupSystem))
+ {
+ if (!entity.TryGetComponent(out FirelockComponent firelock)) continue;
+ reconsiderAdjacent |= firelock.EmergencyPressureStop();
+ }
+
+ foreach (var entity in other.GridIndices.GetEntitiesInTileFast(other.GridIndex, _gridAtmosphereComponent.GridTileLookupSystem))
+ {
+ if (!entity.TryGetComponent(out FirelockComponent firelock)) continue;
+ reconsiderAdjacent |= firelock.EmergencyPressureStop();
+ }
+
+ if (reconsiderAdjacent)
+ {
+ UpdateAdjacent();
+ other.UpdateAdjacent();
+ }
}
private void React()
@@ -1130,7 +1154,7 @@ namespace Content.Server.Atmos
_adjacentTiles[direction.ToIndex()] = adjacent;
adjacent?.UpdateAdjacent(direction.GetOpposite());
- if (adjacent != null && !_gridAtmosphereComponent.IsAirBlocked(adjacent.GridIndices))
+ if (adjacent != null && !BlockedAirflow.HasFlag(direction) && !_gridAtmosphereComponent.IsAirBlocked(adjacent.GridIndices, direction.GetOpposite()))
{
_adjacentBits |= direction;
}
@@ -1139,9 +1163,15 @@ namespace Content.Server.Atmos
public void UpdateAdjacent(AtmosDirection direction)
{
- if (!_gridAtmosphereComponent.IsAirBlocked(GridIndices.Offset(direction.ToDirection())))
+ _adjacentTiles[direction.ToIndex()] = _gridAtmosphereComponent.GetTile(GridIndices.Offset(direction.ToDirection()));
+
+ if (!BlockedAirflow.HasFlag(direction) && !_gridAtmosphereComponent.IsAirBlocked(GridIndices.Offset(direction.ToDirection()), direction.GetOpposite()))
{
- _adjacentTiles[direction.ToIndex()] = _gridAtmosphereComponent.GetTile(GridIndices.Offset(direction.ToDirection()));
+ _adjacentBits |= direction;
+ }
+ else
+ {
+ _adjacentBits &= ~direction;
}
}
diff --git a/Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs b/Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs
index 9e422a8767..22c0783548 100644
--- a/Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs
+++ b/Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs
@@ -1,5 +1,7 @@
#nullable enable
+using System;
using Content.Server.GameObjects.EntitySystems;
+using Content.Shared.Atmos;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Transform;
@@ -9,6 +11,7 @@ using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
+using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -21,12 +24,21 @@ namespace Content.Server.GameObjects.Components.Atmos
[Dependency] private readonly IEntityManager _entityManager = default!;
private (GridId, MapIndices) _lastPosition;
+ private AtmosphereSystem _atmosphereSystem = default!;
public override string Name => "Airtight";
+ [ViewVariables]
+ private int _airBlockedDirection;
private bool _airBlocked = true;
private bool _fixVacuum = false;
+ [ViewVariables]
+ private bool _rotateAirBlocked = true;
+
+ [ViewVariables]
+ private bool _fixAirBlockedDirectionInitialize = true;
+
[ViewVariables(VVAccess.ReadWrite)]
public bool AirBlocked
{
@@ -35,10 +47,18 @@ namespace Content.Server.GameObjects.Components.Atmos
{
_airBlocked = value;
- if (Owner.TryGetComponent(out SnapGridComponent? snapGrid))
- {
- EntitySystem.Get().GetGridAtmosphere(Owner.Transform.GridID)?.Invalidate(snapGrid.Position);
- }
+ UpdatePosition();
+ }
+ }
+
+ public AtmosDirection AirBlockedDirection
+ {
+ get => (AtmosDirection)_airBlockedDirection;
+ set
+ {
+ _airBlockedDirection = (int) value;
+
+ UpdatePosition();
}
}
@@ -51,22 +71,53 @@ namespace Content.Server.GameObjects.Components.Atmos
serializer.DataField(ref _airBlocked, "airBlocked", true);
serializer.DataField(ref _fixVacuum, "fixVacuum", true);
+ serializer.DataField(ref _airBlockedDirection, "airBlockedDirection", (int)AtmosDirection.All, WithFormat.Flags());
+ serializer.DataField(ref _rotateAirBlocked, "rotateAirBlocked", true);
+ serializer.DataField(ref _fixAirBlockedDirectionInitialize, "fixAirBlockedDirectionInitialize", true);
}
public override void Initialize()
{
base.Initialize();
- // Using the SnapGrid is critical for the performance of the room builder, and thus if
- // it is absent the component will not be airtight. A warning is much easier to track
- // down than the object magically not being airtight, so log one if the SnapGrid component
- // is missing.
+ _atmosphereSystem = EntitySystem.Get();
+
+ // Using the SnapGrid is critical for performance, and thus if it is absent the component
+ // will not be airtight. A warning is much easier to track down than the object magically
+ // not being airtight, so log one if the SnapGrid component is missing.
if (!Owner.EnsureComponent(out SnapGridComponent _))
Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition.ToString()} didn't have a {nameof(SnapGridComponent)}");
+ Owner.EntityManager.EventBus.SubscribeEvent(EventSource.Local, this, RotateEvent);
+
+ if(_fixAirBlockedDirectionInitialize)
+ RotateEvent(new RotateEvent(Owner, Angle.South, Owner.Transform.LocalRotation));
+
UpdatePosition();
}
+ private void RotateEvent(RotateEvent ev)
+ {
+ if (!_rotateAirBlocked || ev.Sender != Owner || ev.NewRotation == ev.OldRotation || AirBlockedDirection == AtmosDirection.Invalid)
+ return;
+
+ var diff = ev.NewRotation - ev.OldRotation;
+
+ var newAirBlockedDirs = AtmosDirection.Invalid;
+
+ // TODO ATMOS MULTIZ When we make multiZ atmos, special case this.
+ for (int i = 0; i < Atmospherics.Directions; i++)
+ {
+ var direction = (AtmosDirection) (1 << i);
+ if (!AirBlockedDirection.HasFlag(direction)) continue;
+ var angle = direction.ToAngle();
+ angle += diff;
+ newAirBlockedDirs |= angle.ToAtmosDirectionCardinal();
+ }
+
+ AirBlockedDirection = newAirBlockedDirs;
+ }
+
public void MapInit()
{
if (Owner.TryGetComponent(out SnapGridComponent? snapGrid))
@@ -89,13 +140,10 @@ namespace Content.Server.GameObjects.Components.Atmos
snapGrid.OnPositionChanged -= OnTransformMove;
}
- if (_fixVacuum)
- {
- var mapIndices = Owner.Transform.Coordinates.ToMapIndices(_entityManager, _mapManager);
- EntitySystem.Get().GetGridAtmosphere(Owner.Transform.GridID)?.FixVacuum(mapIndices);
- }
+ UpdatePosition(_lastPosition.Item1, _lastPosition.Item2);
- UpdatePosition();
+ if (_fixVacuum)
+ _atmosphereSystem.GetGridAtmosphere(_lastPosition.Item1)?.FixVacuum(_lastPosition.Item2);
}
private void OnTransformMove()
@@ -111,13 +159,18 @@ namespace Content.Server.GameObjects.Components.Atmos
private void UpdatePosition()
{
- var mapIndices = Owner.Transform.Coordinates.ToMapIndices(_entityManager, _mapManager);
- UpdatePosition(Owner.Transform.GridID, mapIndices);
+ if (Owner.TryGetComponent(out SnapGridComponent? snapGrid))
+ UpdatePosition(Owner.Transform.GridID, snapGrid.Position);
}
private void UpdatePosition(GridId gridId, MapIndices pos)
{
- EntitySystem.Get().GetGridAtmosphere(gridId)?.Invalidate(pos);
+ var gridAtmos = _atmosphereSystem.GetGridAtmosphere(gridId);
+
+ if (gridAtmos == null) return;
+
+ gridAtmos.UpdateAdjacentBits(pos);
+ gridAtmos.Invalidate(pos);
}
}
}
diff --git a/Content.Server/GameObjects/Components/Atmos/FirelockComponent.cs b/Content.Server/GameObjects/Components/Atmos/FirelockComponent.cs
new file mode 100644
index 0000000000..6ef8768e37
--- /dev/null
+++ b/Content.Server/GameObjects/Components/Atmos/FirelockComponent.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Threading.Tasks;
+using Content.Server.Atmos;
+using Content.Server.GameObjects.Components.Doors;
+using Content.Server.GameObjects.Components.Interactable;
+using Content.Server.Interfaces;
+using Content.Shared.GameObjects.Components.Doors;
+using Content.Shared.GameObjects.Components.Interactable;
+using Content.Shared.Interfaces;
+using Content.Shared.Interfaces.GameObjects.Components;
+using Robust.Shared.GameObjects;
+using Robust.Shared.GameObjects.Components;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.IoC;
+
+namespace Content.Server.GameObjects.Components.Atmos
+{
+ [RegisterComponent]
+ public class FirelockComponent : ServerDoorComponent, IInteractUsing, ICollideBehavior
+ {
+ public override string Name => "Firelock";
+
+ protected override TimeSpan CloseTimeOne => TimeSpan.FromSeconds(0.1f);
+ protected override TimeSpan CloseTimeTwo => TimeSpan.FromSeconds(0.6f);
+ protected override TimeSpan OpenTimeOne => TimeSpan.FromSeconds(0.1f);
+ protected override TimeSpan OpenTimeTwo => TimeSpan.FromSeconds(0.6f);
+
+ public void CollideWith(IEntity collidedWith)
+ {
+ // We do nothing.
+ }
+
+ protected override void Startup()
+ {
+ base.Startup();
+
+ if (Owner.TryGetComponent(out AirtightComponent airtightComponent))
+ {
+ airtightComponent.AirBlocked = false;
+ }
+
+ if (Owner.TryGetComponent(out ICollidableComponent collidableComponent))
+ {
+ collidableComponent.Hard = false;
+ }
+
+ Safety = false;
+
+ if (Occludes && Owner.TryGetComponent(out OccluderComponent occluder))
+ {
+ occluder.Enabled = false;
+ }
+
+ State = DoorState.Open;
+ SetAppearance(DoorVisualState.Open);
+ }
+
+ public bool EmergencyPressureStop()
+ {
+ var closed = State == DoorState.Open && Close();
+
+ if(closed)
+ Owner.GetComponent().AirBlocked = true;
+
+ return closed;
+ }
+
+ public override bool CanOpen()
+ {
+ return !IsHoldingFire() && !IsHoldingPressure() && base.CanOpen();
+ }
+
+ public override bool CanClose(IEntity user) => true;
+ public override bool CanOpen(IEntity user) => CanOpen();
+
+ public async Task InteractUsing(InteractUsingEventArgs eventArgs)
+ {
+ if (!eventArgs.Using.TryGetComponent(out var tool))
+ return false;
+
+ if (tool.HasQuality(ToolQuality.Prying))
+ {
+ var holdingPressure = IsHoldingPressure();
+ var holdingFire = IsHoldingFire();
+
+ if (State == DoorState.Closed)
+ {
+ if(holdingPressure)
+ Owner.PopupMessage(eventArgs.User, "A gush of air blows in your face... Maybe you should reconsider.");
+ }
+
+ if (!await tool.UseTool(eventArgs.User, Owner, holdingPressure || holdingFire ? 1.5f : 0.25f, ToolQuality.Prying)) return false;
+
+ if (State == DoorState.Closed)
+ Open();
+ else if (State == DoorState.Open)
+ Close();
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs b/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs
index 705b9c585f..f33e03fd64 100644
--- a/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs
+++ b/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs
@@ -9,11 +9,14 @@ using Content.Server.GameObjects.Components.Atmos.Piping;
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Content.Shared.Atmos;
using Content.Shared.Maps;
+using Robust.Server.GameObjects.EntitySystems.TileLookup;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Map;
using Robust.Shared.GameObjects.Components.Transform;
+using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.Map;
+using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
@@ -32,6 +35,8 @@ namespace Content.Server.GameObjects.Components.Atmos
[Robust.Shared.IoC.Dependency] private ITileDefinitionManager _tileDefinitionManager = default!;
[Robust.Shared.IoC.Dependency] private IServerEntityManager _serverEntityManager = default!;
+ public GridTileLookupSystem GridTileLookupSystem { get; private set; } = default!;
+
///
/// Check current execution time every n instances processed.
///
@@ -52,6 +57,7 @@ namespace Content.Server.GameObjects.Components.Atmos
private bool _paused = false;
private float _timer = 0f;
private Stopwatch _stopwatch = new Stopwatch();
+ private GridId _gridId;
[ViewVariables]
public int UpdateCounter { get; private set; } = 0;
@@ -155,23 +161,25 @@ namespace Content.Server.GameObjects.Components.Atmos
///
public virtual void PryTile(MapIndices indices)
{
- if (!Owner.TryGetComponent(out IMapGridComponent? mapGridComponent)) return;
if (IsSpace(indices) || IsAirBlocked(indices)) return;
- var mapGrid = mapGridComponent.Grid;
- indices.PryTile(mapGrid.Index, _mapManager, _tileDefinitionManager, _serverEntityManager);
+ indices.PryTile(_gridId, _mapManager, _tileDefinitionManager, _serverEntityManager);
}
public override void Initialize()
{
base.Initialize();
RepopulateTiles();
+
+ GridTileLookupSystem = EntitySystem.Get();
}
public override void OnAdd()
{
base.OnAdd();
- RepopulateTiles();
+
+ if (Owner.TryGetComponent(out IMapGridComponent? mapGrid))
+ _gridId = mapGrid.GridIndex;
}
public virtual void RepopulateTiles()
@@ -182,6 +190,8 @@ namespace Content.Server.GameObjects.Components.Atmos
{
if(!Tiles.ContainsKey(tile.GridIndices))
Tiles.Add(tile.GridIndices, new TileAtmosphere(this, tile.GridIndex, tile.GridIndices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C}));
+
+ Invalidate(tile.GridIndices);
}
foreach (var (_, tile) in Tiles.ToArray())
@@ -199,16 +209,13 @@ namespace Content.Server.GameObjects.Components.Atmos
protected virtual void Revalidate()
{
- if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
-
foreach (var indices in _invalidatedCoords.ToArray())
{
var tile = GetTile(indices);
- AddActiveTile(tile);
if (tile == null)
{
- tile = new TileAtmosphere(this, mapGrid.Grid.Index, indices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C});
+ tile = new TileAtmosphere(this, _gridId, indices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C});
Tiles[indices] = tile;
}
@@ -224,19 +231,19 @@ namespace Content.Server.GameObjects.Components.Atmos
}
else
{
- var obs = GetObstructingComponent(indices);
-
- if (obs != null)
+ if (tile.Air == null && NeedsVacuumFixing(indices))
{
- if (tile.Air == null && obs.FixVacuum)
- {
- FixVacuum(tile.GridIndices);
- }
+ FixVacuum(tile.GridIndices);
}
tile.Air ??= new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C};
}
+ AddActiveTile(tile);
+ tile.BlockedAirflow = GetBlockedDirections(indices);
+
+ // TODO ATMOS: Query all the contents of this tile (like walls) and calculate the correct thermal conductivity
+ tile.ThermalConductivity = tile.Tile?.Tile.GetContentTileDefinition().ThermalConductivity ?? 0.5f;
tile.UpdateAdjacent();
tile.UpdateVisuals();
@@ -246,19 +253,23 @@ namespace Content.Server.GameObjects.Components.Atmos
var otherIndices = indices.Offset(direction.ToDirection());
var otherTile = GetTile(otherIndices);
AddActiveTile(otherTile);
- otherTile?.UpdateAdjacent(direction.GetOpposite());
}
}
_invalidatedCoords.Clear();
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void UpdateAdjacentBits(MapIndices indices)
+ {
+ GetTile(indices)?.UpdateAdjacent();
+ }
+
///
public virtual void FixVacuum(MapIndices indices)
{
- if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
var tile = GetTile(indices);
- if (tile?.GridIndex != mapGrid.Grid.Index) return;
+ if (tile?.GridIndex != _gridId) return;
var adjacent = GetAdjacentTiles(indices);
tile.Air = new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C};
Tiles[indices] = tile;
@@ -277,8 +288,7 @@ namespace Content.Server.GameObjects.Components.Atmos
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public virtual void AddActiveTile(TileAtmosphere? tile)
{
- if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
- if (tile?.GridIndex != mapGrid.Grid.Index) return;
+ if (tile?.GridIndex != _gridId) return;
tile.Excited = true;
_activeTiles.Add(tile);
}
@@ -297,8 +307,7 @@ namespace Content.Server.GameObjects.Components.Atmos
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public virtual void AddHotspotTile(TileAtmosphere? tile)
{
- if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
- if (tile?.GridIndex != mapGrid.Grid.Index || tile?.Air == null) return;
+ if (tile?.GridIndex != _gridId || tile?.Air == null) return;
_hotspotTiles.Add(tile);
}
@@ -312,8 +321,7 @@ namespace Content.Server.GameObjects.Components.Atmos
public virtual void AddSuperconductivityTile(TileAtmosphere? tile)
{
- if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
- if (tile?.GridIndex != mapGrid.Grid.Index) return;
+ if (tile?.GridIndex != _gridId) return;
_superconductivityTiles.Add(tile);
}
@@ -327,8 +335,7 @@ namespace Content.Server.GameObjects.Components.Atmos
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public virtual void AddHighPressureDelta(TileAtmosphere? tile)
{
- if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
- if (tile?.GridIndex != mapGrid.Grid.Index) return;
+ if (tile?.GridIndex != _gridId) return;
_highPressureDelta.Add(tile);
}
@@ -382,24 +389,30 @@ namespace Content.Server.GameObjects.Components.Atmos
///
public TileAtmosphere? GetTile(MapIndices indices, bool createSpace = true)
{
- if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return null;
-
if (Tiles.TryGetValue(indices, out var tile)) return tile;
// We don't have that tile!
if (IsSpace(indices) && createSpace)
{
- return new TileAtmosphere(this, mapGrid.Grid.Index, indices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.TCMB}, true);
+ return new TileAtmosphere(this, _gridId, indices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.TCMB}, true);
}
return null;
}
///
- public bool IsAirBlocked(MapIndices indices)
+ public bool IsAirBlocked(MapIndices indices, AtmosDirection direction = AtmosDirection.All)
{
- var ac = GetObstructingComponent(indices);
- return ac != null && ac.AirBlocked;
+ foreach (var obstructingComponent in GetObstructingComponents(indices))
+ {
+ if (!obstructingComponent.AirBlocked)
+ continue;
+
+ if (obstructingComponent.AirBlockedDirection.HasFlag(direction))
+ return true;
+ }
+
+ return false;
}
///
@@ -763,17 +776,43 @@ namespace Content.Server.GameObjects.Components.Atmos
return true;
}
- private AirtightComponent? GetObstructingComponent(MapIndices indices)
+ private IEnumerable GetObstructingComponents(MapIndices indices)
{
- if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return default;
+ var gridLookup = EntitySystem.Get();
- foreach (var v in mapGrid.Grid.GetSnapGridCell(indices, SnapGridOffset.Center))
+ var list = new List();
+
+ foreach (var v in gridLookup.GetEntitiesIntersecting(_gridId, indices))
{
- if (v.Owner.TryGetComponent(out var ac))
- return ac;
+ if (v.TryGetComponent(out var ac))
+ list.Add(ac);
}
- return null;
+ return list;
+ }
+
+ private bool NeedsVacuumFixing(MapIndices indices)
+ {
+ var value = false;
+
+ foreach (var airtightComponent in GetObstructingComponents(indices))
+ {
+ value |= airtightComponent.FixVacuum;
+ }
+
+ return value;
+ }
+
+ private AtmosDirection GetBlockedDirections(MapIndices indices)
+ {
+ var value = AtmosDirection.Invalid;
+
+ foreach (var airtightComponent in GetObstructingComponents(indices))
+ {
+ value |= airtightComponent.AirBlockedDirection;
+ }
+
+ return value;
}
public void Dispose()
diff --git a/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs b/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs
index cebf82ab73..79b48d6453 100644
--- a/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs
+++ b/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs
@@ -2,6 +2,7 @@
using System;
using System.Linq;
using System.Threading;
+using Content.Server.Atmos;
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Access;
using Content.Server.GameObjects.Components.Atmos;
@@ -51,14 +52,14 @@ namespace Content.Server.GameObjects.Components.Doors
protected const float AutoCloseDelay = 5;
[ViewVariables(VVAccess.ReadWrite)]
protected float CloseSpeed = AutoCloseDelay;
-
+
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
- private static readonly TimeSpan CloseTimeOne = TimeSpan.FromSeconds(0.3f);
- private static readonly TimeSpan CloseTimeTwo = TimeSpan.FromSeconds(0.9f);
- private static readonly TimeSpan OpenTimeOne = TimeSpan.FromSeconds(0.3f);
- private static readonly TimeSpan OpenTimeTwo = TimeSpan.FromSeconds(0.9f);
- private static readonly TimeSpan DenyTime = TimeSpan.FromSeconds(0.45f);
+ protected virtual TimeSpan CloseTimeOne => TimeSpan.FromSeconds(0.3f);
+ protected virtual TimeSpan CloseTimeTwo => TimeSpan.FromSeconds(0.9f);
+ protected virtual TimeSpan OpenTimeOne => TimeSpan.FromSeconds(0.3f);
+ protected virtual TimeSpan OpenTimeTwo => TimeSpan.FromSeconds(0.9f);
+ protected virtual TimeSpan DenyTime => TimeSpan.FromSeconds(0.45f);
private const int DoorCrushDamage = 15;
private const float DoorStunTime = 5f;
@@ -67,6 +68,8 @@ namespace Content.Server.GameObjects.Components.Doors
[ViewVariables(VVAccess.ReadWrite)] private bool _occludes;
+ public bool Occludes => _occludes;
+
[ViewVariables(VVAccess.ReadWrite)]
public bool IsWeldedShut
{
@@ -85,6 +88,9 @@ namespace Content.Server.GameObjects.Components.Doors
private bool _isWeldedShut;
private bool _canWeldShut = true;
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ private bool _canCrush = true;
public override void ExposeData(ObjectSerializer serializer)
{
@@ -92,6 +98,7 @@ namespace Content.Server.GameObjects.Components.Doors
serializer.DataField(ref _occludes, "occludes", true);
serializer.DataField(ref _isWeldedShut, "welded", false);
+ serializer.DataField(ref _canCrush, "canCrush", true);
}
public override void OnRemove()
@@ -146,7 +153,7 @@ namespace Content.Server.GameObjects.Components.Doors
}
}
- private void SetAppearance(DoorVisualState state)
+ protected void SetAppearance(DoorVisualState state)
{
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
@@ -159,7 +166,7 @@ namespace Content.Server.GameObjects.Components.Doors
return !_isWeldedShut;
}
- public bool CanOpen(IEntity user)
+ public virtual bool CanOpen(IEntity user)
{
if (!CanOpen()) return false;
@@ -253,7 +260,7 @@ namespace Content.Server.GameObjects.Components.Doors
return true;
}
- public bool CanClose(IEntity user)
+ public virtual bool CanClose(IEntity user)
{
if (!CanClose()) return false;
if (!Owner.TryGetComponent(out AccessReader? accessReader))
@@ -313,11 +320,62 @@ namespace Content.Server.GameObjects.Components.Doors
}
}
+ public bool IsHoldingPressure(float threshold = 20)
+ {
+ var atmosphereSystem = EntitySystem.Get();
+
+ if (!Owner.Transform.Coordinates.TryGetTileAtmosphere(out var tileAtmos))
+ return false;
+
+ var gridAtmosphere = atmosphereSystem.GetGridAtmosphere(Owner.Transform.GridID);
+
+ if (gridAtmosphere == null)
+ return false;
+
+ var minMoles = float.MaxValue;
+ var maxMoles = 0f;
+
+ foreach (var (direction, adjacent) in gridAtmosphere.GetAdjacentTiles(tileAtmos.GridIndices))
+ {
+ var moles = adjacent.Air.TotalMoles;
+ if (moles < minMoles)
+ minMoles = moles;
+ if (moles > maxMoles)
+ maxMoles = moles;
+ }
+
+ return (maxMoles - minMoles) > threshold;
+ }
+
+ public bool IsHoldingFire()
+ {
+ var atmosphereSystem = EntitySystem.Get();
+
+ if (!Owner.Transform.Coordinates.TryGetTileAtmosphere(out var tileAtmos))
+ return false;
+
+ if (tileAtmos.Hotspot.Valid)
+ return true;
+
+ var gridAtmosphere = atmosphereSystem.GetGridAtmosphere(Owner.Transform.GridID);
+
+ if (gridAtmosphere == null)
+ return false;
+
+ foreach (var (direction, adjacent) in gridAtmosphere.GetAdjacentTiles(tileAtmos.GridIndices))
+ {
+ if (adjacent.Hotspot.Valid)
+ return true;
+ }
+
+ return false;
+ }
+
public bool Close()
{
bool shouldCheckCrush = false;
- if (Owner.TryGetComponent(out ICollidableComponent? collidable) && collidable.IsColliding(Vector2.Zero, false))
+ if (_canCrush && Owner.TryGetComponent(out ICollidableComponent? collidable) && collidable.IsColliding(Vector2.Zero, false))
{
if (Safety)
return false;
@@ -336,7 +394,7 @@ namespace Content.Server.GameObjects.Components.Doors
Timer.Spawn(CloseTimeOne, async () =>
{
- if (shouldCheckCrush)
+ if (shouldCheckCrush && _canCrush)
{
CheckCrush();
}
diff --git a/Content.Server/Utility/GridTileLookupHelpers.cs b/Content.Server/Utility/GridTileLookupHelpers.cs
new file mode 100644
index 0000000000..2494815d2f
--- /dev/null
+++ b/Content.Server/Utility/GridTileLookupHelpers.cs
@@ -0,0 +1,40 @@
+#nullable enable
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using Content.Shared.Maps;
+using Robust.Server.GameObjects.EntitySystems.TileLookup;
+using Robust.Shared.GameObjects.Systems;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.Map;
+
+namespace Content.Server.Utility
+{
+ public static class GridTileLookupHelpers
+ {
+ ///
+ /// Helper that returns all entities in a turf very fast.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static IEnumerable GetEntitiesInTileFast(this TileRef turf, GridTileLookupSystem? gridTileLookup = null)
+ {
+ gridTileLookup ??= EntitySystem.Get();
+
+ return gridTileLookup.GetEntitiesIntersecting(turf.GridIndex, turf.GridIndices);
+ }
+
+ ///
+ /// Helper that returns all entities in a turf.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static IEnumerable GetEntitiesInTileFast(this MapIndices indices, GridId gridId, GridTileLookupSystem? gridTileLookup = null)
+ {
+ var turf = indices.GetTileRef(gridId);
+
+ if (turf == null)
+ return Enumerable.Empty();
+
+ return GetEntitiesInTileFast(turf.Value, gridTileLookup);
+ }
+ }
+}
diff --git a/Content.Shared/Atmos/AtmosDirection.cs b/Content.Shared/Atmos/AtmosDirection.cs
index 450a92e4df..fa0f36cc8f 100644
--- a/Content.Shared/Atmos/AtmosDirection.cs
+++ b/Content.Shared/Atmos/AtmosDirection.cs
@@ -1,13 +1,15 @@
using System;
using Robust.Shared.Maths;
+using Robust.Shared.Serialization;
namespace Content.Shared.Atmos
{
///
/// The reason we use this over is that we are going to do some heavy bitflag usage.
///
- [Flags]
- public enum AtmosDirection : byte
+ [Flags, Serializable]
+ [FlagsFor(typeof(AtmosDirectionFlags))]
+ public enum AtmosDirection
{
Invalid = 0,
North = 1 << 0,
@@ -75,6 +77,49 @@ namespace Content.Shared.Atmos
};
}
+ ///
+ /// Converts a direction to an angle, where angle is -PI to +PI.
+ ///
+ ///
+ ///
+ public static Angle ToAngle(this AtmosDirection direction)
+ {
+ return direction switch
+ {
+ AtmosDirection.East => Angle.FromDegrees(0),
+ AtmosDirection.North => Angle.FromDegrees(90),
+ AtmosDirection.West => Angle.FromDegrees(180),
+ AtmosDirection.South => Angle.FromDegrees(270),
+
+ AtmosDirection.NorthEast => Angle.FromDegrees(45),
+ AtmosDirection.NorthWest => Angle.FromDegrees(135),
+ AtmosDirection.SouthWest => Angle.FromDegrees(225),
+ AtmosDirection.SouthEast => Angle.FromDegrees(315),
+
+ _ => throw new ArgumentOutOfRangeException(nameof(direction), $"It was {direction}."),
+ };
+ }
+
+ ///
+ /// Converts an angle to a cardinal AtmosDirection
+ ///
+ ///
+ ///
+ public static AtmosDirection ToAtmosDirectionCardinal(this Angle angle)
+ {
+ return angle.GetCardinalDir().ToAtmosDirection();
+ }
+
+ ///
+ /// Converts an angle to an AtmosDirection
+ ///
+ ///
+ ///
+ public static AtmosDirection ToAtmosDirection(this Angle angle)
+ {
+ return angle.GetDir().ToAtmosDirection();
+ }
+
public static int ToIndex(this AtmosDirection direction)
{
// This will throw if you pass an invalid direction. Not this method's fault, but yours!
@@ -85,5 +130,12 @@ namespace Content.Shared.Atmos
{
return direction | other;
}
+
+ public static AtmosDirection WithoutFlag(this AtmosDirection direction, AtmosDirection other)
+ {
+ return direction & ~other;
+ }
}
+
+ public sealed class AtmosDirectionFlags { }
}
diff --git a/Content.Shared/Maps/TurfHelpers.cs b/Content.Shared/Maps/TurfHelpers.cs
index 406d91331e..7207647375 100644
--- a/Content.Shared/Maps/TurfHelpers.cs
+++ b/Content.Shared/Maps/TurfHelpers.cs
@@ -1,8 +1,11 @@
#nullable enable
using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Physics;
using Content.Shared.Utility;
+using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.Interfaces.Physics;
@@ -57,7 +60,7 @@ namespace Content.Shared.Maps
if (!mapManager.TryGetGrid(coordinates.GetGridId(entityManager), out var grid))
return null;
- if (!grid.TryGetTileRef(coordinates.ToMapIndices(entityManager, mapManager), out var tile))
+ if (!grid.TryGetTileRef(coordinates, out var tile))
return null;
return tile;
@@ -120,13 +123,40 @@ namespace Content.Shared.Maps
///
/// Helper that returns all entities in a turf.
///
- public static IEnumerable GetEntitiesInTile(this TileRef turf, bool approximate = false)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static IEnumerable GetEntitiesInTile(this TileRef turf, bool approximate = false, IEntityManager? entityManager = null)
{
- var entityManager = IoCManager.Resolve();
+ entityManager ??= IoCManager.Resolve();
return entityManager.GetEntitiesIntersecting(turf.MapIndex, GetWorldTileBox(turf), approximate);
}
+ ///
+ /// Helper that returns all entities in a turf.
+ ///
+ public static IEnumerable GetEntitiesInTile(this EntityCoordinates coordinates, bool approximate = false, IEntityManager? entityManager = null)
+ {
+ var turf = coordinates.GetTileRef();
+
+ if (turf == null)
+ return Enumerable.Empty();
+
+ return GetEntitiesInTile(turf.Value, approximate, entityManager);
+ }
+
+ ///
+ /// Helper that returns all entities in a turf.
+ ///
+ public static IEnumerable GetEntitiesInTile(this MapIndices indices, GridId gridId, bool approximate = false, IEntityManager? entityManager = null)
+ {
+ var turf = indices.GetTileRef(gridId);
+
+ if (turf == null)
+ return Enumerable.Empty();
+
+ return GetEntitiesInTile(turf.Value, approximate, entityManager);
+ }
+
///
/// Checks if a turf has something dense on it.
///
diff --git a/Resources/Prototypes/Entities/Constructible/Doors/airlock_base.yml b/Resources/Prototypes/Entities/Constructible/Doors/airlock_base.yml
index 54163a71c3..324bd96eb9 100644
--- a/Resources/Prototypes/Entities/Constructible/Doors/airlock_base.yml
+++ b/Resources/Prototypes/Entities/Constructible/Doors/airlock_base.yml
@@ -56,7 +56,6 @@
type: WiresBoundUserInterface
- type: Airtight
fixVacuum: true
- adjacentAtmosphere: true
- type: Occluder
- type: SnapGrid
offset: Center
diff --git a/Resources/Prototypes/Entities/Constructible/Doors/firelock.yml b/Resources/Prototypes/Entities/Constructible/Doors/firelock.yml
new file mode 100644
index 0000000000..e7bf98f41c
--- /dev/null
+++ b/Resources/Prototypes/Entities/Constructible/Doors/firelock.yml
@@ -0,0 +1,108 @@
+- type: entity
+ id: Firelock
+ name: firelock
+ description: Apply crowbar.
+ components:
+ - type: Clickable
+ - type: InteractionOutline
+ - type: Sprite
+ netsync: false
+ drawdepth: Mobs # They're on the same layer as mobs, perspective.
+ sprite: Constructible/Structures/Doors/firelock.rsi
+ layers:
+ - state: closed
+ map: ["enum.DoorVisualLayers.Base"]
+ - state: closed_unlit
+ shader: unshaded
+ map: ["enum.DoorVisualLayers.BaseUnlit"]
+ - state: welded
+ map: ["enum.DoorVisualLayers.BaseWelded"]
+ - state: bolted
+ shader: unshaded
+ map: ["enum.DoorVisualLayers.BaseBolted"]
+ - state: panel_open
+ map: ["enum.WiresVisualLayers.MaintenancePanel"]
+ - type: Icon
+ sprite: Constructible/Structures/Doors/firelock.rsi
+ state: closed
+ - type: Collidable
+ shapes:
+ - !type:PhysShapeAabb
+ bounds: "-0.49,-0.49,0.49,0.49" # don't want this colliding with walls or they won't close
+ mask:
+ - MobImpassable
+ layer:
+ - Opaque
+ - Impassable
+ - MobImpassable
+ - VaultImpassable
+ - SmallImpassable
+ - type: Firelock
+ - type: Appearance
+ visuals:
+ - type: AirlockVisualizer
+ open_sound: /Audio/Machines/airlock_open.ogg
+ close_sound: /Audio/Machines/airlock_close.ogg
+ deny_sound: /Audio/Machines/airlock_deny.ogg
+ animation_time: 0.6
+ - type: WiresVisualizer
+ - type: Wires
+ BoardName: "Firelock Control"
+ LayoutId: Firelock
+ - type: UserInterface
+ interfaces:
+ - key: enum.WiresUiKey.Key
+ type: WiresBoundUserInterface
+ - type: Airtight
+ fixVacuum: true
+ - type: Occluder
+ - type: SnapGrid
+ offset: Center
+ placement:
+ mode: SnapgridCenter
+
+- type: entity
+ id: FirelockGlass
+ parent: Firelock
+ name: glass firelock
+ components:
+ - type: Firelock
+ occludes: false
+ - type: Occluder
+ enabled: false
+ - type: Sprite
+ sprite: Constructible/Structures/Doors/firelock_glass.rsi
+ - type: Icon
+ sprite: Constructible/Structures/Doors/firelock_glass.rsi
+
+- type: entity
+ id: FirelockEdge
+ parent: Firelock
+ name: firelock
+ prefix: south
+ components:
+ - type: Firelock
+ occludes: false
+ canCrush: false
+ - type: Occluder
+ enabled: false
+ - type: Sprite
+ sprite: Constructible/Structures/Doors/edge_door_hazard.rsi
+ - type: Icon
+ sprite: Constructible/Structures/Doors/edge_door_hazard.rsi
+ - type: Airtight
+ fixVacuum: true
+ airBlockedDirection:
+ - South
+ - type: Collidable
+ shapes:
+ - !type:PhysShapeAabb
+ bounds: "-0.49,-0.49,-0.2,0.49" # don't want this colliding with walls or they won't close
+ mask:
+ - MobImpassable
+ layer:
+ - Opaque
+ - Impassable
+ - MobImpassable
+ - VaultImpassable
+ - SmallImpassable
diff --git a/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/alert_cold.png b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/alert_cold.png
new file mode 100644
index 0000000000..c18f592c9f
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/alert_cold.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/alert_hot.png b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/alert_hot.png
new file mode 100644
index 0000000000..3a572356dc
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/alert_hot.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/bolted.png b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/bolted.png
new file mode 100644
index 0000000000..0858c19f05
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/bolted.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/closed.png b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/closed.png
new file mode 100644
index 0000000000..8968f68da9
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/closed.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/closed_unlit.png b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/closed_unlit.png
new file mode 100644
index 0000000000..0858c19f05
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/closed_unlit.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/closing.png b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/closing.png
new file mode 100644
index 0000000000..689f02afa2
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/closing.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/closing_unlit.png b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/closing_unlit.png
new file mode 100644
index 0000000000..92b5018fb9
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/closing_unlit.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/deny.png b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/deny.png
new file mode 100644
index 0000000000..8968f68da9
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/deny.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/deny_unlit.png b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/deny_unlit.png
new file mode 100644
index 0000000000..0858c19f05
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/deny_unlit.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/door_spark.png b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/door_spark.png
new file mode 100644
index 0000000000..59ff3aed71
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/door_spark.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/locked.png b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/locked.png
new file mode 100644
index 0000000000..8968f68da9
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/locked.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/meta.json b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/meta.json
new file mode 100644
index 0000000000..8ad6942753
--- /dev/null
+++ b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/meta.json
@@ -0,0 +1,430 @@
+{
+ "version": 1,
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "license": "CC-BY-SA 3.0",
+ "copyright": "Taken from https://github.com/vgstation-coders/vgstation13/ at 38b65a605df7ae2907d6bf0d4aebc5faa1bbc561",
+ "states": [
+ {
+ "name": "alert_cold",
+ "directions": 4,
+ "delays": [
+ [
+ 0.5,
+ 0.5
+ ],
+ [
+ 0.5,
+ 0.5
+ ],
+ [
+ 0.5,
+ 0.5
+ ],
+ [
+ 0.5,
+ 0.5
+ ]
+ ]
+ },
+ {
+ "name": "alert_hot",
+ "directions": 4,
+ "delays": [
+ [
+ 0.5,
+ 0.5
+ ],
+ [
+ 0.5,
+ 0.5
+ ],
+ [
+ 0.5,
+ 0.5
+ ],
+ [
+ 0.5,
+ 0.5
+ ]
+ ]
+ },
+ {
+ "name": "bolted",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "closed",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "closed_unlit",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "closing",
+ "directions": 4,
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "closing_unlit",
+ "directions": 4,
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "deny",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "deny_unlit",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "locked",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "open",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "opening",
+ "directions": 4,
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "opening_unlit",
+ "directions": 4,
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "palert",
+ "directions": 4,
+ "delays": [
+ [
+ 0.5,
+ 0.5
+ ],
+ [
+ 0.5,
+ 0.5
+ ],
+ [
+ 0.5,
+ 0.5
+ ],
+ [
+ 0.5,
+ 0.5
+ ]
+ ]
+ },
+ {
+ "name": "panel_closing",
+ "directions": 1,
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.07,
+ 0.07,
+ 0.07,
+ 0.07,
+ 0.27
+ ]
+ ]
+ },
+ {
+ "name": "panel_open",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "panel_opening",
+ "directions": 1,
+ "delays": [
+ [
+ 0.2,
+ 0.07,
+ 0.07,
+ 0.07,
+ 0.07,
+ 0.07,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "welded",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "welded_open",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/open.png b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/open.png
new file mode 100644
index 0000000000..3f0b3ef549
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/open.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/opening.png b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/opening.png
new file mode 100644
index 0000000000..7cf50771cd
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/opening.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/opening_unlit.png b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/opening_unlit.png
new file mode 100644
index 0000000000..92b5018fb9
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/opening_unlit.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/palert.png b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/palert.png
new file mode 100644
index 0000000000..3d6343358e
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/palert.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/panel_closing.png b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/panel_closing.png
new file mode 100644
index 0000000000..ae69b8aded
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/panel_closing.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/panel_open.png b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/panel_open.png
new file mode 100644
index 0000000000..5f3bfeae15
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/panel_open.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/panel_opening.png b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/panel_opening.png
new file mode 100644
index 0000000000..8271b80b11
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/panel_opening.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/welded.png b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/welded.png
new file mode 100644
index 0000000000..e6bd2ff64f
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/welded.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/welded_open.png b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/welded_open.png
new file mode 100644
index 0000000000..f0d674692d
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/edge_door_hazard.rsi/welded_open.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/bolted.png b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/bolted.png
new file mode 100644
index 0000000000..53bdd1ccf4
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/bolted.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/closed.png b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/closed.png
new file mode 100644
index 0000000000..8c90b693a4
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/closed.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/closed_unlit.png b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/closed_unlit.png
new file mode 100644
index 0000000000..1d33d3f366
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/closed_unlit.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/closing.png b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/closing.png
new file mode 100644
index 0000000000..4347bf8afe
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/closing.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/closing_unlit.png b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/closing_unlit.png
new file mode 100644
index 0000000000..d0b97a4029
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/closing_unlit.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/deny.png b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/deny.png
new file mode 100644
index 0000000000..225d45a82a
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/deny.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/deny_unlit.png b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/deny_unlit.png
new file mode 100644
index 0000000000..c584771ed1
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/deny_unlit.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/frame1.png b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/frame1.png
new file mode 100644
index 0000000000..458698515f
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/frame1.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/frame2.png b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/frame2.png
new file mode 100644
index 0000000000..67f782bd89
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/frame2.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/frame3.png b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/frame3.png
new file mode 100644
index 0000000000..5421154834
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/frame3.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/frame4.png b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/frame4.png
new file mode 100644
index 0000000000..6252d37d1e
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/frame4.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/locked.png b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/locked.png
new file mode 100644
index 0000000000..12ccff5cb0
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/locked.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/meta.json b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/meta.json
new file mode 100644
index 0000000000..08653842b9
--- /dev/null
+++ b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/meta.json
@@ -0,0 +1,227 @@
+{
+ "version": 1,
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "license": "CC-BY-SA 3.0",
+ "copyright": "Taken from https://github.com/tgstation/tgstation at 04e43d8c1d5097fdb697addd4395fb849dd341bd",
+ "states": [
+ {
+ "name": "bolted",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "closed",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "closed_unlit",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "closing",
+ "directions": 1,
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "closing_unlit",
+ "directions": 1,
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "deny",
+ "directions": 1,
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "deny_unlit",
+ "directions": 1,
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "frame1",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "frame2",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "frame3",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "frame4",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "locked",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "open",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "opening",
+ "directions": 1,
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "opening_unlit",
+ "directions": 1,
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "panel_closing",
+ "directions": 1,
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.07,
+ 0.07,
+ 0.07,
+ 0.07,
+ 0.27
+ ]
+ ]
+ },
+ {
+ "name": "panel_open",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "panel_opening",
+ "directions": 1,
+ "delays": [
+ [
+ 0.2,
+ 0.07,
+ 0.07,
+ 0.07,
+ 0.07,
+ 0.07,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "welded",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "welded_open",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/open.png b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/open.png
new file mode 100644
index 0000000000..a89b493a8b
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/open.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/opening.png b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/opening.png
new file mode 100644
index 0000000000..79a05996ad
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/opening.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/opening_unlit.png b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/opening_unlit.png
new file mode 100644
index 0000000000..816c648cc4
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/opening_unlit.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/panel_closing.png b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/panel_closing.png
new file mode 100644
index 0000000000..ae69b8aded
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/panel_closing.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/panel_open.png b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/panel_open.png
new file mode 100644
index 0000000000..5f3bfeae15
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/panel_open.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/panel_opening.png b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/panel_opening.png
new file mode 100644
index 0000000000..8271b80b11
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/panel_opening.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/welded.png b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/welded.png
new file mode 100644
index 0000000000..54de288e54
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/welded.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/welded_open.png b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/welded_open.png
new file mode 100644
index 0000000000..d5b1d509b5
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock.rsi/welded_open.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/bolted.png b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/bolted.png
new file mode 100644
index 0000000000..53bdd1ccf4
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/bolted.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/closed.png b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/closed.png
new file mode 100644
index 0000000000..602b83df5b
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/closed.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/closed_unlit.png b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/closed_unlit.png
new file mode 100644
index 0000000000..1d33d3f366
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/closed_unlit.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/closing.png b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/closing.png
new file mode 100644
index 0000000000..6718ae08e9
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/closing.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/closing_unlit.png b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/closing_unlit.png
new file mode 100644
index 0000000000..d0b97a4029
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/closing_unlit.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/deny.png b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/deny.png
new file mode 100644
index 0000000000..a2b151c140
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/deny.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/deny_unlit.png b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/deny_unlit.png
new file mode 100644
index 0000000000..c584771ed1
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/deny_unlit.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/locked.png b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/locked.png
new file mode 100644
index 0000000000..5c87415ec1
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/locked.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/meta.json b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/meta.json
new file mode 100644
index 0000000000..c4328c5639
--- /dev/null
+++ b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/meta.json
@@ -0,0 +1,191 @@
+{
+ "version": 1,
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "license": "CC-BY-SA 3.0",
+ "copyright": "Taken from https://github.com/tgstation/tgstation at 04e43d8c1d5097fdb697addd4395fb849dd341bd",
+ "states": [
+ {
+ "name": "bolted",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "closed",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "closed_unlit",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "closing",
+ "directions": 1,
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "closing_unlit",
+ "directions": 1,
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "deny",
+ "directions": 1,
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "deny_unlit",
+ "directions": 1,
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "locked",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "open",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "opening",
+ "directions": 1,
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "opening_unlit",
+ "directions": 1,
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "panel_closing",
+ "directions": 1,
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.07,
+ 0.07,
+ 0.07,
+ 0.07,
+ 0.27
+ ]
+ ]
+ },
+ {
+ "name": "panel_open",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "panel_opening",
+ "directions": 1,
+ "delays": [
+ [
+ 0.2,
+ 0.07,
+ 0.07,
+ 0.07,
+ 0.07,
+ 0.07,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "welded",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "welded_open",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ }
+ ]
+}
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/open.png b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/open.png
new file mode 100644
index 0000000000..6fcb5b059b
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/open.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/opening.png b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/opening.png
new file mode 100644
index 0000000000..113ddab7b7
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/opening.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/opening_unlit.png b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/opening_unlit.png
new file mode 100644
index 0000000000..816c648cc4
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/opening_unlit.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/panel_closing.png b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/panel_closing.png
new file mode 100644
index 0000000000..ae69b8aded
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/panel_closing.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/panel_open.png b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/panel_open.png
new file mode 100644
index 0000000000..5f3bfeae15
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/panel_open.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/panel_opening.png b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/panel_opening.png
new file mode 100644
index 0000000000..8271b80b11
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/panel_opening.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/welded.png b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/welded.png
new file mode 100644
index 0000000000..54de288e54
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/welded.png differ
diff --git a/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/welded_open.png b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/welded_open.png
new file mode 100644
index 0000000000..d5b1d509b5
Binary files /dev/null and b/Resources/Textures/Constructible/Structures/Doors/firelock_glass.rsi/welded_open.png differ
diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings
index f0a586f719..be0b3eff72 100644
--- a/SpaceStation14.sln.DotSettings
+++ b/SpaceStation14.sln.DotSettings
@@ -66,6 +66,7 @@
<data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="Lidgren.Network" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data>
True
True
+ True
True
True
True