]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Partial atmos refactor (#22521)
authorLeon Friedrich <60421075+ElectroJr@users.noreply.github.com>
Sat, 23 Mar 2024 16:34:56 +0000 (03:34 +1100)
committerGitHub <noreply@github.com>
Sat, 23 Mar 2024 16:34:56 +0000 (03:34 +1100)
* Reduce atmos component queries

* Remove method events

* Cache airtight data

* Make MolesArchived nullable

* Fix airtight cache

* only get tile def once

* Immutable mixtures

* firelock queries

* misc

* misc cleanup

* Trim disconnected tiles

* Fix merge issues and bugs

* Why does the PR keep increasing in scope

* debug overlay

* Fix bugs

* Fix test, remove unused events

* Add setmapatmos command

* Fix overlays

* Add map check

* A

* Resolve conflicts with #26102

* Remove some obsolete methods

43 files changed:
Content.Client/Atmos/Overlays/GasTileOverlay.cs
Content.Client/Mapping/MappingSystem.cs
Content.IntegrationTests/Tests/Body/LungTest.cs
Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs
Content.Server/Atmos/Commands/SetMapAtmosCommand.cs [new file with mode: 0644]
Content.Server/Atmos/Components/AirtightComponent.cs
Content.Server/Atmos/Components/GridAtmosphereComponent.cs
Content.Server/Atmos/Components/MapAtmosphereComponent.cs
Content.Server/Atmos/EntitySystems/AirtightSystem.cs
Content.Server/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs
Content.Server/Atmos/EntitySystems/AtmosObstructionEnumerator.cs [deleted file]
Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs
Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs
Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs
Content.Server/Atmos/EntitySystems/AtmosphereSystem.HighPressureDelta.cs
Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs
Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs
Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs
Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs
Content.Server/Atmos/EntitySystems/AtmosphereSystem.Superconductivity.cs
Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs
Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs
Content.Server/Atmos/EntitySystems/AutomaticAtmosSystem.cs
Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs
Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs
Content.Server/Atmos/GasMixture.cs
Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs
Content.Server/Atmos/Piping/Unary/EntitySystems/GasCondenserSystem.cs
Content.Server/Atmos/Serialization/TileAtmosCollectionSerializer.cs
Content.Server/Atmos/TileAtmosphere.cs
Content.Server/Body/Systems/LungSystem.cs
Content.Server/Chemistry/ReagentEffects/ModifyLungGas.cs
Content.Server/Construction/Conditions/ComponentInTile.cs
Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs
Content.Server/Parallax/BiomeSystem.cs
Content.Server/Power/Generator/GasPowerReceiverSystem.cs
Content.Server/Salvage/SpawnSalvageMissionJob.cs
Content.Server/Spreader/SpreaderSystem.cs
Content.Shared/Atmos/Atmospherics.cs
Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs
Content.Shared/Maps/ContentTileDefinition.cs
Content.Shared/Maps/TurfHelpers.cs
Resources/Locale/en-US/atmos/commands.ftl [new file with mode: 0644]

index ef65d43fe85ecaa019955524eb1013323b61de9b..f4dc274a4e546ce05d4c1848f2714f542fe958f6 100644 (file)
@@ -8,7 +8,6 @@ using Robust.Client.GameObjects;
 using Robust.Client.Graphics;
 using Robust.Client.ResourceManagement;
 using Robust.Shared.Enums;
-using Robust.Shared.Graphics;
 using Robust.Shared.Graphics.RSI;
 using Robust.Shared.Map;
 using Robust.Shared.Map.Components;
@@ -23,7 +22,7 @@ namespace Content.Client.Atmos.Overlays
         private readonly IEntityManager _entManager;
         private readonly IMapManager _mapManager;
 
-        public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities;
+        public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld;
         private readonly ShaderInstance _shader;
 
         // Gas overlays
@@ -79,7 +78,8 @@ namespace Content.Client.Atmos.Overlays
                         var rsi = resourceCache.GetResource<RSIResource>(animated.RsiPath).RSI;
                         var stateId = animated.RsiState;
 
-                        if (!rsi.TryGetState(stateId, out var state)) continue;
+                        if (!rsi.TryGetState(stateId, out var state))
+                            continue;
 
                         _frames[i] = state.GetFrames(RsiDirection.South);
                         _frameDelays[i] = state.GetDelays();
@@ -111,7 +111,8 @@ namespace Content.Client.Atmos.Overlays
             for (var i = 0; i < _gasCount; i++)
             {
                 var delays = _frameDelays[i];
-                if (delays.Length == 0) continue;
+                if (delays.Length == 0)
+                    continue;
 
                 var frameCount = _frameCounter[i];
                 _timer[i] += args.DeltaSeconds;
@@ -127,7 +128,8 @@ namespace Content.Client.Atmos.Overlays
             for (var i = 0; i < FireStates; i++)
             {
                 var delays = _fireFrameDelays[i];
-                if (delays.Length == 0) continue;
+                if (delays.Length == 0)
+                    continue;
 
                 var frameCount = _fireFrameCounter[i];
                 _fireTimer[i] += args.DeltaSeconds;
@@ -161,26 +163,10 @@ namespace Content.Client.Atmos.Overlays
             var mapUid = _mapManager.GetMapEntityId(args.MapId);
 
             if (_entManager.TryGetComponent<MapAtmosphereComponent>(mapUid, out var atmos))
-            {
-                var bottomLeft = args.WorldAABB.BottomLeft.Floored();
-                var topRight = args.WorldAABB.TopRight.Ceiled();
-
-                for (var x = bottomLeft.X; x <= topRight.X; x++)
-                {
-                    for (var y = bottomLeft.Y; y <= topRight.Y; y++)
-                    {
-                        var tilePosition = new Vector2(x, y);
-
-                        for (var i = 0; i < atmos.OverlayData.Opacity.Length; i++)
-                        {
-                            var opacity = atmos.OverlayData.Opacity[i];
+                DrawMapOverlay(drawHandle, args, mapUid, atmos);
 
-                            if (opacity > 0)
-                                args.WorldHandle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity));
-                        }
-                    }
-                }
-            }
+            if (args.Space != OverlaySpace.WorldSpaceEntities)
+                return;
 
             // TODO: WorldBounds callback.
             _mapManager.FindGridsIntersecting(args.MapId, args.WorldAABB, ref gridState,
@@ -265,5 +251,41 @@ namespace Content.Client.Atmos.Overlays
             drawHandle.UseShader(null);
             drawHandle.SetTransform(Matrix3.Identity);
         }
+
+        private void DrawMapOverlay(
+            DrawingHandleWorld handle,
+            OverlayDrawArgs args,
+            EntityUid map,
+            MapAtmosphereComponent atmos)
+        {
+            var mapGrid = _entManager.HasComponent<MapGridComponent>(map);
+
+            // map-grid atmospheres get drawn above grids
+            if (mapGrid && args.Space != OverlaySpace.WorldSpaceEntities)
+                return;
+
+            // Normal map atmospheres get drawn below grids
+            if (!mapGrid && args.Space != OverlaySpace.WorldSpaceBelowWorld)
+                return;
+
+            var bottomLeft = args.WorldAABB.BottomLeft.Floored();
+            var topRight = args.WorldAABB.TopRight.Ceiled();
+
+            for (var x = bottomLeft.X; x <= topRight.X; x++)
+            {
+                for (var y = bottomLeft.Y; y <= topRight.Y; y++)
+                {
+                    var tilePosition = new Vector2(x, y);
+
+                    for (var i = 0; i < atmos.OverlayData.Opacity.Length; i++)
+                    {
+                        var opacity = atmos.OverlayData.Opacity[i];
+
+                        if (opacity > 0)
+                            handle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity));
+                    }
+                }
+            }
+        }
     }
 }
index 4456be36a65252d362ad97ead95c881115850011..8daf193dfeb16872a2a4d2c68b29eac343e216d6 100644 (file)
@@ -83,7 +83,7 @@ public sealed partial class MappingSystem : EntitySystem
             if (tileDef is not ContentTileDefinition contentTileDef)
                 return;
 
-            var tileIcon = contentTileDef.IsSpace
+            var tileIcon = contentTileDef.MapAtmosphere
                 ? _spaceIcon
                 : new Texture(contentTileDef.Sprite!.Value);
 
index d0325480acddfc77d5baf51073045937615409fc..f2e19849b0022528b20edffb56005297ffd49ae8 100644 (file)
@@ -128,7 +128,7 @@ namespace Content.IntegrationTests.Tests.Body
                     metaSys.Update(1.0f);
                     metaSys.Update(1.0f);
                     respSys.Update(2.0f);
-                    Assert.That(GetMapMoles(), Is.EqualTo(startingMoles).Within(0.0001));
+                    Assert.That(GetMapMoles(), Is.EqualTo(startingMoles).Within(0.0002));
                 });
             }
 
index 84e1afaf45cdda063f41fe355162b5516709b9b1..88448e7b80023bf6fa7d2fa78783e932122724bf 100644 (file)
@@ -1006,15 +1006,10 @@ public abstract partial class InteractionTest
         await Server.WaitPost(() =>
         {
             var atmosSystem = SEntMan.System<AtmosphereSystem>();
-            var atmos = SEntMan.EnsureComponent<MapAtmosphereComponent>(target);
             var moles = new float[Atmospherics.AdjustedNumberOfGases];
             moles[(int) Gas.Oxygen] = 21.824779f;
             moles[(int) Gas.Nitrogen] = 82.10312f;
-            atmosSystem.SetMapAtmosphere(target, false, new GasMixture(2500)
-            {
-                Temperature = 293.15f,
-                Moles = moles,
-            }, atmos);
+            atmosSystem.SetMapAtmosphere(target, false, new GasMixture(moles, Atmospherics.T20C));
         });
     }
 
diff --git a/Content.Server/Atmos/Commands/SetMapAtmosCommand.cs b/Content.Server/Atmos/Commands/SetMapAtmosCommand.cs
new file mode 100644 (file)
index 0000000..6f04cfb
--- /dev/null
@@ -0,0 +1,94 @@
+using Content.Server.Administration;
+using Content.Server.Atmos.Components;
+using Content.Server.Atmos.EntitySystems;
+using Content.Shared.Administration;
+using Content.Shared.Atmos;
+using Robust.Shared.Console;
+using Robust.Shared.Map;
+
+namespace Content.Server.Atmos.Commands;
+
+[AdminCommand(AdminFlags.Admin)]
+public sealed class AddMapAtmosCommand : LocalizedCommands
+{
+    [Dependency] private readonly IEntityManager _entities = default!;
+    [Dependency] private readonly IMapManager _map = default!;
+
+    private const string _cmd = "cmd-set-map-atmos";
+    public override string Command => "setmapatmos";
+    public override string Description => Loc.GetString($"{_cmd}-desc");
+    public override string Help => Loc.GetString($"{_cmd}-help");
+
+    public override void Execute(IConsoleShell shell, string argStr, string[] args)
+    {
+        if (args.Length < 2)
+        {
+            shell.WriteLine(Help);
+            return;
+        }
+
+        int.TryParse(args[0], out var id);
+        var map = _map.GetMapEntityId(new MapId(id));
+        if (!map.IsValid())
+        {
+            shell.WriteError(Loc.GetString("cmd-parse-failure-mapid",  ("arg", args[0])));
+            return;
+        }
+
+        if (!bool.TryParse(args[1], out var space))
+        {
+            shell.WriteError(Loc.GetString("cmd-parse-failure-bool", ("arg", args[1])));
+            return;
+        }
+
+        if (space || args.Length < 4)
+        {
+            _entities.RemoveComponent<MapAtmosphereComponent>(map);
+            shell.WriteLine(Loc.GetString($"{_cmd}-removed", ("map", id)));
+            return;
+        }
+
+        if (!float.TryParse(args[2], out var temp))
+        {
+            shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[2])));
+            return;
+        }
+
+        var mix = new GasMixture(Atmospherics.CellVolume) {Temperature = Math.Min(temp, Atmospherics.TCMB)};
+        for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
+        {
+            if (args.Length == 3 + i)
+                break;
+
+            if (!float.TryParse(args[3+i], out var moles))
+            {
+                shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[3+i])));
+                return;
+            }
+
+            mix.AdjustMoles(i, moles);
+        }
+
+        var atmos = _entities.EntitySysManager.GetEntitySystem<AtmosphereSystem>();
+        atmos.SetMapAtmosphere(map, space, mix);
+        shell.WriteLine(Loc.GetString($"{_cmd}-updated", ("map", id)));
+    }
+
+    public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
+    {
+        if (args.Length == 1)
+            return CompletionResult.FromHintOptions(CompletionHelper.MapIds(_entities), Loc.GetString($"{_cmd}-hint-map"));
+
+        if (args.Length == 2)
+            return CompletionResult.FromHintOptions(new[]{ "false", "true"}, Loc.GetString($"{_cmd}-hint-space"));
+
+        if (!bool.TryParse(args[1], out var space) || space)
+            return CompletionResult.Empty;
+
+        if (args.Length == 3)
+            return CompletionResult.FromHint(Loc.GetString($"{_cmd}-hint-temp"));
+
+        var gas = (Gas) args.Length - 4;
+        return CompletionResult.FromHint(Loc.GetString($"{_cmd}-hint-gas" , ("gas", gas.ToString())));
+    }
+}
index 897981724c9136c5a6700145c50b1d8e71af11a0..ca107eafbe8a3191a0aa72fe6cfb92a6831e1606 100644 (file)
@@ -1,9 +1,10 @@
+using Content.Server.Atmos.EntitySystems;
 using Content.Shared.Atmos;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
 
 namespace Content.Server.Atmos.Components
 {
-    [RegisterComponent]
+    [RegisterComponent, Access(typeof(AirtightSystem))]
     public sealed partial class AirtightComponent : Component
     {
         public (EntityUid Grid, Vector2i Tile) LastPosition { get; set; }
@@ -29,6 +30,7 @@ namespace Content.Server.Atmos.Components
         [DataField("noAirWhenFullyAirBlocked")]
         public bool NoAirWhenFullyAirBlocked { get; set; } = true;
 
+        [Access(Other = AccessPermissions.ReadWriteExecute)]
         public AtmosDirection AirBlockedDirection => (AtmosDirection)CurrentAirBlockedDirection;
     }
 }
index 7fcd63bc5d89a6998d2e18b0fbaeaa34c3f108b5..e682fd09644efb0c52743f56caaeab552fba1865 100644 (file)
@@ -28,6 +28,9 @@ namespace Content.Server.Atmos.Components
         [IncludeDataField(customTypeSerializer:typeof(TileAtmosCollectionSerializer))]
         public Dictionary<Vector2i, TileAtmosphere> Tiles = new(1000);
 
+        [ViewVariables]
+        public HashSet<TileAtmosphere> MapTiles = new(1000);
+
         [ViewVariables]
         public readonly HashSet<TileAtmosphere> ActiveTiles = new(1000);
 
@@ -80,7 +83,10 @@ namespace Content.Server.Atmos.Components
         public readonly HashSet<Vector2i> InvalidatedCoords = new(1000);
 
         [ViewVariables]
-        public readonly Queue<Vector2i> CurrentRunInvalidatedCoordinates = new();
+        public readonly Queue<TileAtmosphere> CurrentRunInvalidatedTiles = new();
+
+        [ViewVariables]
+        public readonly List<TileAtmosphere> PossiblyDisconnectedTiles = new(100);
 
         [ViewVariables]
         public int InvalidatedCoordsCount => InvalidatedCoords.Count;
index bbf5ea6403e572e99090846f71bad1ccf3e52c09..6bdef901d448607ef7a43c79e14c647be6a5aa5b 100644 (file)
@@ -12,12 +12,14 @@ public sealed partial class MapAtmosphereComponent : SharedMapAtmosphereComponen
     /// <summary>
     ///     The default GasMixture a map will have. Space mixture by default.
     /// </summary>
-    [DataField("mixture"), ViewVariables(VVAccess.ReadWrite)]
-    public GasMixture? Mixture = GasMixture.SpaceGas;
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public GasMixture Mixture = GasMixture.SpaceGas;
 
     /// <summary>
     ///     Whether empty tiles will be considered space or not.
     /// </summary>
-    [DataField("space"), ViewVariables(VVAccess.ReadWrite)]
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
     public bool Space = true;
+
+    public SharedGasTileOverlaySystem.GasOverlayData Overlay;
 }
index 97dccbaabb7317ab834994805c0f81dabbef9571..548d6a36926b76f8b9b3005290ecaf4d83468b3b 100644 (file)
@@ -2,7 +2,6 @@ using Content.Server.Atmos.Components;
 using Content.Server.Explosion.EntitySystems;
 using Content.Shared.Atmos;
 using JetBrains.Annotations;
-using Robust.Shared.Map;
 using Robust.Shared.Map.Components;
 
 namespace Content.Server.Atmos.EntitySystems
@@ -10,7 +9,7 @@ namespace Content.Server.Atmos.EntitySystems
     [UsedImplicitly]
     public sealed class AirtightSystem : EntitySystem
     {
-        [Dependency] private readonly IMapManager _mapManager = default!;
+        [Dependency] private readonly SharedTransformSystem _transform = default!;
         [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
         [Dependency] private readonly ExplosionSystem _explosionSystem = default!;
 
@@ -121,19 +120,16 @@ namespace Content.Server.Atmos.EntitySystems
             if (!xform.Anchored || !TryComp(xform.GridUid, out MapGridComponent? grid))
                 return;
 
-            airtight.LastPosition = (xform.GridUid.Value, grid.TileIndicesFor(xform.Coordinates));
-            InvalidatePosition(airtight.LastPosition.Item1, airtight.LastPosition.Item2, airtight.FixVacuum && !airtight.AirBlocked);
+            var indices = _transform.GetGridTilePositionOrDefault((ent, xform), grid);
+            airtight.LastPosition = (xform.GridUid.Value, indices);
+            InvalidatePosition((xform.GridUid.Value, grid), indices);
         }
 
-        public void InvalidatePosition(EntityUid gridId, Vector2i pos, bool fixVacuum = false)
+        public void InvalidatePosition(Entity<MapGridComponent?> grid, Vector2i pos)
         {
-            if (!TryComp(gridId, out MapGridComponent? grid))
-                return;
-
             var query = EntityManager.GetEntityQuery<AirtightComponent>();
-            _explosionSystem.UpdateAirtightMap(gridId, pos, grid, query);
-            // TODO make atmos system use query
-            _atmosphereSystem.InvalidateTile(gridId, pos);
+            _explosionSystem.UpdateAirtightMap(grid, pos, grid, query);
+            _atmosphereSystem.InvalidateTile(grid.Owner, pos);
         }
 
         private AtmosDirection Rotate(AtmosDirection myDirection, Angle myAngle)
index 4af32fce58f22d5b66a3e94be16a46178ece0b5a..c0284f26c90a1a3c6ea17290df52e9d49eb8b7a2 100644 (file)
@@ -97,22 +97,19 @@ namespace Content.Server.Atmos.EntitySystems
             }
         }
 
-        private AtmosDebugOverlayData ConvertTileToData(TileAtmosphere? tile)
+        private AtmosDebugOverlayData? ConvertTileToData(TileAtmosphere tile)
         {
-            if (tile == null)
-                return default;
-
             return new AtmosDebugOverlayData(
                 tile.GridIndices,
                 tile.Air?.Temperature ?? default,
                 tile.Air?.Moles,
                 tile.PressureDirection,
                 tile.LastPressureDirection,
-                tile.BlockedAirflow,
+                tile.AirtightData.BlockedDirections,
                 tile.ExcitedGroup?.GetHashCode(),
                 tile.Space,
-                false,
-                false);
+                tile.MapAtmosphere,
+                tile.NoGridTile);
         }
 
         public override void Update(float frameTime)
diff --git a/Content.Server/Atmos/EntitySystems/AtmosObstructionEnumerator.cs b/Content.Server/Atmos/EntitySystems/AtmosObstructionEnumerator.cs
deleted file mode 100644 (file)
index aed009e..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-using Content.Server.Atmos.Components;
-using Robust.Shared.Map;
-using Robust.Shared.Map.Enumerators;
-
-namespace Content.Server.Atmos.EntitySystems;
-
-public struct AtmosObstructionEnumerator
-{
-    private AnchoredEntitiesEnumerator _enumerator;
-    private EntityQuery<AirtightComponent> _query;
-
-    public AtmosObstructionEnumerator(AnchoredEntitiesEnumerator enumerator, EntityQuery<AirtightComponent> query)
-    {
-        _enumerator = enumerator;
-        _query = query;
-    }
-
-    public bool MoveNext([NotNullWhen(true)] out AirtightComponent? airtight)
-    {
-        if (!_enumerator.MoveNext(out var uid))
-        {
-            airtight = null;
-            return false;
-        }
-
-        // No rider, it makes it uglier.
-        // ReSharper disable once ConvertIfStatementToReturnStatement
-        if (!_query.TryGetComponent(uid.Value, out airtight))
-        {
-            // ReSharper disable once TailRecursiveCall
-            return MoveNext(out airtight);
-        }
-
-        return true;
-    }
-}
index 310e602336a1f8d31b1e1aa45d15978b70089e6a..cece99cacf64da3d641cc96b6dee8efd61e78552 100644 (file)
@@ -85,10 +85,10 @@ public partial class AtmosphereSystem
         return ev.Mixtures!;
     }
 
-    public void InvalidateTile(EntityUid gridUid, Vector2i tile)
+    public void InvalidateTile(Entity<GridAtmosphereComponent?> entity, Vector2i tile)
     {
-        var ev = new InvalidateTileMethodEvent(gridUid, tile);
-        RaiseLocalEvent(gridUid, ref ev);
+        if (_atmosQuery.Resolve(entity.Owner, ref entity.Comp, false))
+            entity.Comp.InvalidatedCoords.Add(tile);
     }
 
     public GasMixture?[]? GetTileMixtures(EntityUid? gridUid, EntityUid? mapUid, List<Vector2i> tiles, bool excite = false)
@@ -176,11 +176,11 @@ public partial class AtmosphereSystem
 
     public bool IsTileAirBlocked(EntityUid gridUid, Vector2i tile, AtmosDirection directions = AtmosDirection.All, MapGridComponent? mapGridComp = null)
     {
-        var ev = new IsTileAirBlockedMethodEvent(gridUid, tile, directions, mapGridComp);
-        RaiseLocalEvent(gridUid, ref ev);
+        if (!Resolve(gridUid, ref mapGridComp))
+            return false;
 
-        // If nothing handled the event, it'll default to true.
-        return ev.Result;
+        var data = GetAirtightData(gridUid, mapGridComp, tile);
+        return data.BlockedDirections.IsFlagSet(directions);
     }
 
     public bool IsTileSpace(EntityUid? gridUid, EntityUid? mapUid, Vector2i tile, MapGridComponent? mapGridComp = null)
@@ -231,12 +231,6 @@ public partial class AtmosphereSystem
         return ev.Result ?? Enumerable.Empty<GasMixture>();
     }
 
-    public void UpdateAdjacent(EntityUid gridUid, Vector2i tile, MapGridComponent? mapGridComp = null)
-    {
-        var ev = new UpdateAdjacentMethodEvent(gridUid, tile, mapGridComp);
-        RaiseLocalEvent(gridUid, ref ev);
-    }
-
     public void HotspotExpose(EntityUid gridUid, Vector2i tile, float exposedTemperature, float exposedVolume,
         EntityUid? sparkSourceUid = null, bool soh = false)
     {
@@ -259,12 +253,6 @@ public partial class AtmosphereSystem
         return ev.Result;
     }
 
-    public void FixTileVacuum(EntityUid gridUid, Vector2i tile)
-    {
-        var ev = new FixTileVacuumMethodEvent(gridUid, tile);
-        RaiseLocalEvent(gridUid, ref ev);
-    }
-
     public void AddPipeNet(EntityUid gridUid, PipeNet pipeNet)
     {
         var ev = new AddPipeNetMethodEvent(gridUid, pipeNet);
@@ -307,9 +295,6 @@ public partial class AtmosphereSystem
     [ByRefEvent] private record struct GetAllMixturesMethodEvent
         (EntityUid Grid, bool Excite = false, IEnumerable<GasMixture>? Mixtures = null, bool Handled = false);
 
-    [ByRefEvent] private record struct InvalidateTileMethodEvent
-        (EntityUid Grid, Vector2i Tile, bool Handled = false);
-
     [ByRefEvent] private record struct GetTileMixturesMethodEvent
         (EntityUid? GridUid, EntityUid? MapUid, List<Vector2i> Tiles, bool Excite = false, GasMixture?[]? Mixtures = null, bool Handled = false);
 
@@ -319,16 +304,6 @@ public partial class AtmosphereSystem
     [ByRefEvent] private record struct ReactTileMethodEvent
         (EntityUid GridId, Vector2i Tile, ReactionResult Result = default, bool Handled = false);
 
-    [ByRefEvent] private record struct IsTileAirBlockedMethodEvent
-        (EntityUid Grid, Vector2i Tile, AtmosDirection Direction = AtmosDirection.All, MapGridComponent? MapGridComponent = null, bool Result = false, bool Handled = false)
-    {
-        /// <summary>
-        ///     True if one of the enabled blockers has <see cref="AirtightComponent.NoAirWhenFullyAirBlocked"/>. Note
-        ///     that this does not actually check if all directions are blocked.
-        /// </summary>
-        public bool NoAir = false;
-    }
-
     [ByRefEvent] private record struct IsTileSpaceMethodEvent
         (EntityUid? Grid, EntityUid? Map, Vector2i Tile, MapGridComponent? MapGridComponent = null, bool Result = true, bool Handled = false);
 
@@ -339,9 +314,6 @@ public partial class AtmosphereSystem
         (EntityUid Grid, Vector2i Tile, bool IncludeBlocked, bool Excite,
             IEnumerable<GasMixture>? Result = null, bool Handled = false);
 
-    [ByRefEvent] private record struct UpdateAdjacentMethodEvent
-        (EntityUid Grid, Vector2i Tile, MapGridComponent? MapGridComponent = null, bool Handled = false);
-
     [ByRefEvent] private record struct HotspotExposeMethodEvent
         (EntityUid Grid, EntityUid? SparkSourceUid, Vector2i Tile, float ExposedTemperature, float ExposedVolume, bool soh, bool Handled = false);
 
@@ -351,9 +323,6 @@ public partial class AtmosphereSystem
     [ByRefEvent] private record struct IsHotspotActiveMethodEvent
         (EntityUid Grid, Vector2i Tile, bool Result = false, bool Handled = false);
 
-    [ByRefEvent] private record struct FixTileVacuumMethodEvent
-        (EntityUid Grid, Vector2i Tile, bool Handled = false);
-
     [ByRefEvent] private record struct AddPipeNetMethodEvent
         (EntityUid Grid, PipeNet PipeNet, bool Handled = false);
 
index 1d809dcd0320766fce855c81d7c1c627904aa443..de4c9199cf72ccd5ef273b9f89664ed404c9c47e 100644 (file)
@@ -72,7 +72,8 @@ namespace Content.Server.Atmos.EntitySystems
 
             var tileSize = excitedGroup.Tiles.Count;
 
-            if (excitedGroup.Disposed) return;
+            if (excitedGroup.Disposed)
+                return;
 
             if (tileSize == 0)
             {
@@ -98,7 +99,9 @@ namespace Content.Server.Atmos.EntitySystems
 
             foreach (var tile in excitedGroup.Tiles)
             {
-                if (tile?.Air == null) continue;
+                if (tile?.Air == null)
+                    continue;
+
                 tile.Air.CopyFromMutable(combined);
                 InvalidateVisuals(tile.GridIndex, tile.GridIndices);
             }
@@ -106,21 +109,23 @@ namespace Content.Server.Atmos.EntitySystems
             excitedGroup.BreakdownCooldown = 0;
         }
 
-        private void ExcitedGroupDismantle(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup, bool unexcite = true)
+        /// <summary>
+        /// This de-activates and removes all tiles in an excited group.
+        /// </summary>
+        private void DeactivateGroupTiles(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup)
         {
             foreach (var tile in excitedGroup.Tiles)
             {
                 tile.ExcitedGroup = null;
-
-                if (!unexcite)
-                    continue;
-
                 RemoveActiveTile(gridAtmosphere, tile);
             }
 
             excitedGroup.Tiles.Clear();
         }
 
+        /// <summary>
+        /// This removes an excited group without de-activating its tiles.
+        /// </summary>
         private void ExcitedGroupDispose(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup)
         {
             if (excitedGroup.Disposed)
@@ -129,9 +134,14 @@ namespace Content.Server.Atmos.EntitySystems
             DebugTools.Assert(gridAtmosphere.ExcitedGroups.Contains(excitedGroup), "Grid Atmosphere does not contain Excited Group!");
 
             excitedGroup.Disposed = true;
-
             gridAtmosphere.ExcitedGroups.Remove(excitedGroup);
-            ExcitedGroupDismantle(gridAtmosphere, excitedGroup, false);
+
+            foreach (var tile in excitedGroup.Tiles)
+            {
+                tile.ExcitedGroup = null;
+            }
+
+            excitedGroup.Tiles.Clear();
         }
     }
 }
index 1f1a208b24b07063fee7041924d991564fbc6b95..d43cc81b0f8be9fe265e6f401548aecd35f712e0 100644 (file)
@@ -14,6 +14,7 @@ public sealed partial class AtmosphereSystem
     private void InitializeGridAtmosphere()
     {
         SubscribeLocalEvent<GridAtmosphereComponent, ComponentInit>(OnGridAtmosphereInit);
+        SubscribeLocalEvent<GridAtmosphereComponent, ComponentStartup>(OnGridAtmosphereStartup);
         SubscribeLocalEvent<GridAtmosphereComponent, ComponentRemove>(OnAtmosphereRemove);
         SubscribeLocalEvent<GridAtmosphereComponent, GridSplitEvent>(OnGridSplit);
 
@@ -22,19 +23,15 @@ public sealed partial class AtmosphereSystem
         SubscribeLocalEvent<GridAtmosphereComponent, HasAtmosphereMethodEvent>(GridHasAtmosphere);
         SubscribeLocalEvent<GridAtmosphereComponent, IsSimulatedGridMethodEvent>(GridIsSimulated);
         SubscribeLocalEvent<GridAtmosphereComponent, GetAllMixturesMethodEvent>(GridGetAllMixtures);
-        SubscribeLocalEvent<GridAtmosphereComponent, InvalidateTileMethodEvent>(GridInvalidateTile);
         SubscribeLocalEvent<GridAtmosphereComponent, GetTileMixtureMethodEvent>(GridGetTileMixture);
         SubscribeLocalEvent<GridAtmosphereComponent, GetTileMixturesMethodEvent>(GridGetTileMixtures);
         SubscribeLocalEvent<GridAtmosphereComponent, ReactTileMethodEvent>(GridReactTile);
-        SubscribeLocalEvent<GridAtmosphereComponent, IsTileAirBlockedMethodEvent>(GridIsTileAirBlocked);
         SubscribeLocalEvent<GridAtmosphereComponent, IsTileSpaceMethodEvent>(GridIsTileSpace);
         SubscribeLocalEvent<GridAtmosphereComponent, GetAdjacentTilesMethodEvent>(GridGetAdjacentTiles);
         SubscribeLocalEvent<GridAtmosphereComponent, GetAdjacentTileMixturesMethodEvent>(GridGetAdjacentTileMixtures);
-        SubscribeLocalEvent<GridAtmosphereComponent, UpdateAdjacentMethodEvent>(GridUpdateAdjacent);
         SubscribeLocalEvent<GridAtmosphereComponent, HotspotExposeMethodEvent>(GridHotspotExpose);
         SubscribeLocalEvent<GridAtmosphereComponent, HotspotExtinguishMethodEvent>(GridHotspotExtinguish);
         SubscribeLocalEvent<GridAtmosphereComponent, IsHotspotActiveMethodEvent>(GridIsHotspotActive);
-        SubscribeLocalEvent<GridAtmosphereComponent, FixTileVacuumMethodEvent>(GridFixTileVacuum);
         SubscribeLocalEvent<GridAtmosphereComponent, AddPipeNetMethodEvent>(GridAddPipeNet);
         SubscribeLocalEvent<GridAtmosphereComponent, RemovePipeNetMethodEvent>(GridRemovePipeNet);
         SubscribeLocalEvent<GridAtmosphereComponent, AddAtmosDeviceMethodEvent>(GridAddAtmosDevice);
@@ -56,22 +53,23 @@ public sealed partial class AtmosphereSystem
         }
     }
 
-    private void OnGridAtmosphereInit(EntityUid uid, GridAtmosphereComponent gridAtmosphere, ComponentInit args)
+    private void OnGridAtmosphereInit(EntityUid uid, GridAtmosphereComponent component, ComponentInit args)
     {
         base.Initialize();
 
-        if (!TryComp(uid, out MapGridComponent? mapGrid))
-            return;
-
         EnsureComp<GasTileOverlayComponent>(uid);
-
-        foreach (var (indices, tile) in gridAtmosphere.Tiles)
+        foreach (var tile in component.Tiles.Values)
         {
-            gridAtmosphere.InvalidatedCoords.Add(indices);
             tile.GridIndex = uid;
         }
+    }
+
+    private void OnGridAtmosphereStartup(EntityUid uid, GridAtmosphereComponent component, ComponentStartup args)
+    {
+        if (!TryComp(uid, out MapGridComponent? mapGrid))
+            return;
 
-        GridRepopulateTiles((uid, mapGrid, gridAtmosphere));
+        InvalidateAllTiles((uid, mapGrid, component));
     }
 
     private void OnGridSplit(EntityUid uid, GridAtmosphereComponent originalGridAtmos, ref GridSplitEvent args)
@@ -104,8 +102,7 @@ public sealed partial class AtmosphereSystem
                     continue;
 
                 // Copy a bunch of data over... Not great, maybe put this in TileAtmosphere?
-                newTileAtmosphere.Air = tileAtmosphere.Air?.Clone() ?? null;
-                newTileAtmosphere.MolesArchived = newTileAtmosphere.Air == null ? null : new float[Atmospherics.AdjustedNumberOfGases];
+                newTileAtmosphere.Air = tileAtmosphere.Air?.Clone();
                 newTileAtmosphere.Hotspot = tileAtmosphere.Hotspot;
                 newTileAtmosphere.HeatCapacity = tileAtmosphere.HeatCapacity;
                 newTileAtmosphere.Temperature = tileAtmosphere.Temperature;
@@ -170,15 +167,6 @@ public sealed partial class AtmosphereSystem
         args.Handled = true;
     }
 
-    private void GridInvalidateTile(EntityUid uid, GridAtmosphereComponent component, ref InvalidateTileMethodEvent args)
-    {
-        if (args.Handled)
-            return;
-
-        component.InvalidatedCoords.Add(args.Tile);
-        args.Handled = true;
-    }
-
     private void GridGetTileMixture(EntityUid uid, GridAtmosphereComponent component,
         ref GetTileMixtureMethodEvent args)
     {
@@ -233,43 +221,6 @@ public sealed partial class AtmosphereSystem
         args.Handled = true;
     }
 
-    private void GridIsTileAirBlocked(EntityUid uid, GridAtmosphereComponent component,
-        ref IsTileAirBlockedMethodEvent args)
-    {
-        if (args.Handled)
-            return;
-
-        var mapGridComp = args.MapGridComponent;
-
-        if (!Resolve(uid, ref mapGridComp))
-            return;
-
-        var directions = AtmosDirection.Invalid;
-
-        var enumerator = GetObstructingComponentsEnumerator(mapGridComp, args.Tile);
-
-        while (enumerator.MoveNext(out var obstructingComponent))
-        {
-            if (!obstructingComponent.AirBlocked)
-                continue;
-
-            // We set the directions that are air-blocked so far,
-            // as you could have a full obstruction with only 4 directional air blockers.
-            directions |= obstructingComponent.AirBlockedDirection;
-            args.NoAir |= obstructingComponent.NoAirWhenFullyAirBlocked;
-
-            if (directions.IsFlagSet(args.Direction))
-            {
-                args.Result = true;
-                args.Handled = true;
-                return;
-            }
-        }
-
-        args.Result = false;
-        args.Handled = true;
-    }
-
     private void GridIsTileSpace(EntityUid uid, GridAtmosphereComponent component, ref IsTileSpaceMethodEvent args)
     {
         if (args.Handled)
@@ -331,71 +282,58 @@ public sealed partial class AtmosphereSystem
         args.Handled = true;
     }
 
-    private void GridUpdateAdjacent(EntityUid uid, GridAtmosphereComponent component,
-        ref UpdateAdjacentMethodEvent args)
+    /// <summary>
+    /// Update array of adjacent tiles and the adjacency flags. Optionally activates all tiles with modified adjacencies.
+    /// </summary>
+    private void UpdateAdjacentTiles(
+        Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
+        TileAtmosphere tile,
+        bool activate = false)
     {
-        if (args.Handled)
-            return;
-
-        var mapGridComp = args.MapGridComponent;
-
-        if (!Resolve(uid, ref mapGridComp))
-            return;
-
-        var xform = Transform(uid);
-        EntityUid? mapUid = _mapManager.MapExists(xform.MapID) ? _mapManager.GetMapEntityId(xform.MapID) : null;
-
-        if (!component.Tiles.TryGetValue(args.Tile, out var tile))
-            return;
+        var uid = ent.Owner;
+        var atmos = ent.Comp1;
+        var blockedDirs = tile.AirtightData.BlockedDirections;
+        if (activate)
+            AddActiveTile(atmos, tile);
 
         tile.AdjacentBits = AtmosDirection.Invalid;
-        tile.BlockedAirflow = GetBlockedDirections(mapGridComp, tile.GridIndices);
-
         for (var i = 0; i < Atmospherics.Directions; i++)
         {
             var direction = (AtmosDirection) (1 << i);
+            var adjacentIndices = tile.GridIndices.Offset(direction);
 
-            var otherIndices = tile.GridIndices.Offset(direction);
-
-            if (!component.Tiles.TryGetValue(otherIndices, out var adjacent))
+            TileAtmosphere? adjacent;
+            if (!tile.NoGridTile)
             {
-                adjacent = new TileAtmosphere(tile.GridIndex, otherIndices,
-                    GetTileMixture(null, mapUid, otherIndices),
-                    space: IsTileSpace(null, mapUid, otherIndices, mapGridComp));
+                adjacent = GetOrNewTile(uid, atmos, adjacentIndices);
             }
-
-            var oppositeDirection = direction.GetOpposite();
-
-            adjacent.BlockedAirflow = GetBlockedDirections(mapGridComp, adjacent.GridIndices);
-
-            // Pass in MapGridComponent so we don't have to resolve it for every adjacent direction.
-            var tileBlockedEv = new IsTileAirBlockedMethodEvent(uid, tile.GridIndices, direction, mapGridComp);
-            GridIsTileAirBlocked(uid, component, ref tileBlockedEv);
-
-            var adjacentBlockedEv =
-                new IsTileAirBlockedMethodEvent(uid, adjacent.GridIndices, oppositeDirection, mapGridComp);
-            GridIsTileAirBlocked(uid, component, ref adjacentBlockedEv);
-
-            if (!adjacent.BlockedAirflow.IsFlagSet(oppositeDirection) && !tileBlockedEv.Result)
+            else if (!atmos.Tiles.TryGetValue(adjacentIndices, out adjacent))
             {
-                adjacent.AdjacentBits |= oppositeDirection;
-                adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = tile;
+                tile.AdjacentBits &= ~direction;
+                tile.AdjacentTiles[i] = null;
+                continue;
             }
-            else
+
+            var adjBlockDirs = adjacent.AirtightData.BlockedDirections;
+            if (activate)
+                AddActiveTile(atmos, adjacent);
+
+            var oppositeDirection = direction.GetOpposite();
+            if (adjBlockDirs.IsFlagSet(oppositeDirection) || blockedDirs.IsFlagSet(direction))
             {
+                // Adjacency is blocked by some airtight entity.
+                tile.AdjacentBits &= ~direction;
                 adjacent.AdjacentBits &= ~oppositeDirection;
+                tile.AdjacentTiles[i] = null;
                 adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = null;
             }
-
-            if (!tile.BlockedAirflow.IsFlagSet(direction) && !adjacentBlockedEv.Result)
-            {
-                tile.AdjacentBits |= direction;
-                tile.AdjacentTiles[direction.ToIndex()] = adjacent;
-            }
             else
             {
-                tile.AdjacentBits &= ~direction;
-                tile.AdjacentTiles[direction.ToIndex()] = null;
+                // No airtight entity in the way.
+                tile.AdjacentBits |= direction;
+                adjacent.AdjacentBits |= oppositeDirection;
+                tile.AdjacentTiles[i] = adjacent;
+                adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = tile;
             }
 
             DebugTools.Assert(!(tile.AdjacentBits.IsFlagSet(direction) ^
@@ -409,6 +347,16 @@ public sealed partial class AtmosphereSystem
             tile.MonstermosInfo.CurrentTransferDirection = AtmosDirection.Invalid;
     }
 
+    private (GasMixture Air, bool IsSpace) GetDefaultMapAtmosphere(MapAtmosphereComponent? map)
+    {
+        if (map == null)
+            return (GasMixture.SpaceGas, true);
+
+        var air = map.Mixture;
+        DebugTools.Assert(air.Immutable);
+        return (air, map.Space);
+    }
+
     private void GridHotspotExpose(EntityUid uid, GridAtmosphereComponent component, ref HotspotExposeMethodEvent args)
     {
         if (args.Handled)
@@ -451,54 +399,50 @@ public sealed partial class AtmosphereSystem
         args.Handled = true;
     }
 
-    private void GridFixTileVacuum(EntityUid uid, GridAtmosphereComponent component, ref FixTileVacuumMethodEvent args)
+    private void GridFixTileVacuum(
+        Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
+        TileAtmosphere tile,
+        float volume)
     {
-        if (args.Handled)
-            return;
-
-        var adjEv = new GetAdjacentTileMixturesMethodEvent(uid, args.Tile, false, true);
-        GridGetAdjacentTileMixtures(uid, component, ref adjEv);
-
-        if (!adjEv.Handled || !component.Tiles.TryGetValue(args.Tile, out var tile))
-            return;
-
-        if (!TryComp<MapGridComponent>(uid, out var mapGridComp))
-            return;
-
-        var adjacent = adjEv.Result!.ToArray();
-
-        // Return early, let's not cause any funny NaNs or needless vacuums.
-        if (adjacent.Length == 0)
-            return;
+        DebugTools.AssertNotNull(tile.Air);
+        DebugTools.Assert(tile.Air?.Immutable == false );
+        Array.Clear(tile.MolesArchived);
+        tile.ArchivedCycle = 0;
 
-        tile.Air = new GasMixture
+        var count = 0;
+        foreach (var adj in tile.AdjacentTiles)
         {
-            Volume = GetVolumeForTiles(mapGridComp, 1),
-            Temperature = Atmospherics.T20C
-        };
-
-        tile.MolesArchived = new float[Atmospherics.AdjustedNumberOfGases];
-        tile.ArchivedCycle = 0;
+            if (adj?.Air != null)
+                count++;
+        }
 
-        var ratio = 1f / adjacent.Length;
+        var ratio = 1f / count;
         var totalTemperature = 0f;
 
-        foreach (var adj in adjacent)
+        foreach (var adj in tile.AdjacentTiles)
         {
+            if (adj?.Air == null)
+                continue;
+
             totalTemperature += adj.Temperature;
 
+            // TODO ATMOS. Why is this removing and then re-adding air to the neighbouring tiles?
+            // Is it some rounding issue to do with Atmospherics.GasMinMoles? because otherwise this is just unnecessary.
+            // if we get rid of this, then this could also just add moles and then multiply by ratio at the end, rather
+            // than having to iterate over adjacent tiles twice.
+
             // Remove a bit of gas from the adjacent ratio...
-            var mix = adj.RemoveRatio(ratio);
+            var mix = adj.Air.RemoveRatio(ratio);
 
             // And merge it to the new tile air.
             Merge(tile.Air, mix);
 
             // Return removed gas to its original mixture.
-            Merge(adj, mix);
+            Merge(adj.Air, mix);
         }
 
         // New temperature is the arithmetic mean of the sum of the adjacent temperatures...
-        tile.Air.Temperature = totalTemperature / adjacent.Length;
+        tile.Air.Temperature = totalTemperature / count;
     }
 
     private void GridAddPipeNet(EntityUid uid, GridAtmosphereComponent component, ref AddPipeNetMethodEvent args)
@@ -547,30 +491,21 @@ public sealed partial class AtmosphereSystem
     /// <summary>
     ///     Repopulates all tiles on a grid atmosphere.
     /// </summary>
-    /// <param name="mapGrid">The grid where to get all valid tiles from.</param>
-    /// <param name="gridAtmosphere">The grid atmosphere where the tiles will be repopulated.</param>
-    private void GridRepopulateTiles(Entity<MapGridComponent, GridAtmosphereComponent> grid)
+    public void InvalidateAllTiles(Entity<MapGridComponent?, GridAtmosphereComponent?> entity)
     {
-        var (uid, mapGrid, gridAtmosphere) = grid;
-        var volume = GetVolumeForTiles(mapGrid, 1);
+        var (uid, grid, atmos) = entity;
+        if (!Resolve(uid, ref grid, ref atmos))
+            return;
 
-        foreach (var tile in mapGrid.GetAllTiles())
+        foreach (var indices in atmos.Tiles.Keys)
         {
-            if (!gridAtmosphere.Tiles.ContainsKey(tile.GridIndices))
-                gridAtmosphere.Tiles[tile.GridIndices] = new TileAtmosphere(tile.GridUid, tile.GridIndices,
-                    new GasMixture(volume) { Temperature = Atmospherics.T20C });
-
-            gridAtmosphere.InvalidatedCoords.Add(tile.GridIndices);
+            atmos.InvalidatedCoords.Add(indices);
         }
 
-        TryComp(uid, out GasTileOverlayComponent? overlay);
-
-        // Gotta do this afterwards so we can properly update adjacent tiles.
-        foreach (var (position, _) in gridAtmosphere.Tiles.ToArray())
+        var enumerator = _map.GetAllTilesEnumerator(uid, grid);
+        while (enumerator.MoveNext(out var tile))
         {
-            var ev = new UpdateAdjacentMethodEvent(uid, position);
-            GridUpdateAdjacent(uid, gridAtmosphere, ref ev);
-            InvalidateVisuals(uid, position, overlay);
+            atmos.InvalidatedCoords.Add(tile.Value.GridIndices);
         }
     }
 
index 4d25292a66796884ebf1a2deba986a0ea9541387..0ec89feaef96550c0800d12ddd7945d36d9baa3f 100644 (file)
@@ -1,13 +1,11 @@
 using Content.Server.Atmos.Components;
 using Content.Shared.Atmos;
-using Content.Shared.Audio;
 using Content.Shared.Mobs.Components;
 using Content.Shared.Physics;
 using Robust.Shared.Audio;
 using Robust.Shared.Map;
 using Robust.Shared.Physics;
 using Robust.Shared.Physics.Components;
-using Robust.Shared.Player;
 using Robust.Shared.Random;
 using Robust.Shared.Utility;
 
index 795c6e0547e20ebd7195cc1c1dc269e95fb442f7..c27e18b55b0013c0c32ad0c99bf5016f4e590bf5 100644 (file)
@@ -1,12 +1,13 @@
 using Content.Server.Atmos.Components;
 using Content.Shared.Atmos;
 using Content.Shared.Atmos.Components;
+using Robust.Shared.Utility;
 
 namespace Content.Server.Atmos.EntitySystems
 {
     public sealed partial class AtmosphereSystem
     {
-        private void ProcessCell(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int fireCount, GasTileOverlayComponent? visuals)
+        private void ProcessCell(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int fireCount, GasTileOverlayComponent visuals)
         {
             // Can't process a tile without air
             if (tile.Air == null)
@@ -116,15 +117,9 @@ namespace Content.Server.Atmos.EntitySystems
         private void Archive(TileAtmosphere tile, int fireCount)
         {
             if (tile.Air != null)
-            {
                 tile.Air.Moles.AsSpan().CopyTo(tile.MolesArchived.AsSpan());
-                tile.TemperatureArchived = tile.Air.Temperature;
-            }
-            else
-            {
-                tile.TemperatureArchived = tile.Temperature;
-            }
 
+            tile.TemperatureArchived = tile.Temperature;
             tile.ArchivedCycle = fireCount;
         }
 
@@ -166,6 +161,12 @@ namespace Content.Server.Atmos.EntitySystems
         /// <param name="disposeExcitedGroup">Whether to dispose of the tile's <see cref="ExcitedGroup"/></param>
         private void RemoveActiveTile(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, bool disposeExcitedGroup = true)
         {
+            DebugTools.Assert(tile.Excited == gridAtmosphere.ActiveTiles.Contains(tile));
+            DebugTools.Assert(tile.Excited || tile.ExcitedGroup == null);
+
+            if (!tile.Excited)
+                return;
+
             tile.Excited = false;
             gridAtmosphere.ActiveTiles.Remove(tile);
 
@@ -186,7 +187,6 @@ namespace Content.Server.Atmos.EntitySystems
             if (tile.Air == null)
                 return tile.HeatCapacity;
 
-            // Moles archived is not null if air is not null.
             return GetHeatCapacityCalculation(tile.MolesArchived!, tile.Space);
         }
 
index ab59aab7e9681827277dff3ee9f915949df98a86..ed105c8d33fff8cf132a09c44222e22f3c6219d7 100644 (file)
@@ -1,6 +1,8 @@
 using Content.Server.Atmos.Components;
 using Content.Shared.Atmos.Components;
 using Robust.Shared.GameStates;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Utility;
 
 namespace Content.Server.Atmos.EntitySystems;
 
@@ -8,10 +10,25 @@ public partial class AtmosphereSystem
 {
     private void InitializeMap()
     {
+        SubscribeLocalEvent<MapAtmosphereComponent, ComponentInit>(OnMapStartup);
+        SubscribeLocalEvent<MapAtmosphereComponent, ComponentRemove>(OnMapRemove);
         SubscribeLocalEvent<MapAtmosphereComponent, IsTileSpaceMethodEvent>(MapIsTileSpace);
         SubscribeLocalEvent<MapAtmosphereComponent, GetTileMixtureMethodEvent>(MapGetTileMixture);
         SubscribeLocalEvent<MapAtmosphereComponent, GetTileMixturesMethodEvent>(MapGetTileMixtures);
         SubscribeLocalEvent<MapAtmosphereComponent, ComponentGetState>(OnMapGetState);
+        SubscribeLocalEvent<GridAtmosphereComponent, EntParentChangedMessage>(OnGridParentChanged);
+    }
+
+    private void OnMapStartup(EntityUid uid, MapAtmosphereComponent component, ComponentInit args)
+    {
+        component.Mixture.MarkImmutable();
+        component.Overlay = _gasTileOverlaySystem.GetOverlayData(component.Mixture);
+    }
+
+    private void OnMapRemove(EntityUid uid, MapAtmosphereComponent component, ComponentRemove args)
+    {
+        if (!TerminatingOrDeleted(uid))
+            RefreshAllGridMapAtmospheres(uid);
     }
 
     private void MapIsTileSpace(EntityUid uid, MapAtmosphereComponent component, ref IsTileSpaceMethodEvent args)
@@ -28,54 +45,115 @@ public partial class AtmosphereSystem
         if (args.Handled)
             return;
 
-        // Clone the mixture, if possible.
-        args.Mixture = component.Mixture?.Clone();
+        args.Mixture = component.Mixture;
         args.Handled = true;
     }
 
     private void MapGetTileMixtures(EntityUid uid, MapAtmosphereComponent component, ref GetTileMixturesMethodEvent args)
     {
-        if (args.Handled || component.Mixture == null)
+        if (args.Handled)
             return;
         args.Handled = true;
         args.Mixtures ??= new GasMixture?[args.Tiles.Count];
 
         for (var i = 0; i < args.Tiles.Count; i++)
         {
-            args.Mixtures[i] ??= component.Mixture.Clone();
+            args.Mixtures[i] ??= component.Mixture;
         }
     }
 
     private void OnMapGetState(EntityUid uid, MapAtmosphereComponent component, ref ComponentGetState args)
     {
-        args.State = new MapAtmosphereComponentState(_gasTileOverlaySystem.GetOverlayData(component.Mixture));
+        args.State = new MapAtmosphereComponentState(component.Overlay);
+    }
+
+    public void SetMapAtmosphere(EntityUid uid, bool space, GasMixture mixture)
+    {
+        DebugTools.Assert(HasComp<MapComponent>(uid));
+        var component = EnsureComp<MapAtmosphereComponent>(uid);
+        SetMapGasMixture(uid, mixture, component, false);
+        SetMapSpace(uid, space, component, false);
+        RefreshAllGridMapAtmospheres(uid);
     }
 
-    public void SetMapAtmosphere(EntityUid uid, bool space, GasMixture mixture, MapAtmosphereComponent? component = null)
+    public void SetMapGasMixture(EntityUid uid, GasMixture mixture, MapAtmosphereComponent? component = null, bool updateTiles = true)
     {
         if (!Resolve(uid, ref component))
             return;
 
-        component.Space = space;
+        if (!mixture.Immutable)
+        {
+            mixture = mixture.Clone();
+            mixture.MarkImmutable();
+        }
+
         component.Mixture = mixture;
+        component.Overlay = _gasTileOverlaySystem.GetOverlayData(component.Mixture);
         Dirty(uid, component);
+        if (updateTiles)
+            RefreshAllGridMapAtmospheres(uid);
     }
 
-    public void SetMapGasMixture(EntityUid uid, GasMixture? mixture, MapAtmosphereComponent? component = null)
+    public void SetMapSpace(EntityUid uid, bool space, MapAtmosphereComponent? component = null, bool updateTiles = true)
     {
         if (!Resolve(uid, ref component))
             return;
 
-        component.Mixture = mixture;
-        Dirty(uid, component);
+        if (component.Space == space)
+            return;
+
+        component.Space = space;
+
+        if (updateTiles)
+            RefreshAllGridMapAtmospheres(uid);
     }
 
-    public void SetMapSpace(EntityUid uid, bool space, MapAtmosphereComponent? component = null)
+    /// <summary>
+    /// Forces a refresh of all MapAtmosphere tiles on every grid on a map.
+    /// </summary>
+    public void RefreshAllGridMapAtmospheres(EntityUid map)
     {
-        if (!Resolve(uid, ref component))
+        DebugTools.Assert(HasComp<MapComponent>(map));
+        var enumerator = AllEntityQuery<GridAtmosphereComponent, TransformComponent>();
+        while (enumerator.MoveNext(out var grid, out var atmos, out var xform))
+        {
+            if (xform.MapUid == map)
+                RefreshMapAtmosphereTiles((grid, atmos));
+        }
+    }
+
+    /// <summary>
+    /// Forces a refresh of all MapAtmosphere tiles on a given grid.
+    /// </summary>
+    private void RefreshMapAtmosphereTiles(Entity<GridAtmosphereComponent?> grid)
+    {
+        if (!Resolve(grid.Owner, ref grid.Comp))
             return;
 
-        component.Space = space;
-        Dirty(uid, component);
+        var atmos = grid.Comp;
+        foreach (var tile in atmos.MapTiles)
+        {
+            RemoveMapAtmos(atmos, tile);
+            atmos.InvalidatedCoords.Add(tile.GridIndices);
+        }
+        atmos.MapTiles.Clear();
+    }
+
+    /// <summary>
+    /// Handles updating map-atmospheres when grids move across maps.
+    /// </summary>
+    private void OnGridParentChanged(Entity<GridAtmosphereComponent> grid, ref EntParentChangedMessage args)
+    {
+        // Do nothing if detaching to nullspace
+        if (!args.Transform.ParentUid.IsValid())
+            return;
+
+        // Avoid doing work if moving from a space-map to another space-map.
+        if (args.OldParent == null
+            || HasComp<MapAtmosphereComponent>(args.OldParent)
+            || HasComp<MapAtmosphereComponent>(args.Transform.ParentUid))
+        {
+            RefreshMapAtmosphereTiles((grid, grid));
+        }
     }
 }
index 737976a82973b1add45631ec67cd1cd78d66e8ae..dcbc1e86ee20f403427c59575fa0a7a62622f040 100644 (file)
@@ -5,7 +5,6 @@ using Content.Server.Doors.Systems;
 using Content.Shared.Atmos;
 using Content.Shared.Atmos.Components;
 using Content.Shared.Database;
-using Content.Shared.Doors.Components;
 using Robust.Shared.Map.Components;
 using Robust.Shared.Physics.Components;
 using Robust.Shared.Random;
@@ -27,7 +26,10 @@ namespace Content.Server.Atmos.EntitySystems
         private readonly TileAtmosphere[] _depressurizeSpaceTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit];
         private readonly TileAtmosphere[] _depressurizeProgressionOrder = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit * 2];
 
-        private void EqualizePressureInZone(Entity<MapGridComponent, GridAtmosphereComponent> ent, TileAtmosphere tile, int cycleNum, GasTileOverlayComponent? visuals)
+        private void EqualizePressureInZone(
+            Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
+            TileAtmosphere tile,
+            int cycleNum)
         {
             if (tile.Air == null || (tile.MonstermosInfo.LastCycle >= cycleNum))
                 return; // Already done.
@@ -56,7 +58,7 @@ namespace Content.Server.Atmos.EntitySystems
                 return;
             }
 
-            var (_, mapGrid, gridAtmosphere) = ent;
+            var gridAtmosphere = ent.Comp1;
             var queueCycle = ++gridAtmosphere.EqualizationQueueCycleControl;
             var totalMoles = 0f;
             _equalizeTiles[0] = tile;
@@ -91,7 +93,7 @@ namespace Content.Server.Atmos.EntitySystems
                     {
                         // Looks like someone opened an airlock to space!
 
-                        ExplosivelyDepressurize(ent, tile, cycleNum, visuals);
+                        ExplosivelyDepressurize(ent, tile, cycleNum);
                         return;
                     }
                 }
@@ -216,9 +218,13 @@ namespace Content.Server.Atmos.EntitySystems
                         for (var k = 0; k < Atmospherics.Directions; k++)
                         {
                             var direction = (AtmosDirection) (1 << k);
-                            if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
+                            if (!otherTile.AdjacentBits.IsFlagSet(direction))
+                                continue;
+
+                            if (giver.MonstermosInfo.MoleDelta <= 0)
+                                break; // We're done here now. Let's not do more work than needed.
+
                             var otherTile2 = otherTile.AdjacentTiles[k];
-                            if (giver.MonstermosInfo.MoleDelta <= 0) break; // We're done here now. Let's not do more work than needed.
                             if (otherTile2 == null || otherTile2.MonstermosInfo.LastQueueCycle != queueCycle) continue;
                             DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
                             if (otherTile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) continue;
@@ -332,7 +338,7 @@ namespace Content.Server.Atmos.EntitySystems
             for (var i = 0; i < tileCount; i++)
             {
                 var otherTile = _equalizeTiles[i]!;
-                FinalizeEq(gridAtmosphere, otherTile, visuals);
+                FinalizeEq(gridAtmosphere, otherTile, ent);
             }
 
             for (var i = 0; i < tileCount; i++)
@@ -341,12 +347,17 @@ namespace Content.Server.Atmos.EntitySystems
                 for (var j = 0; j < Atmospherics.Directions; j++)
                 {
                     var direction = (AtmosDirection) (1 << j);
-                    if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
+                    if (!otherTile.AdjacentBits.IsFlagSet(direction))
+                        continue;
+
                     var otherTile2 = otherTile.AdjacentTiles[j]!;
                     if (otherTile2.AdjacentBits == 0)
                         continue;
+
                     DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
-                    if (otherTile2.Air != null && CompareExchange(otherTile2.Air, tile.Air) == GasCompareResult.NoExchange) continue;
+                    if (otherTile2.Air != null && CompareExchange(otherTile2.Air, tile.Air) == GasCompareResult.NoExchange)
+                        continue;
+
                     AddActiveTile(gridAtmosphere, otherTile2);
                     break;
                 }
@@ -359,7 +370,10 @@ namespace Content.Server.Atmos.EntitySystems
             Array.Clear(_equalizeQueue, 0, Atmospherics.MonstermosTileLimit);
         }
 
-        private void ExplosivelyDepressurize(Entity<MapGridComponent, GridAtmosphereComponent> ent, TileAtmosphere tile, int cycleNum, GasTileOverlayComponent? visuals)
+        private void ExplosivelyDepressurize(
+            Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
+            TileAtmosphere tile,
+            int cycleNum)
         {
             // Check if explosive depressurization is enabled and if the tile is valid.
             if (!MonstermosDepressurization || tile.Air == null)
@@ -368,7 +382,7 @@ namespace Content.Server.Atmos.EntitySystems
             const int limit = Atmospherics.MonstermosHardTileLimit;
 
             var totalMolesRemoved = 0f;
-            var (owner, mapGrid, gridAtmosphere) = ent;
+            var (owner, gridAtmosphere, visuals, mapGrid, _) = ent;
             var queueCycle = ++gridAtmosphere.EqualizationQueueCycleControl;
 
             var tileCount = 0;
@@ -388,20 +402,27 @@ namespace Content.Server.Atmos.EntitySystems
                 {
                     for (var j = 0; j < Atmospherics.Directions; j++)
                     {
-                        var direction = (AtmosDirection) (1 << j);
-                        if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
                         var otherTile2 = otherTile.AdjacentTiles[j];
-                        if (otherTile2?.Air == null) continue;
+                        if (otherTile2?.Air == null)
+                            continue;
+
+                        if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle)
+                            continue;
+
+                        var direction = (AtmosDirection) (1 << j);
+                        DebugTools.Assert(otherTile.AdjacentBits.IsFlagSet(direction));
                         DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
-                        if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle) continue;
 
-                        ConsiderFirelocks((owner, gridAtmosphere), otherTile, otherTile2, visuals, mapGrid);
+                        ConsiderFirelocks(ent, otherTile, otherTile2);
 
                         // The firelocks might have closed on us.
-                        if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
+                        if (!otherTile.AdjacentBits.IsFlagSet(direction))
+                            continue;
+
                         otherTile2.MonstermosInfo = new MonstermosInfo { LastQueueCycle = queueCycle };
                         _depressurizeTiles[tileCount++] = otherTile2;
-                        if (tileCount >= limit) break;
+                        if (tileCount >= limit)
+                            break;
                     }
                 }
                 else
@@ -437,13 +458,21 @@ namespace Content.Server.Atmos.EntitySystems
                     // Flood fill into this new direction
                     var direction = (AtmosDirection) (1 << j);
                     // Tiles in _depressurizeProgressionOrder cannot have null air.
-                    if (!otherTile.AdjacentBits.IsFlagSet(direction) && !otherTile.Space) continue;
+                    if (!otherTile.AdjacentBits.IsFlagSet(direction) && !otherTile.Space)
+                        continue;
+
                     var tile2 = otherTile.AdjacentTiles[j];
-                    if (tile2?.MonstermosInfo.LastQueueCycle != queueCycle) continue;
+                    if (tile2?.MonstermosInfo.LastQueueCycle != queueCycle)
+                        continue;
+
                     DebugTools.Assert(tile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
                     // If flood fill has already reached this tile, continue.
-                    if (tile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) continue;
-                    if(tile2.Space) continue;
+                    if (tile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow)
+                        continue;
+
+                    if(tile2.Space)
+                        continue;
+
                     tile2.MonstermosInfo.CurrentTransferDirection = direction.GetOpposite();
                     tile2.MonstermosInfo.CurrentTransferAmount = 0.0f;
                     tile2.PressureSpecificTarget = otherTile.PressureSpecificTarget;
@@ -535,7 +564,7 @@ namespace Content.Server.Atmos.EntitySystems
                 _physics.ApplyAngularImpulse(owner, Vector2Helpers.Cross(tile.GridIndices - gridPhysics.LocalCenter, direction) * totalMolesRemoved, body: gridPhysics);
             }
 
-            if(tileCount > 10 && (totalMolesRemoved / tileCount) > 10)
+            if (tileCount > 10 && (totalMolesRemoved / tileCount) > 10)
                 _adminLog.Add(LogType.ExplosiveDepressurization, LogImpact.High,
                     $"Explosive depressurization removed {totalMolesRemoved} moles from {tileCount} tiles starting from position {tile.GridIndices:position} on grid ID {tile.GridIndex:grid}");
 
@@ -544,36 +573,33 @@ namespace Content.Server.Atmos.EntitySystems
             Array.Clear(_depressurizeProgressionOrder, 0, Atmospherics.MonstermosHardTileLimit * 2);
         }
 
-        private void ConsiderFirelocks(Entity<GridAtmosphereComponent> ent, TileAtmosphere tile, TileAtmosphere other, GasTileOverlayComponent? visuals, MapGridComponent mapGrid)
+        private void ConsiderFirelocks(
+            Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
+            TileAtmosphere tile,
+            TileAtmosphere other)
         {
             var reconsiderAdjacent = false;
 
-            foreach (var entity in mapGrid.GetAnchoredEntities(tile.GridIndices))
+            var mapGrid = ent.Comp3;
+            foreach (var entity in _map.GetAnchoredEntities(ent.Owner, mapGrid, tile.GridIndices))
             {
-                if (!TryComp(entity, out FirelockComponent? firelock))
-                    continue;
-
-                reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
+                if (_firelockQuery.TryGetComponent(entity, out var firelock))
+                    reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
             }
 
-            foreach (var entity in mapGrid.GetAnchoredEntities(other.GridIndices))
+            foreach (var entity in _map.GetAnchoredEntities(ent.Owner, mapGrid, other.GridIndices))
             {
-                if (!TryComp(entity, out FirelockComponent? firelock))
-                    continue;
-
-                reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
+                if (_firelockQuery.TryGetComponent(entity, out var firelock))
+                    reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
             }
 
             if (!reconsiderAdjacent)
                 return;
 
-            var (owner, gridAtmosphere) = ent;
-            var tileEv = new UpdateAdjacentMethodEvent(owner, tile.GridIndices);
-            var otherEv = new UpdateAdjacentMethodEvent(owner, other.GridIndices);
-            GridUpdateAdjacent(owner, gridAtmosphere, ref tileEv);
-            GridUpdateAdjacent(owner, gridAtmosphere, ref otherEv);
-            InvalidateVisuals(tile.GridIndex, tile.GridIndices, visuals);
-            InvalidateVisuals(other.GridIndex, other.GridIndices, visuals);
+            UpdateAdjacentTiles(ent, tile);
+            UpdateAdjacentTiles(ent, other);
+            InvalidateVisuals(tile.GridIndex, tile.GridIndices, ent);
+            InvalidateVisuals(other.GridIndex, other.GridIndices, ent);
         }
 
         private void FinalizeEq(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, GasTileOverlayComponent? visuals)
@@ -642,7 +668,7 @@ namespace Content.Server.Atmos.EntitySystems
             if (adj == null)
             {
                 var nonNull = tile.AdjacentTiles.Where(x => x != null).Count();
-                Log.Error($"Encountered null adjacent tile in {nameof(AdjustEqMovement)}. Dir: {direction}, Tile: {tile.Tile}, non-null adj count: {nonNull}, Trace: {Environment.StackTrace}");
+                Log.Error($"Encountered null adjacent tile in {nameof(AdjustEqMovement)}. Dir: {direction}, Tile: ({tile.GridIndex}, {tile.GridIndices}), non-null adj count: {nonNull}, Trace: {Environment.StackTrace}");
                 return;
             }
 
index 4f8df0af67074a4adfd13e793c03deb32e7fce40..1f3ca2145b9716623ca5527b7ca742070a03b4ce 100644 (file)
@@ -1,6 +1,5 @@
 using Content.Server.Atmos.Components;
 using Content.Server.Atmos.Piping.Components;
-using Content.Server.NodeContainer.NodeGroups;
 using Content.Shared.Atmos;
 using Content.Shared.Atmos.Components;
 using Content.Shared.Maps;
@@ -8,6 +7,7 @@ using Robust.Shared.Map;
 using Robust.Shared.Map.Components;
 using Robust.Shared.Physics.Components;
 using Robust.Shared.Timing;
+using Robust.Shared.Utility;
 
 namespace Content.Server.Atmos.EntitySystems
 {
@@ -30,131 +30,249 @@ namespace Content.Server.Atmos.EntitySystems
         private int _currentRunAtmosphereIndex;
         private bool _simulationPaused;
 
-        private readonly List<Entity<GridAtmosphereComponent>> _currentRunAtmosphere = new();
+        private TileAtmosphere GetOrNewTile(EntityUid owner, GridAtmosphereComponent atmosphere, Vector2i index)
+        {
+            var tile = atmosphere.Tiles.GetOrNew(index, out var existing);
+            if (existing)
+                return tile;
+
+            atmosphere.InvalidatedCoords.Add(index);
+            tile.GridIndex = owner;
+            tile.GridIndices = index;
+            return tile;
+        }
+
+        private readonly List<Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent>> _currentRunAtmosphere = new();
 
         /// <summary>
         ///     Revalidates all invalid coordinates in a grid atmosphere.
+        ///     I.e., process any tiles that have had their airtight blockers modified.
         /// </summary>
         /// <param name="ent">The grid atmosphere in question.</param>
         /// <returns>Whether the process succeeded or got paused due to time constrains.</returns>
-        private bool ProcessRevalidate(Entity<GridAtmosphereComponent> ent, GasTileOverlayComponent? visuals)
+        private bool ProcessRevalidate(Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent)
         {
-            var (owner, atmosphere) = ent;
+            if (ent.Comp4.MapUid == null)
+            {
+                Log.Error($"Attempted to process atmosphere on a map-less grid? Grid: {ToPrettyString(ent)}");
+                return true;
+            }
+
+            var (uid, atmosphere, visuals, grid, xform) = ent;
+            var volume = GetVolumeForTiles(grid);
+            TryComp(xform.MapUid, out MapAtmosphereComponent? mapAtmos);
+
             if (!atmosphere.ProcessingPaused)
             {
-                atmosphere.CurrentRunInvalidatedCoordinates.Clear();
-                atmosphere.CurrentRunInvalidatedCoordinates.EnsureCapacity(atmosphere.InvalidatedCoords.Count);
-                foreach (var tile in atmosphere.InvalidatedCoords)
+                atmosphere.CurrentRunInvalidatedTiles.Clear();
+                atmosphere.CurrentRunInvalidatedTiles.EnsureCapacity(atmosphere.InvalidatedCoords.Count);
+                foreach (var indices in atmosphere.InvalidatedCoords)
                 {
-                    atmosphere.CurrentRunInvalidatedCoordinates.Enqueue(tile);
+                    var tile = GetOrNewTile(uid, atmosphere, indices);
+                    atmosphere.CurrentRunInvalidatedTiles.Enqueue(tile);
+
+                    // Update tile.IsSpace and tile.MapAtmosphere, and tile.AirtightData.
+                    UpdateTileData(ent, mapAtmos, tile);
                 }
                 atmosphere.InvalidatedCoords.Clear();
+
+                if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime)
+                    return false;
             }
 
-            if (!TryComp(owner, out MapGridComponent? mapGridComp))
-                return true;
+            var number = 0;
+            while (atmosphere.CurrentRunInvalidatedTiles.TryDequeue(out var tile))
+            {
+                DebugTools.Assert(atmosphere.Tiles.GetValueOrDefault(tile.GridIndices) == tile);
+                UpdateAdjacentTiles(ent, tile, activate: true);
+                UpdateTileAir(ent, tile, volume);
+                InvalidateVisuals(uid, tile.GridIndices, visuals);
 
-            var mapUid = _mapManager.GetMapEntityIdOrThrow(Transform(owner).MapID);
+                if (number++ < InvalidCoordinatesLagCheckIterations)
+                    continue;
 
-            var volume = GetVolumeForTiles(mapGridComp);
+                number = 0;
+                // Process the rest next time.
+                if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime)
+                    return false;
+            }
 
-            var number = 0;
-            while (atmosphere.CurrentRunInvalidatedCoordinates.TryDequeue(out var indices))
+            TrimDisconnectedMapTiles(ent);
+            return true;
+        }
+
+        /// <summary>
+        /// This method queued a tile and all of its neighbours up for processing by <see cref="TrimDisconnectedMapTiles"/>.
+        /// </summary>
+        public void QueueTileTrim(GridAtmosphereComponent atmos, TileAtmosphere tile)
+        {
+            if (!tile.TrimQueued)
             {
-                if (!atmosphere.Tiles.TryGetValue(indices, out var tile))
+                tile.TrimQueued = true;
+                atmos.PossiblyDisconnectedTiles.Add(tile);
+            }
+
+            for (var i = 0; i < Atmospherics.Directions; i++)
+            {
+                var direction = (AtmosDirection) (1 << i);
+                var indices = tile.GridIndices.Offset(direction);
+                if (atmos.Tiles.TryGetValue(indices, out var adj)
+                    && adj.NoGridTile
+                    && !adj.TrimQueued)
                 {
-                    tile = new TileAtmosphere(owner, indices,
-                        new GasMixture(volume) { Temperature = Atmospherics.T20C });
-                    atmosphere.Tiles[indices] = tile;
+                    adj.TrimQueued = true;
+                    atmos.PossiblyDisconnectedTiles.Add(adj);
                 }
+            }
+        }
 
-                var airBlockedEv = new IsTileAirBlockedMethodEvent(owner, indices, MapGridComponent:mapGridComp);
-                GridIsTileAirBlocked(owner, atmosphere, ref airBlockedEv);
-                var isAirBlocked = airBlockedEv.Result;
+        /// <summary>
+        /// Tiles in a <see cref="GridAtmosphereComponent"/> are either grid-tiles, or they they should be are tiles
+        /// adjacent to grid-tiles that represent the map's atmosphere. This method trims any map-tiles that are no longer
+        /// adjacent to any grid-tiles.
+        /// </summary>
+        private void TrimDisconnectedMapTiles(
+            Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent)
+        {
+            var atmos = ent.Comp1;
 
-                var oldBlocked = tile.BlockedAirflow;
-                var updateAdjacentEv = new UpdateAdjacentMethodEvent(owner, indices, mapGridComp);
-                GridUpdateAdjacent(owner, atmosphere, ref updateAdjacentEv);
+            foreach (var tile in atmos.PossiblyDisconnectedTiles)
+            {
+                tile.TrimQueued = false;
+                if (!tile.NoGridTile)
+                    continue;
 
-                // Blocked airflow changed, rebuild excited groups!
-                if (tile.Excited && tile.BlockedAirflow != oldBlocked)
+                var connected = false;
+                for (var i = 0; i < Atmospherics.Directions; i++)
                 {
-                    RemoveActiveTile(atmosphere, tile);
+                    var indices = tile.GridIndices.Offset((AtmosDirection) (1 << i));
+                    if (_map.TryGetTile(ent.Comp3, indices, out var gridTile) && !gridTile.IsEmpty)
+                    {
+                        connected = true;
+                        break;
+                    }
                 }
 
-                // Call this instead of the grid method as the map has a say on whether the tile is space or not.
-                if ((!mapGridComp.TryGetTileRef(indices, out var t) || t.IsSpace(_tileDefinitionManager)) && !isAirBlocked)
+                if (!connected)
                 {
-                    tile.Air = GetTileMixture(null, mapUid, indices);
-                    tile.MolesArchived = tile.Air != null ? new float[Atmospherics.AdjustedNumberOfGases] : null;
-                    tile.Space = IsTileSpace(null, mapUid, indices, mapGridComp);
+                    RemoveActiveTile(atmos, tile);
+                    atmos.Tiles.Remove(tile.GridIndices);
                 }
-                else if (isAirBlocked)
+            }
+
+            atmos.PossiblyDisconnectedTiles.Clear();
+        }
+
+        /// <summary>
+        /// Checks whether a tile has a corresponding grid-tile, or whether it is a "map" tile. Also checks whether the
+        /// tile should be considered "space"
+        /// </summary>
+        private void UpdateTileData(
+            Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
+            MapAtmosphereComponent? mapAtmos,
+            TileAtmosphere tile)
+        {
+            var idx = tile.GridIndices;
+            bool mapAtmosphere;
+            if (_map.TryGetTile(ent.Comp3, idx, out var gTile) && !gTile.IsEmpty)
+            {
+                var contentDef = (ContentTileDefinition) _tileDefinitionManager[gTile.TypeId];
+                mapAtmosphere = contentDef.MapAtmosphere;
+                tile.ThermalConductivity = contentDef.ThermalConductivity;
+                tile.HeatCapacity = contentDef.HeatCapacity;
+                tile.NoGridTile = false;
+            }
+            else
+            {
+                mapAtmosphere = true;
+                tile.ThermalConductivity =  0.5f;
+                tile.HeatCapacity = float.PositiveInfinity;
+
+                if (!tile.NoGridTile)
                 {
-                    if (airBlockedEv.NoAir)
-                    {
-                        tile.Air = null;
-                        tile.MolesArchived = null;
-                        tile.ArchivedCycle = 0;
-                        tile.LastShare = 0f;
-                        tile.Hotspot = new Hotspot();
-                    }
+                    tile.NoGridTile = true;
+
+                    // This tile just became a non-grid atmos tile.
+                    // It, or one of its neighbours, might now be completely disconnected from the grid.
+                    QueueTileTrim(ent.Comp1, tile);
                 }
-                else
-                {
-                    if (tile.Air == null && NeedsVacuumFixing(mapGridComp, indices))
-                    {
-                        var vacuumEv = new FixTileVacuumMethodEvent(owner, indices);
-                        GridFixTileVacuum(owner, atmosphere, ref vacuumEv);
-                    }
+            }
 
-                    // Tile used to be space, but isn't anymore.
-                    if (tile.Space || (tile.Air?.Immutable ?? false))
-                    {
-                        tile.Air = null;
-                        tile.MolesArchived = null;
-                        tile.ArchivedCycle = 0;
-                        tile.LastShare = 0f;
-                        tile.Space = false;
-                    }
+            UpdateAirtightData(ent.Owner, ent.Comp1, ent.Comp3, tile);
 
-                    tile.Air ??= new GasMixture(volume){Temperature = Atmospherics.T20C};
-                    tile.MolesArchived ??= new float[Atmospherics.AdjustedNumberOfGases];
+            if (mapAtmosphere)
+            {
+                if (!tile.MapAtmosphere)
+                {
+                    (tile.Air, tile.Space) = GetDefaultMapAtmosphere(mapAtmos);
+                    tile.MapAtmosphere = true;
+                    ent.Comp1.MapTiles.Add(tile);
                 }
 
-                // We activate the tile.
-                AddActiveTile(atmosphere, tile);
+                DebugTools.AssertNotNull(tile.Air);
+                DebugTools.Assert(tile.Air?.Immutable ?? false);
+                return;
+            }
 
-                // TODO ATMOS: Query all the contents of this tile (like walls) and calculate the correct thermal conductivity and heat capacity
-                var tileDef = mapGridComp.TryGetTileRef(indices, out var tileRef)
-                    ? tileRef.GetContentTileDefinition(_tileDefinitionManager)
-                    : null;
+            if (!tile.MapAtmosphere)
+                return;
 
-                tile.ThermalConductivity = tileDef?.ThermalConductivity ?? 0.5f;
-                tile.HeatCapacity = tileDef?.HeatCapacity ?? float.PositiveInfinity;
-                InvalidateVisuals(owner, indices, visuals);
+            // Tile used to be exposed to the map's atmosphere, but isn't anymore.
+            RemoveMapAtmos(ent.Comp1, tile);
+        }
 
-                for (var i = 0; i < Atmospherics.Directions; i++)
-                {
-                    var direction = (AtmosDirection) (1 << i);
-                    var otherIndices = indices.Offset(direction);
+        private void RemoveMapAtmos(GridAtmosphereComponent atmos, TileAtmosphere tile)
+        {
+            DebugTools.Assert(tile.MapAtmosphere);
+            DebugTools.AssertNotNull(tile.Air);
+            DebugTools.Assert(tile.Air?.Immutable ?? false);
+            tile.MapAtmosphere = false;
+            atmos.MapTiles.Remove(tile);
+            tile.Air = null;
+            Array.Clear(tile.MolesArchived);
+            tile.ArchivedCycle = 0;
+            tile.LastShare = 0f;
+            tile.Space = false;
+        }
 
-                    if (atmosphere.Tiles.TryGetValue(otherIndices, out var otherTile))
-                        AddActiveTile(atmosphere, otherTile);
-                }
+        /// <summary>
+        /// Check whether a grid-tile should have an air mixture, and give it one if it doesn't already have one.
+        /// </summary>
+        private void UpdateTileAir(
+            Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
+            TileAtmosphere tile,
+            float volume)
+        {
+            if (tile.MapAtmosphere)
+            {
+                DebugTools.AssertNotNull(tile.Air);
+                DebugTools.Assert(tile.Air?.Immutable ?? false);
+                return;
+            }
 
-                if (number++ < InvalidCoordinatesLagCheckIterations)
-                    continue;
+            var data = tile.AirtightData;
+            var fullyBlocked = data.BlockedDirections == AtmosDirection.All;
 
-                number = 0;
-                // Process the rest next time.
-                if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime)
-                {
-                    return false;
-                }
+            if (fullyBlocked && data.NoAirWhenBlocked)
+            {
+                if (tile.Air == null)
+                    return;
+
+                tile.Air = null;
+                Array.Clear(tile.MolesArchived);
+                tile.ArchivedCycle = 0;
+                tile.LastShare = 0f;
+                tile.Hotspot = new Hotspot();
+                return;
             }
 
-            return true;
+            if (tile.Air != null)
+                return;
+
+            tile.Air = new GasMixture(volume){Temperature = Atmospherics.T20C};
+
+            if (data.FixVacuum)
+                GridFixTileVacuum(ent, tile, volume);
         }
 
         private void QueueRunTiles(
@@ -170,19 +288,16 @@ namespace Content.Server.Atmos.EntitySystems
             }
         }
 
-        private bool ProcessTileEqualize(Entity<GridAtmosphereComponent> ent, GasTileOverlayComponent? visuals)
+        private bool ProcessTileEqualize(Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent)
         {
-            var (uid, atmosphere) = ent;
+            var atmosphere = ent.Comp1;
             if (!atmosphere.ProcessingPaused)
                 QueueRunTiles(atmosphere.CurrentRunTiles, atmosphere.ActiveTiles);
 
-            if (!TryComp(uid, out MapGridComponent? mapGridComp))
-                throw new Exception("Tried to process a grid atmosphere on an entity that isn't a grid!");
-
             var number = 0;
             while (atmosphere.CurrentRunTiles.TryDequeue(out var tile))
             {
-                EqualizePressureInZone((uid, mapGridComp, atmosphere), tile, atmosphere.UpdateCounter, visuals);
+                EqualizePressureInZone(ent, tile, atmosphere.UpdateCounter);
 
                 if (number++ < LagCheckIterations)
                     continue;
@@ -198,7 +313,7 @@ namespace Content.Server.Atmos.EntitySystems
             return true;
         }
 
-        private bool ProcessActiveTiles(GridAtmosphereComponent atmosphere, GasTileOverlayComponent? visuals)
+        private bool ProcessActiveTiles(GridAtmosphereComponent atmosphere, GasTileOverlayComponent visuals)
         {
             if(!atmosphere.ProcessingPaused)
                 QueueRunTiles(atmosphere.CurrentRunTiles, atmosphere.ActiveTiles);
@@ -240,11 +355,11 @@ namespace Content.Server.Atmos.EntitySystems
                 excitedGroup.BreakdownCooldown++;
                 excitedGroup.DismantleCooldown++;
 
-                if(excitedGroup.BreakdownCooldown > Atmospherics.ExcitedGroupBreakdownCycles)
+                if (excitedGroup.BreakdownCooldown > Atmospherics.ExcitedGroupBreakdownCycles)
                     ExcitedGroupSelfBreakdown(gridAtmosphere, excitedGroup);
-
-                else if(excitedGroup.DismantleCooldown > Atmospherics.ExcitedGroupsDismantleCycles)
-                    ExcitedGroupDismantle(gridAtmosphere, excitedGroup);
+                else if (excitedGroup.DismantleCooldown > Atmospherics.ExcitedGroupsDismantleCycles)
+                    DeactivateGroupTiles(gridAtmosphere, excitedGroup);
+                // TODO ATMOS. What is the point of this? why is this only de-exciting the group? Shouldn't it also dismantle it?
 
                 if (number++ < LagCheckIterations)
                     continue;
@@ -435,10 +550,10 @@ namespace Content.Server.Atmos.EntitySystems
                 _currentRunAtmosphereIndex = 0;
                 _currentRunAtmosphere.Clear();
 
-                var query = EntityQueryEnumerator<GridAtmosphereComponent>();
-                while (query.MoveNext(out var uid, out var grid))
+                var query = EntityQueryEnumerator<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent>();
+                while (query.MoveNext(out var uid, out var atmos, out var overlay, out var grid, out var xform ))
                 {
-                    _currentRunAtmosphere.Add((uid, grid));
+                    _currentRunAtmosphere.Add((uid, atmos, overlay, grid, xform));
                 }
             }
 
@@ -448,8 +563,7 @@ namespace Content.Server.Atmos.EntitySystems
             for (; _currentRunAtmosphereIndex < _currentRunAtmosphere.Count; _currentRunAtmosphereIndex++)
             {
                 var ent = _currentRunAtmosphere[_currentRunAtmosphereIndex];
-                var (owner, atmosphere) = ent;
-                TryComp(owner, out GasTileOverlayComponent? visuals);
+                var (owner, atmosphere, visuals, grid, xform) = ent;
 
                 if (!TryComp(owner, out TransformComponent? x)
                     || x.MapUid == null
@@ -474,13 +588,14 @@ namespace Content.Server.Atmos.EntitySystems
                 switch (atmosphere.State)
                 {
                     case AtmosphereProcessingState.Revalidate:
-                        if (!ProcessRevalidate(ent, visuals))
+                        if (!ProcessRevalidate(ent))
                         {
                             atmosphere.ProcessingPaused = true;
                             return;
                         }
 
                         atmosphere.ProcessingPaused = false;
+
                         // Next state depends on whether monstermos equalization is enabled or not.
                         // Note: We do this here instead of on the tile equalization step to prevent ending it early.
                         //       Therefore, a change to this CVar might only be applied after that step is over.
@@ -489,7 +604,7 @@ namespace Content.Server.Atmos.EntitySystems
                             : AtmosphereProcessingState.ActiveTiles;
                         continue;
                     case AtmosphereProcessingState.TileEqualize:
-                        if (!ProcessTileEqualize(ent, visuals))
+                        if (!ProcessTileEqualize(ent))
                         {
                             atmosphere.ProcessingPaused = true;
                             return;
@@ -499,7 +614,7 @@ namespace Content.Server.Atmos.EntitySystems
                         atmosphere.State = AtmosphereProcessingState.ActiveTiles;
                         continue;
                     case AtmosphereProcessingState.ActiveTiles:
-                        if (!ProcessActiveTiles(atmosphere, visuals))
+                        if (!ProcessActiveTiles(ent, ent))
                         {
                             atmosphere.ProcessingPaused = true;
                             return;
@@ -520,7 +635,7 @@ namespace Content.Server.Atmos.EntitySystems
                         atmosphere.State = AtmosphereProcessingState.HighPressureDelta;
                         continue;
                     case AtmosphereProcessingState.HighPressureDelta:
-                        if (!ProcessHighPressureDelta(ent))
+                        if (!ProcessHighPressureDelta((ent, ent)))
                         {
                             atmosphere.ProcessingPaused = true;
                             return;
index 33fa16a6c68b4dec783f5869b7207263095685f7..5c73cf112466d86bfb4b25a7b2a443676cc774d4 100644 (file)
@@ -1,5 +1,6 @@
 using Content.Server.Atmos.Components;
 using Content.Shared.Atmos;
+using Robust.Shared.Map.Components;
 
 namespace Content.Server.Atmos.EntitySystems
 {
@@ -12,7 +13,8 @@ namespace Content.Server.Atmos.EntitySystems
             for(var i = 0; i < Atmospherics.Directions; i++)
             {
                 var direction = (AtmosDirection) (1 << i);
-                if (!directions.IsFlagSet(direction)) continue;
+                if (!directions.IsFlagSet(direction))
+                    continue;
 
                 var adjacent = tile.AdjacentTiles[direction.ToIndex()];
 
@@ -92,7 +94,9 @@ namespace Content.Server.Atmos.EntitySystems
         {
             if (tile.Air == null)
             {
-                if (other.Tile != null)
+                // TODO ATMOS: why does this need to check if a tile exists if it doesn't use the tile?
+                if (TryComp<MapGridComponent>(other.GridIndex, out var grid)
+                    && _mapSystem.TryGetTileRef(other.GridIndex, grid, other.GridIndices, out var _))
                 {
                     TemperatureShareOpenToSolid(other, tile);
                 }
index 699c2a70aef0dcb7670ebaf6798c3fbccc135261..9b0d0d9670d4a318200777c81b891af27a9ba4c5 100644 (file)
@@ -41,20 +41,6 @@ public partial class AtmosphereSystem
         _gasTileOverlaySystem.Invalidate(gridUid, tile, comp);
     }
 
-    public bool NeedsVacuumFixing(MapGridComponent mapGrid, Vector2i indices)
-    {
-        var value = false;
-
-        var enumerator = GetObstructingComponentsEnumerator(mapGrid, indices);
-
-        while (enumerator.MoveNext(out var airtight))
-        {
-            value |= airtight.FixVacuum;
-        }
-
-        return value;
-    }
-
     /// <summary>
     ///     Gets the volume in liters for a number of tiles, on a specific grid.
     /// </summary>
@@ -66,34 +52,44 @@ public partial class AtmosphereSystem
         return Atmospherics.CellVolume * mapGrid.TileSize * tiles;
     }
 
-    /// <summary>
-    ///     Gets all obstructing <see cref="AirtightComponent"/> instances in a specific tile.
-    /// </summary>
-    /// <param name="mapGrid">The grid where to get the tile.</param>
-    /// <param name="tile">The indices of the tile.</param>
-    /// <returns>The enumerator for the airtight components.</returns>
-    public AtmosObstructionEnumerator GetObstructingComponentsEnumerator(MapGridComponent mapGrid, Vector2i tile)
+    public readonly record struct AirtightData(AtmosDirection BlockedDirections, bool NoAirWhenBlocked,
+        bool FixVacuum);
+
+    private void UpdateAirtightData(EntityUid uid, GridAtmosphereComponent atmos, MapGridComponent grid, TileAtmosphere tile)
     {
-        var ancEnumerator = mapGrid.GetAnchoredEntitiesEnumerator(tile);
-        var airQuery = GetEntityQuery<AirtightComponent>();
+        var oldBlocked = tile.AirtightData.BlockedDirections;
+
+        tile.AirtightData = tile.NoGridTile
+            ? default
+            : GetAirtightData(uid, grid, tile.GridIndices);
 
-        var enumerator = new AtmosObstructionEnumerator(ancEnumerator, airQuery);
-        return enumerator;
+        if (tile.AirtightData.BlockedDirections != oldBlocked && tile.ExcitedGroup != null)
+            ExcitedGroupDispose(atmos, tile.ExcitedGroup);
     }
 
-    private AtmosDirection GetBlockedDirections(MapGridComponent mapGrid, Vector2i indices)
+    private AirtightData GetAirtightData(EntityUid uid, MapGridComponent grid, Vector2i tile)
     {
-        var value = AtmosDirection.Invalid;
+        var blockedDirs = AtmosDirection.Invalid;
+        var noAirWhenBlocked = false;
+        var fixVacuum = false;
 
-        var enumerator = GetObstructingComponentsEnumerator(mapGrid, indices);
-
-        while (enumerator.MoveNext(out var airtight))
+        foreach (var ent in _map.GetAnchoredEntities(uid, grid, tile))
         {
-            if(airtight.AirBlocked)
-                value |= airtight.AirBlockedDirection;
+            if (!_airtightQuery.TryGetComponent(ent, out var airtight))
+                continue;
+
+            if(!airtight.AirBlocked)
+                continue;
+
+            blockedDirs |= airtight.AirBlockedDirection;
+            noAirWhenBlocked |= airtight.NoAirWhenFullyAirBlocked;
+            fixVacuum |= airtight.FixVacuum;
+
+            if (blockedDirs == AtmosDirection.All && noAirWhenBlocked && fixVacuum)
+                break;
         }
 
-        return value;
+        return new AirtightData(blockedDirs, noAirWhenBlocked, fixVacuum);
     }
 
     /// <summary>
index dd2a967559184ad9809e2392fabe29303254270d..d2f40e77169a9b06ece438a69c920889ab951994 100644 (file)
@@ -4,6 +4,7 @@ using Content.Server.Body.Systems;
 using Content.Server.Fluids.EntitySystems;
 using Content.Server.NodeContainer.EntitySystems;
 using Content.Shared.Atmos.EntitySystems;
+using Content.Shared.Doors.Components;
 using Content.Shared.Maps;
 using JetBrains.Annotations;
 using Robust.Server.GameObjects;
@@ -40,6 +41,9 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
     private const float ExposedUpdateDelay = 1f;
     private float _exposedTimer = 0f;
 
+    private EntityQuery<GridAtmosphereComponent> _atmosQuery;
+    private EntityQuery<AirtightComponent> _airtightQuery;
+    private EntityQuery<FirelockComponent> _firelockQuery;
     private HashSet<EntityUid> _entSet = new();
 
     public override void Initialize()
@@ -55,6 +59,9 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
         InitializeGridAtmosphere();
         InitializeMap();
 
+        _atmosQuery = GetEntityQuery<GridAtmosphereComponent>();
+        _airtightQuery = GetEntityQuery<AirtightComponent>();
+        _firelockQuery = GetEntityQuery<FirelockComponent>();
 
         SubscribeLocalEvent<TileChangedEvent>(OnTileChanged);
 
index 203c747e29ed9de64c3e90c01ad0dc4e6144153a..70e3eef3c443f912fb9b9679d7bcbd8d34418f3b 100644 (file)
@@ -27,8 +27,12 @@ public sealed class AutomaticAtmosSystem : EntitySystem
         // Also, these calls are surprisingly slow.
         // TODO: Make tiledefmanager cache the IsSpace property, and turn this lookup-through-two-interfaces into
         // TODO: a simple array lookup, as tile IDs are likely contiguous, and there's at most 2^16 possibilities anyway.
-        if (!((ev.OldTile.IsSpace(_tileDefinitionManager) && !ev.NewTile.IsSpace(_tileDefinitionManager)) ||
-            (!ev.OldTile.IsSpace(_tileDefinitionManager) && ev.NewTile.IsSpace(_tileDefinitionManager))) ||
+
+        var oldSpace = ev.OldTile.IsSpace(_tileDefinitionManager);
+        var newSpace = ev.NewTile.IsSpace(_tileDefinitionManager);
+
+        if (!(oldSpace && !newSpace ||
+            !oldSpace && newSpace) ||
             _atmosphereSystem.HasAtmosphere(ev.Entity))
             return;
 
index d5563c170cca085a4c709a9008294d5da78a59ef..2eedb1c6a7fb7f0278497131d7a8c16e48a853d1 100644 (file)
@@ -260,13 +260,13 @@ namespace Content.Server.Atmos.EntitySystems
             {
                 var gas = _atmo.GetGas(i);
 
-                if (mixture?.Moles[i] <= UIMinMoles)
+                if (mixture?[i] <= UIMinMoles)
                     continue;
 
                 if (mixture != null)
                 {
                     var gasName = Loc.GetString(gas.Name);
-                    gases.Add(new GasEntry(gasName, mixture.Moles[i], gas.Color));
+                    gases.Add(new GasEntry(gasName, mixture[i], gas.Color));
                 }
             }
 
index 94e095892a37e86f85ff173c41e75ba5c99de0c9..6d49feb0181018ce4ccf7bfb38bc938b3dba6555 100644 (file)
@@ -172,7 +172,7 @@ namespace Content.Server.Atmos.EntitySystems
             {
                 var id = VisibleGasId[i];
                 var gas = _atmosphereSystem.GetGas(id);
-                var moles = mixture?.Moles[id] ?? 0f;
+                var moles = mixture?[id] ?? 0f;
                 ref var opacity = ref data.Opacity[i];
 
                 if (moles < gas.GasMolesVisible)
@@ -217,13 +217,13 @@ namespace Content.Server.Atmos.EntitySystems
                 oldData = new GasOverlayData(tile.Hotspot.State, oldData.Opacity);
             }
 
-            if (tile.Air != null)
+            if (tile is {Air: not null, NoGridTile: false})
             {
                 for (var i = 0; i < VisibleGasId.Length; i++)
                 {
                     var id = VisibleGasId[i];
                     var gas = _atmosphereSystem.GetGas(id);
-                    var moles = tile.Air.Moles[id];
+                    var moles = tile.Air[id];
                     ref var oldOpacity = ref oldData.Opacity[i];
 
                     if (moles < gas.GasMolesVisible)
index 0a2ef235a71ed67acd4f7cdeb82ec60427b96a5c..77fd7018333b032926dc81843f3f3e78065a165a 100644 (file)
@@ -3,6 +3,7 @@ using System.Linq;
 using System.Runtime.CompilerServices;
 using Content.Server.Atmos.Reactions;
 using Content.Shared.Atmos;
+using Content.Shared.Atmos.EntitySystems;
 using Robust.Shared.Serialization;
 using Robust.Shared.Utility;
 
@@ -17,11 +18,13 @@ namespace Content.Server.Atmos
     {
         public static GasMixture SpaceGas => new() {Volume = Atmospherics.CellVolume, Temperature = Atmospherics.TCMB, Immutable = true};
 
-        // This must always have a length that is a multiple of 4 for SIMD acceleration.
-        [DataField("moles")]
-        [ViewVariables(VVAccess.ReadWrite)]
+        // No access, to ensure immutable mixtures are never accidentally mutated.
+        [Access(typeof(SharedAtmosphereSystem), typeof(SharedAtmosDebugOverlaySystem), Other = AccessPermissions.None)]
+        [DataField]
         public float[] Moles = new float[Atmospherics.AdjustedNumberOfGases];
 
+        public float this[int gas] => Moles[gas];
+
         [DataField("temperature")]
         [ViewVariables(VVAccess.ReadWrite)]
         private float _temperature = Atmospherics.TCMB;
@@ -80,6 +83,19 @@ namespace Content.Server.Atmos
             Volume = volume;
         }
 
+        public GasMixture(float[] moles, float temp, float volume = Atmospherics.CellVolume)
+        {
+            if (moles.Length != Atmospherics.AdjustedNumberOfGases)
+                throw new InvalidOperationException($"Invalid mole array length");
+
+            if (volume < 0)
+                volume = 0;
+
+            _temperature = temp;
+            Moles = moles;
+            Volume = volume;
+        }
+
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public void MarkImmutable()
         {
@@ -117,15 +133,16 @@ namespace Content.Server.Atmos
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public void AdjustMoles(int gasId, float quantity)
         {
-            if (!Immutable)
-            {
-                if (!float.IsFinite(quantity))
-                    throw new ArgumentException($"Invalid quantity \"{quantity}\" specified!", nameof(quantity));
+            if (Immutable)
+                return;
 
-                // Clamping is needed because x - x can be negative with floating point numbers. If we don't
-                // clamp here, the caller always has to call GetMoles(), clamp, then SetMoles().
-                Moles[gasId] = MathF.Max(Moles[gasId] + quantity, 0);
-            }
+            if (!float.IsFinite(quantity))
+                throw new ArgumentException($"Invalid quantity \"{quantity}\" specified!", nameof(quantity));
+
+            // Clamping is needed because x - x can be negative with floating point numbers. If we don't
+            // clamp here, the caller always has to call GetMoles(), clamp, then SetMoles().
+            ref var moles = ref Moles[gasId];
+            moles = MathF.Max(moles + quantity, 0);
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -163,7 +180,8 @@ namespace Content.Server.Atmos
             {
                 var moles = Moles[i];
                 var otherMoles = removed.Moles[i];
-                if (moles < Atmospherics.GasMinMoles || float.IsNaN(moles))
+
+                if ((moles < Atmospherics.GasMinMoles || float.IsNaN(moles)) && !Immutable)
                     Moles[i] = 0;
 
                 if (otherMoles < Atmospherics.GasMinMoles || float.IsNaN(otherMoles))
@@ -202,6 +220,9 @@ namespace Content.Server.Atmos
 
         void ISerializationHooks.AfterDeserialization()
         {
+            // ISerializationHooks is obsolete.
+            // TODO add fixed-length-array serializer
+
             // The arrays MUST have a specific length.
             Array.Resize(ref Moles, Atmospherics.AdjustedNumberOfGases);
         }
@@ -229,8 +250,12 @@ namespace Content.Server.Atmos
 
         public bool Equals(GasMixture? other)
         {
-            if (ReferenceEquals(null, other)) return false;
-            if (ReferenceEquals(this, other)) return true;
+            if (ReferenceEquals(this, other))
+                return true;
+
+            if (ReferenceEquals(null, other))
+                return false;
+
             return Moles.SequenceEqual(other.Moles)
                    && _temperature.Equals(other._temperature)
                    && ReactionResults.SequenceEqual(other.ReactionResults)
@@ -258,11 +283,13 @@ namespace Content.Server.Atmos
 
         public GasMixture Clone()
         {
+            if (Immutable)
+                return this;
+
             var newMixture = new GasMixture()
             {
                 Moles = (float[])Moles.Clone(),
                 _temperature = _temperature,
-                Immutable = Immutable,
                 Volume = Volume,
             };
             return newMixture;
index 64d02d793bc0c9b244505e08e0bff40787552900..ad647fad1b8fd20e8fe9ffd7313f48742c6ea84b 100644 (file)
@@ -136,7 +136,7 @@ public sealed class GasCanisterSystem : EntitySystem
 
         for (int i = 0; i < containedGasArray.Length; i++)
         {
-            containedGasDict.Add((Gas)i, canister.Air.Moles[i]);
+            containedGasDict.Add((Gas)i, canister.Air[i]);
         }
 
         _adminLogger.Add(LogType.CanisterValve, impact, $"{ToPrettyString(args.Session.AttachedEntity.GetValueOrDefault()):player} set the valve on {ToPrettyString(uid):canister} to {args.Valve:valveState} while it contained [{string.Join(", ", containedGasDict)}]");
index 491bf60062734968cf530435b5ee3f8a96053baa..852542ec6cd29e59a6a23b67e01a6b61aa0a68b2 100644 (file)
@@ -45,7 +45,7 @@ public sealed class GasCondenserSystem : EntitySystem
         var removed = inlet.Air.Remove(molesToConvert);
         for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
         {
-            var moles = removed.Moles[i];
+            var moles = removed[i];
             if (moles <= 0)
                 continue;
 
index 7d385c530a922f0900cf64dff4ef61701475033b..71e4c2d0def536047b49c479c364b38e2e90ecf0 100644 (file)
@@ -183,13 +183,7 @@ public sealed partial class TileAtmosCollectionSerializer : ITypeSerializer<Dict
         target.Clear();
         foreach (var (key, val) in source)
         {
-            target.Add(key,
-                new TileAtmosphere(
-                    val.GridIndex,
-                    val.GridIndices,
-                    val.Air?.Clone(),
-                    val.Air?.Immutable ?? false,
-                    val.Space));
+            target.Add(key, new TileAtmosphere(val));
         }
     }
 }
index a380154296eb7f668bf794dfc3715e3b75b393f1..0dd35a29e762760682ddaf1919baa6b44465e327 100644 (file)
@@ -1,3 +1,4 @@
+using Content.Server.Atmos.Components;
 using Content.Server.Atmos.EntitySystems;
 using Content.Shared.Atmos;
 using Content.Shared.Maps;
@@ -51,6 +52,10 @@ namespace Content.Server.Atmos
         [ViewVariables]
         public readonly TileAtmosphere?[] AdjacentTiles = new TileAtmosphere[Atmospherics.Directions];
 
+        /// <summary>
+        /// Neighbouring tiles to which air can flow. This is a combination of this tile's unblocked direction, and the
+        /// unblocked directions on adjacent tiles.
+        /// </summary>
         [ViewVariables]
         public AtmosDirection AdjacentBits = AtmosDirection.Invalid;
 
@@ -72,10 +77,7 @@ namespace Content.Server.Atmos
         public EntityUid GridIndex { get; set; }
 
         [ViewVariables]
-        public TileRef? Tile => GridIndices.GetTileRef(GridIndex);
-
-        [ViewVariables]
-        public Vector2i GridIndices { get; }
+        public Vector2i GridIndices;
 
         [ViewVariables]
         public ExcitedGroup? ExcitedGroup { get; set; }
@@ -92,7 +94,7 @@ namespace Content.Server.Atmos
         public float LastShare;
 
         [ViewVariables]
-        public float[]? MolesArchived;
+        public readonly float[] MolesArchived = new float[Atmospherics.AdjustedNumberOfGases];
 
         GasMixture IGasMixtureHolder.Air
         {
@@ -103,8 +105,31 @@ namespace Content.Server.Atmos
         [ViewVariables]
         public float MaxFireTemperatureSustained { get; set; }
 
+        /// <summary>
+        /// If true, then this tile is directly exposed to the map's atmosphere, either because the grid has no tile at
+        /// this position, or because the tile type is not airtight.
+        /// </summary>
+        [ViewVariables]
+        public bool MapAtmosphere;
+
+        /// <summary>
+        /// If true, this tile does not actually exist on the grid, it only exists to represent the map's atmosphere for
+        /// adjacent grid tiles.
+        /// </summary>
         [ViewVariables]
-        public AtmosDirection BlockedAirflow { get; set; } = AtmosDirection.Invalid;
+        public bool NoGridTile;
+
+        /// <summary>
+        /// If true, this tile is queued for processing in <see cref="GridAtmosphereComponent.PossiblyDisconnectedTiles"/>
+        /// </summary>
+        [ViewVariables]
+        public bool TrimQueued;
+
+        /// <summary>
+        /// Cached information about airtight entities on this tile. This gets updated anytime a tile gets invalidated
+        /// (i.e., gets added to <see cref="GridAtmosphereComponent.InvalidatedCoords"/>).
+        /// </summary>
+        public AtmosphereSystem.AirtightData AirtightData;
 
         public TileAtmosphere(EntityUid gridIndex, Vector2i gridIndices, GasMixture? mixture = null, bool immutable = false, bool space = false)
         {
@@ -112,10 +137,24 @@ namespace Content.Server.Atmos
             GridIndices = gridIndices;
             Air = mixture;
             Space = space;
-            MolesArchived = Air != null ? new float[Atmospherics.AdjustedNumberOfGases] : null;
 
             if(immutable)
                 Air?.MarkImmutable();
         }
+
+        public TileAtmosphere(TileAtmosphere other)
+        {
+            GridIndex = other.GridIndex;
+            GridIndices = other.GridIndices;
+            Space = other.Space;
+            NoGridTile = other.NoGridTile;
+            MapAtmosphere = other.MapAtmosphere;
+            Air = other.Air?.Clone();
+            Array.Copy(other.MolesArchived, MolesArchived, MolesArchived.Length);
+        }
+
+        public TileAtmosphere()
+        {
+        }
     }
 }
index b5bac507391a8c5aa22957cfd9cd86e62bec7baf..4b60f8814b5335d020298084099293643dfeb0cb 100644 (file)
@@ -77,7 +77,7 @@ public sealed class LungSystem : EntitySystem
         foreach (var gas in Enum.GetValues<Gas>())
         {
             var i = (int) gas;
-            var moles = lung.Air.Moles[i];
+            var moles = lung.Air[i];
             if (moles <= 0)
                 continue;
             var reagent = _atmosphereSystem.GasReagents[i];
index e12cab80a91996efb5e43e6818531a3942a5f133..e7466fbc85d7dbb80dc1204fd60079130652c185 100644 (file)
@@ -16,12 +16,15 @@ public sealed partial class ModifyLungGas : ReagentEffect
 
     public override void Effect(ReagentEffectArgs args)
     {
-        if (args.EntityManager.TryGetComponent<LungComponent>(args.OrganEntity, out var lung))
+        if (!args.EntityManager.TryGetComponent<LungComponent>(args.OrganEntity, out var lung))
+            return;
+
+        foreach (var (gas, ratio) in _ratios)
         {
-            foreach (var (gas, ratio) in _ratios)
-            {
-                lung.Air.Moles[(int) gas] += (ratio * args.Quantity.Float()) / Atmospherics.BreathMolesToReagentMultiplier;
-            }
+            var quantity = ratio * args.Quantity.Float() / Atmospherics.BreathMolesToReagentMultiplier;
+            if (quantity < 0)
+                quantity = Math.Max(quantity, -lung.Air[(int)gas]);
+            lung.Air.AdjustMoles(gas, quantity);
         }
     }
 }
index 91393baf8e3579c1779a7bde717886c7231520ee..429c4fdabfc95b965144ef548366617cb1fe53f2 100644 (file)
@@ -3,6 +3,7 @@ using Content.Shared.Examine;
 using Content.Shared.Maps;
 using JetBrains.Annotations;
 using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
 using Robust.Shared.Utility;
 
 namespace Content.Server.Construction.Conditions
@@ -49,7 +50,15 @@ namespace Content.Server.Construction.Conditions
             var transformSys = entityManager.System<SharedTransformSystem>();
             var indices = transform.Coordinates.ToVector2i(entityManager, IoCManager.Resolve<IMapManager>(), transformSys);
             var lookup = entityManager.EntitySysManager.GetEntitySystem<EntityLookupSystem>();
-            var entities = indices.GetEntitiesInTile(transform.GridUid.Value, LookupFlags.Approximate | LookupFlags.Static, lookup);
+
+
+            if (!entityManager.TryGetComponent<MapGridComponent>(transform.GridUid.Value, out var grid))
+                return !HasEntity;
+
+            if (!entityManager.System<SharedMapSystem>().TryGetTileRef(transform.GridUid.Value, grid, indices, out var tile))
+                return !HasEntity;
+
+            var entities = tile.GetEntitiesInTile(LookupFlags.Approximate | LookupFlags.Static, lookup);
 
             foreach (var ent in entities)
             {
index ba937d77ad71cbb1bccff17561cb0c3813992fd5..85d705846e1976b65832f7b6178ae162a99cf503 100644 (file)
@@ -493,7 +493,7 @@ public sealed partial class ExplosionSystem
         if (_tileDefinitionManager[tileRef.Tile.TypeId] is not ContentTileDefinition tileDef)
             return;
 
-        if (tileDef.IsSpace)
+        if (tileDef.MapAtmosphere)
             canCreateVacuum = true; // is already a vacuum.
 
         int tileBreakages = 0;
@@ -509,7 +509,7 @@ public sealed partial class ExplosionSystem
             if (_tileDefinitionManager[tileDef.BaseTurf] is not ContentTileDefinition newDef)
                 break;
 
-            if (newDef.IsSpace && !canCreateVacuum)
+            if (newDef.MapAtmosphere && !canCreateVacuum)
                 break;
 
             tileDef = newDef;
index c4c2300870d71678d635fe720b3f3ed12bcf0879..83cf9b9cb2d8c8b9f5040b74d9c3806f22719575 100644 (file)
@@ -1001,20 +1001,13 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
         light.AmbientLightColor = Color.FromHex("#D8B059");
         Dirty(mapUid, light, metadata);
 
-        // Atmos
-        var atmos = EnsureComp<MapAtmosphereComponent>(mapUid);
-
         var moles = new float[Atmospherics.AdjustedNumberOfGases];
         moles[(int) Gas.Oxygen] = 21.824779f;
         moles[(int) Gas.Nitrogen] = 82.10312f;
 
-        var mixture = new GasMixture(2500)
-        {
-            Temperature = 293.15f,
-            Moles = moles,
-        };
+        var mixture = new GasMixture(moles, Atmospherics.T20C);
 
-        _atmos.SetMapAtmosphere(mapUid, false, mixture, atmos);
+        _atmos.SetMapAtmosphere(mapUid, false, mixture);
     }
 
     /// <summary>
index c61599edfc9e7047a2ee7070aca90ddc63fa324e..76cf90c3693327cc2bed00ce8118f2f4a55deac6 100644 (file)
@@ -37,7 +37,7 @@ public sealed class GasPowerReceiverSystem : EntitySystem
         if (pipe.Air.Temperature <= component.MaxTemperature)
         {
             // we have enough gas, so we consume it and are powered
-            if (pipe.Air.Moles[(int) component.TargetGas] > component.MolesConsumedSec * timeDelta)
+            if (pipe.Air[(int) component.TargetGas] > component.MolesConsumedSec * timeDelta)
             {
                 pipe.Air.AdjustMoles(component.TargetGas, -component.MolesConsumedSec * timeDelta);
                 SetPowered(uid, component, true);
index 2776db2283ac32ec34290aea4253119f057fae0e..e2b17b58724dd5ac8c5911bd0d82eb169fcafb75 100644 (file)
@@ -125,11 +125,7 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
             air.Gases.CopyTo(moles, 0);
             var atmos = _entManager.EnsureComponent<MapAtmosphereComponent>(mapUid);
             _entManager.System<AtmosphereSystem>().SetMapSpace(mapUid, air.Space, atmos);
-            _entManager.System<AtmosphereSystem>().SetMapGasMixture(mapUid, new GasMixture(2500)
-            {
-                Temperature = mission.Temperature,
-                Moles = moles,
-            }, atmos);
+            _entManager.System<AtmosphereSystem>().SetMapGasMixture(mapUid, new GasMixture(moles, mission.Temperature), atmos);
 
             if (mission.Color != null)
             {
index 89951718236aae6f46adedc35810083c10d60ecd..5b2f3298a2b4022f47d1a41812f320df4ee08da1 100644 (file)
@@ -18,6 +18,7 @@ namespace Content.Server.Spreader;
 /// </summary>
 public sealed class SpreaderSystem : EntitySystem
 {
+    [Dependency] private readonly IMapManager _mapManager = default!;
     [Dependency] private readonly IPrototypeManager _prototype = default!;
     [Dependency] private readonly IRobustRandom _robustRandom = default!;
     [Dependency] private readonly SharedMapSystem _map = default!;
@@ -33,6 +34,8 @@ public sealed class SpreaderSystem : EntitySystem
     // TODO PERFORMANCE Assign each prototype to an index and convert dictionary to array
     private readonly Dictionary<EntityUid, Dictionary<string, int>> _gridUpdates = [];
 
+    private EntityQuery<EdgeSpreaderComponent> _query;
+
     public const float SpreadCooldownSeconds = 1;
 
     [ValidatePrototypeId<TagPrototype>]
@@ -47,6 +50,8 @@ public sealed class SpreaderSystem : EntitySystem
 
         SubscribeLocalEvent<EdgeSpreaderComponent, EntityTerminatingEvent>(OnTerminating);
         SetupPrototypes();
+
+        _query = GetEntityQuery<EdgeSpreaderComponent>();
     }
 
     private void OnPrototypeReload(PrototypesReloadedEventArgs obj)
@@ -66,13 +71,7 @@ public sealed class SpreaderSystem : EntitySystem
 
     private void OnAirtightChanged(ref AirtightChanged ev)
     {
-        var neighbors = GetSpreadableNeighbors(ev.Entity, ev.Airtight, ev.Position);
-
-        foreach (var neighbor in neighbors)
-        {
-            if (!TerminatingOrDeleted(neighbor))
-                EnsureComp<ActiveEdgeSpreaderComponent>(neighbor);
-        }
+        ActivateSpreadableNeighbors(ev.Entity, ev.Position);
     }
 
     private void OnGridInit(GridInitializeEvent ev)
@@ -82,13 +81,7 @@ public sealed class SpreaderSystem : EntitySystem
 
     private void OnTerminating(Entity<EdgeSpreaderComponent> entity, ref EntityTerminatingEvent args)
     {
-        var neighbors = GetSpreadableNeighbors(entity);
-
-        foreach (var neighbor in neighbors)
-        {
-            if (!TerminatingOrDeleted(neighbor))
-                EnsureComp<ActiveEdgeSpreaderComponent>(neighbor);
-        }
+        ActivateSpreadableNeighbors(entity);
     }
 
     /// <inheritdoc/>
@@ -254,8 +247,7 @@ public sealed class SpreaderSystem : EntitySystem
             if (!_map.TryGetTileRef(neighborEnt, neighborGrid, neighborPos, out var tileRef) || tileRef.Tile.IsEmpty)
                 continue;
 
-            var directionEnumerator =
-                _map.GetAnchoredEntitiesEnumerator(neighborEnt, neighborGrid, neighborPos);
+            var directionEnumerator = _map.GetAnchoredEntitiesEnumerator(neighborEnt, neighborGrid, neighborPos);
             var occupied = false;
 
             while (directionEnumerator.MoveNext(out var ent))
@@ -277,8 +269,7 @@ public sealed class SpreaderSystem : EntitySystem
                 continue;
 
             var oldCount = occupiedTiles.Count;
-            directionEnumerator =
-                _map.GetAnchoredEntitiesEnumerator(neighborEnt, neighborGrid, neighborPos);
+            directionEnumerator = _map.GetAnchoredEntitiesEnumerator(neighborEnt, neighborGrid, neighborPos);
 
             while (directionEnumerator.MoveNext(out var ent))
             {
@@ -299,14 +290,11 @@ public sealed class SpreaderSystem : EntitySystem
     }
 
     /// <summary>
-    /// Given an entity, this returns a list of all adjacent entities with a <see cref="EdgeSpreaderComponent"/>.
+    /// This function activates all spreaders that are adjacent to a given entity. This also activates other spreaders
+    /// on the same tile as the current entity (for thin airtight entities like windoors).
     /// </summary>
-    public List<EntityUid> GetSpreadableNeighbors(EntityUid uid, AirtightComponent? comp = null,
-        (EntityUid Grid, Vector2i Tile)? position = null)
+    public void ActivateSpreadableNeighbors(EntityUid uid, (EntityUid Grid, Vector2i Tile)? position = null)
     {
-        Resolve(uid, ref comp, false);
-        var neighbors = new List<EntityUid>();
-
         Vector2i tile;
         EntityUid ent;
         MapGridComponent? grid;
@@ -315,37 +303,40 @@ public sealed class SpreaderSystem : EntitySystem
         {
             var transform = Transform(uid);
             if (!TryComp(transform.GridUid, out grid) || TerminatingOrDeleted(transform.GridUid.Value))
-                return neighbors;
+                return;
+
             tile = _map.TileIndicesFor(transform.GridUid.Value, grid, transform.Coordinates);
             ent = transform.GridUid.Value;
         }
         else
         {
             if (!TryComp(position.Value.Grid, out grid))
-                return neighbors;
-            tile = position.Value.Tile;
-            ent = position.Value.Grid;
+                return;
+            (ent, tile) = position.Value;
         }
 
-        var spreaderQuery = GetEntityQuery<EdgeSpreaderComponent>();
+        var anchored = _map.GetAnchoredEntitiesEnumerator(ent, grid, tile);
+        while (anchored.MoveNext(out var entity))
+        {
+            if (entity == ent)
+                continue;
+            DebugTools.Assert(Transform(entity.Value).Anchored);
+            if (_query.HasComponent(ent) && !TerminatingOrDeleted(entity.Value))
+                EnsureComp<ActiveEdgeSpreaderComponent>(entity.Value);
+        }
 
         for (var i = 0; i < Atmospherics.Directions; i++)
         {
             var direction = (AtmosDirection) (1 << i);
-            if (comp != null && !comp.AirBlockedDirection.IsFlagSet(direction))
-                continue;
+            var adjacentTile = SharedMapSystem.GetDirection(tile, direction.ToDirection());
+            anchored = _map.GetAnchoredEntitiesEnumerator(ent, grid, adjacentTile);
 
-            var directionEnumerator =
-                _map.GetAnchoredEntitiesEnumerator(ent, grid, SharedMapSystem.GetDirection(tile, direction.ToDirection()));
-
-            while (directionEnumerator.MoveNext(out var entity))
+            while (anchored.MoveNext(out var entity))
             {
                 DebugTools.Assert(Transform(entity.Value).Anchored);
-                if (spreaderQuery.HasComponent(entity) && !TerminatingOrDeleted(entity.Value))
-                    neighbors.Add(entity.Value);
+                if (_query.HasComponent(ent) && !TerminatingOrDeleted(entity.Value))
+                    EnsureComp<ActiveEdgeSpreaderComponent>(entity.Value);
             }
         }
-
-        return neighbors;
     }
 }
index c56edd205b72fb75aee6ac1d7575151af15b7b35..6e640b287b691a8ae4d2d8c4fcdcdc97b5189e61 100644 (file)
@@ -8,11 +8,6 @@ namespace Content.Shared.Atmos
     /// </summary>
     public static class Atmospherics
     {
-        static Atmospherics()
-        {
-            AdjustedNumberOfGases = MathHelper.NextMultipleOf(TotalNumberOfGases, 4);
-        }
-
         #region ATMOS
         /// <summary>
         ///     The universal gas constant, in kPa*L/(K*mol)
@@ -183,7 +178,7 @@ namespace Content.Shared.Atmos
         ///     This is the actual length of the gases arrays in mixtures.
         ///     Set to the closest multiple of 4 relative to <see cref="TotalNumberOfGases"/> for SIMD reasons.
         /// </summary>
-        public static readonly int AdjustedNumberOfGases;
+        public const int AdjustedNumberOfGases = ((TotalNumberOfGases + 3) / 4) * 4;
 
         /// <summary>
         ///     Amount of heat released per mole of burnt hydrogen or tritium (hydrogen isotope)
index eb0079eb358e60e03f12ed3559774882ad2e4528..f468724db33396ed1dd601ab9aac89644f3f98ff 100644 (file)
@@ -66,7 +66,10 @@ namespace Content.Shared.Atmos.EntitySystems
         [Serializable, NetSerializable]
         public readonly struct GasOverlayData : IEquatable<GasOverlayData>
         {
+            [ViewVariables]
             public readonly byte FireState;
+
+            [ViewVariables]
             public readonly byte[] Opacity;
 
             // TODO change fire color based on temps
index 65887358024352cb827b945787ac3c3e0c4640c0..839d920df94a629f9860309b7b07ab01458b9a7e 100644 (file)
@@ -77,7 +77,11 @@ namespace Content.Shared.Maps
         [DataField("itemDrop", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
         public string ItemDropPrototypeName { get; private set; } = "FloorTileItemSteel";
 
-        [DataField("isSpace")] public bool IsSpace { get; private set; }
+        // TODO rename data-field in yaml
+        /// <summary>
+        /// Whether or not the tile is exposed to the map's atmosphere.
+        /// </summary>
+        [DataField("isSpace")] public bool MapAtmosphere { get; private set; }
 
         /// <summary>
         ///     Friction override for mob mover in <see cref="SharedMoverController"/>
index 1bbb06162cd84d121beeb31d5b167d122778e7b3..9a0b273c294d31b72f60e62e6e25a2a78b928d6f 100644 (file)
@@ -12,22 +12,6 @@ namespace Content.Shared.Maps
     // That, or make the interface arguments non-optional so people stop failing to pass them in.
     public static class TurfHelpers
     {
-        /// <summary>
-        ///     Attempts to get the turf at map indices with grid id or null if no such turf is found.
-        /// </summary>
-        public static TileRef GetTileRef(this Vector2i vector2i, EntityUid gridId, IEntityManager? entityManager = null)
-        {
-            entityManager ??= IoCManager.Resolve<IEntityManager>();
-
-            if (!entityManager.TryGetComponent<MapGridComponent>(gridId, out var grid))
-                return default;
-
-            if (!grid.TryGetTileRef(vector2i, out var tile))
-                return default;
-
-            return tile;
-        }
-
         /// <summary>
         ///     Attempts to get the turf at a certain coordinates or null if no such turf is found.
         /// </summary>
@@ -68,7 +52,7 @@ namespace Content.Shared.Maps
         /// </summary>
         public static bool IsSpace(this Tile tile, ITileDefinitionManager? tileDefinitionManager = null)
         {
-            return tile.GetContentTileDefinition(tileDefinitionManager).IsSpace;
+            return tile.GetContentTileDefinition(tileDefinitionManager).MapAtmosphere;
         }
 
         /// <summary>
@@ -116,15 +100,6 @@ namespace Content.Shared.Maps
             return GetEntitiesInTile(turf.Value, flags, lookupSystem);
         }
 
-        /// <summary>
-        ///     Helper that returns all entities in a turf.
-        /// </summary>
-        [Obsolete("Use the lookup system")]
-        public static IEnumerable<EntityUid> GetEntitiesInTile(this Vector2i indices, EntityUid gridId, LookupFlags flags = LookupFlags.Static, EntityLookupSystem? lookupSystem = null)
-        {
-            return GetEntitiesInTile(indices.GetTileRef(gridId), flags, lookupSystem);
-        }
-
         /// <summary>
         /// Checks if a turf has something dense on it.
         /// </summary>
diff --git a/Resources/Locale/en-US/atmos/commands.ftl b/Resources/Locale/en-US/atmos/commands.ftl
new file mode 100644 (file)
index 0000000..692908d
--- /dev/null
@@ -0,0 +1,8 @@
+cmd-set-map-atmos-desc = Sets a map's atmosphere
+cmd-set-map-atmos-help = setmapatmos <mapid> <space> [<temperature> [moles...]]
+cmd-set-map-atmos-removed = Atmosphere removed from map {$map}
+cmd-set-map-atmos-updated = Atmosphere set for map {$map}
+cmd-set-map-atmos-hint-map = <mapid>
+cmd-set-map-atmos-hint-space = <space>
+cmd-set-map-atmos-hint-temp = <temperature> (float)
+cmd-set-map-atmos-hint-gas = <{$gas} moles> (float)