diff --git a/Content.Client/Atmos/Overlays/GasTileOverlay.cs b/Content.Client/Atmos/Overlays/GasTileOverlay.cs index ef65d43fe8..f4dc274a4e 100644 --- a/Content.Client/Atmos/Overlays/GasTileOverlay.cs +++ b/Content.Client/Atmos/Overlays/GasTileOverlay.cs @@ -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(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(mapUid, out var atmos)) - { - var bottomLeft = args.WorldAABB.BottomLeft.Floored(); - var topRight = args.WorldAABB.TopRight.Ceiled(); + DrawMapOverlay(drawHandle, args, mapUid, atmos); - 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) - 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(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)); + } + } + } + } } } diff --git a/Content.Client/Mapping/MappingSystem.cs b/Content.Client/Mapping/MappingSystem.cs index 4456be36a6..8daf193dfe 100644 --- a/Content.Client/Mapping/MappingSystem.cs +++ b/Content.Client/Mapping/MappingSystem.cs @@ -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); diff --git a/Content.IntegrationTests/Tests/Body/LungTest.cs b/Content.IntegrationTests/Tests/Body/LungTest.cs index d0325480ac..f2e19849b0 100644 --- a/Content.IntegrationTests/Tests/Body/LungTest.cs +++ b/Content.IntegrationTests/Tests/Body/LungTest.cs @@ -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)); }); } diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs index 84e1afaf45..88448e7b80 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs @@ -1006,15 +1006,10 @@ public abstract partial class InteractionTest await Server.WaitPost(() => { var atmosSystem = SEntMan.System(); - var atmos = SEntMan.EnsureComponent(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 index 0000000000..6f04cfb2da --- /dev/null +++ b/Content.Server/Atmos/Commands/SetMapAtmosCommand.cs @@ -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(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(); + 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()))); + } +} diff --git a/Content.Server/Atmos/Components/AirtightComponent.cs b/Content.Server/Atmos/Components/AirtightComponent.cs index 897981724c..ca107eafbe 100644 --- a/Content.Server/Atmos/Components/AirtightComponent.cs +++ b/Content.Server/Atmos/Components/AirtightComponent.cs @@ -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; } } diff --git a/Content.Server/Atmos/Components/GridAtmosphereComponent.cs b/Content.Server/Atmos/Components/GridAtmosphereComponent.cs index 7fcd63bc5d..e682fd0964 100644 --- a/Content.Server/Atmos/Components/GridAtmosphereComponent.cs +++ b/Content.Server/Atmos/Components/GridAtmosphereComponent.cs @@ -28,6 +28,9 @@ namespace Content.Server.Atmos.Components [IncludeDataField(customTypeSerializer:typeof(TileAtmosCollectionSerializer))] public Dictionary Tiles = new(1000); + [ViewVariables] + public HashSet MapTiles = new(1000); + [ViewVariables] public readonly HashSet ActiveTiles = new(1000); @@ -80,7 +83,10 @@ namespace Content.Server.Atmos.Components public readonly HashSet InvalidatedCoords = new(1000); [ViewVariables] - public readonly Queue CurrentRunInvalidatedCoordinates = new(); + public readonly Queue CurrentRunInvalidatedTiles = new(); + + [ViewVariables] + public readonly List PossiblyDisconnectedTiles = new(100); [ViewVariables] public int InvalidatedCoordsCount => InvalidatedCoords.Count; diff --git a/Content.Server/Atmos/Components/MapAtmosphereComponent.cs b/Content.Server/Atmos/Components/MapAtmosphereComponent.cs index bbf5ea6403..6bdef901d4 100644 --- a/Content.Server/Atmos/Components/MapAtmosphereComponent.cs +++ b/Content.Server/Atmos/Components/MapAtmosphereComponent.cs @@ -12,12 +12,14 @@ public sealed partial class MapAtmosphereComponent : SharedMapAtmosphereComponen /// /// The default GasMixture a map will have. Space mixture by default. /// - [DataField("mixture"), ViewVariables(VVAccess.ReadWrite)] - public GasMixture? Mixture = GasMixture.SpaceGas; + [DataField, ViewVariables(VVAccess.ReadWrite)] + public GasMixture Mixture = GasMixture.SpaceGas; /// /// Whether empty tiles will be considered space or not. /// - [DataField("space"), ViewVariables(VVAccess.ReadWrite)] + [DataField, ViewVariables(VVAccess.ReadWrite)] public bool Space = true; + + public SharedGasTileOverlaySystem.GasOverlayData Overlay; } diff --git a/Content.Server/Atmos/EntitySystems/AirtightSystem.cs b/Content.Server/Atmos/EntitySystems/AirtightSystem.cs index 97dccbaabb..548d6a3692 100644 --- a/Content.Server/Atmos/EntitySystems/AirtightSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AirtightSystem.cs @@ -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 grid, Vector2i pos) { - if (!TryComp(gridId, out MapGridComponent? grid)) - return; - var query = EntityManager.GetEntityQuery(); - _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) diff --git a/Content.Server/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs b/Content.Server/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs index 4af32fce58..c0284f26c9 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs @@ -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 index aed009e9a1..0000000000 --- a/Content.Server/Atmos/EntitySystems/AtmosObstructionEnumerator.cs +++ /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 _query; - - public AtmosObstructionEnumerator(AnchoredEntitiesEnumerator enumerator, EntityQuery 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; - } -} diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs index 310e602336..cece99cacf 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs @@ -85,10 +85,10 @@ public partial class AtmosphereSystem return ev.Mixtures!; } - public void InvalidateTile(EntityUid gridUid, Vector2i tile) + public void InvalidateTile(Entity 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 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(); } - 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? 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 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) - { - /// - /// True if one of the enabled blockers has . Note - /// that this does not actually check if all directions are blocked. - /// - 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? 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); diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs index 1d809dcd03..de4c9199cf 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs @@ -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) + /// + /// This de-activates and removes all tiles in an excited group. + /// + 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(); } + /// + /// This removes an excited group without de-activating its tiles. + /// 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(); } } } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs index 1f1a208b24..d43cc81b0f 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs @@ -14,6 +14,7 @@ public sealed partial class AtmosphereSystem private void InitializeGridAtmosphere() { SubscribeLocalEvent(OnGridAtmosphereInit); + SubscribeLocalEvent(OnGridAtmosphereStartup); SubscribeLocalEvent(OnAtmosphereRemove); SubscribeLocalEvent(OnGridSplit); @@ -22,19 +23,15 @@ public sealed partial class AtmosphereSystem SubscribeLocalEvent(GridHasAtmosphere); SubscribeLocalEvent(GridIsSimulated); SubscribeLocalEvent(GridGetAllMixtures); - SubscribeLocalEvent(GridInvalidateTile); SubscribeLocalEvent(GridGetTileMixture); SubscribeLocalEvent(GridGetTileMixtures); SubscribeLocalEvent(GridReactTile); - SubscribeLocalEvent(GridIsTileAirBlocked); SubscribeLocalEvent(GridIsTileSpace); SubscribeLocalEvent(GridGetAdjacentTiles); SubscribeLocalEvent(GridGetAdjacentTileMixtures); - SubscribeLocalEvent(GridUpdateAdjacent); SubscribeLocalEvent(GridHotspotExpose); SubscribeLocalEvent(GridHotspotExtinguish); SubscribeLocalEvent(GridIsHotspotActive); - SubscribeLocalEvent(GridFixTileVacuum); SubscribeLocalEvent(GridAddPipeNet); SubscribeLocalEvent(GridRemovePipeNet); SubscribeLocalEvent(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(); + EnsureComp(uid); + foreach (var tile in component.Tiles.Values) + { + tile.GridIndex = uid; + } + } + + private void OnGridAtmosphereStartup(EntityUid uid, GridAtmosphereComponent component, ComponentStartup args) + { if (!TryComp(uid, out MapGridComponent? mapGrid)) return; - EnsureComp(uid); - - foreach (var (indices, tile) in gridAtmosphere.Tiles) - { - gridAtmosphere.InvalidatedCoords.Add(indices); - tile.GridIndex = uid; - } - - 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) + /// + /// Update array of adjacent tiles and the adjacency flags. Optionally activates all tiles with modified adjacencies. + /// + private void UpdateAdjacentTiles( + Entity 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) - { - adjacent.AdjacentBits |= oppositeDirection; - adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = tile; - } - else - { - adjacent.AdjacentBits &= ~oppositeDirection; - adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = null; - } - - if (!tile.BlockedAirflow.IsFlagSet(direction) && !adjacentBlockedEv.Result) - { - tile.AdjacentBits |= direction; - tile.AdjacentTiles[direction.ToIndex()] = adjacent; - } - else + else if (!atmos.Tiles.TryGetValue(adjacentIndices, out adjacent)) { tile.AdjacentBits &= ~direction; - tile.AdjacentTiles[direction.ToIndex()] = null; + tile.AdjacentTiles[i] = null; + continue; + } + + 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; + } + else + { + // 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 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(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; - - tile.Air = new GasMixture - { - Volume = GetVolumeForTiles(mapGridComp, 1), - Temperature = Atmospherics.T20C - }; - - tile.MolesArchived = new float[Atmospherics.AdjustedNumberOfGases]; + DebugTools.AssertNotNull(tile.Air); + DebugTools.Assert(tile.Air?.Immutable == false ); + Array.Clear(tile.MolesArchived); tile.ArchivedCycle = 0; - var ratio = 1f / adjacent.Length; + var count = 0; + foreach (var adj in tile.AdjacentTiles) + { + if (adj?.Air != null) + count++; + } + + 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 /// /// Repopulates all tiles on a grid atmosphere. /// - /// The grid where to get all valid tiles from. - /// The grid atmosphere where the tiles will be repopulated. - private void GridRepopulateTiles(Entity grid) + public void InvalidateAllTiles(Entity 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); } } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.HighPressureDelta.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.HighPressureDelta.cs index 4d25292a66..0ec89feaef 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.HighPressureDelta.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.HighPressureDelta.cs @@ -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; diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs index 795c6e0547..c27e18b55b 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs @@ -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 /// Whether to dispose of the tile's 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); } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs index ab59aab7e9..ed105c8d33 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs @@ -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(OnMapStartup); + SubscribeLocalEvent(OnMapRemove); SubscribeLocalEvent(MapIsTileSpace); SubscribeLocalEvent(MapGetTileMixture); SubscribeLocalEvent(MapGetTileMixtures); SubscribeLocalEvent(OnMapGetState); + SubscribeLocalEvent(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, MapAtmosphereComponent? component = null) + public void SetMapAtmosphere(EntityUid uid, bool space, GasMixture mixture) + { + DebugTools.Assert(HasComp(uid)); + var component = EnsureComp(uid); + SetMapGasMixture(uid, mixture, component, false); + SetMapSpace(uid, space, component, false); + RefreshAllGridMapAtmospheres(uid); + } + + public void SetMapGasMixture(EntityUid uid, GasMixture mixture, MapAtmosphereComponent? component = null, bool updateTiles = true) { if (!Resolve(uid, ref component)) return; + 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 SetMapSpace(EntityUid uid, bool space, MapAtmosphereComponent? component = null, bool updateTiles = true) + { + if (!Resolve(uid, ref component)) + return; + + if (component.Space == space) + return; + component.Space = space; - component.Mixture = mixture; - Dirty(uid, component); + + if (updateTiles) + RefreshAllGridMapAtmospheres(uid); } - public void SetMapGasMixture(EntityUid uid, GasMixture? mixture, MapAtmosphereComponent? component = null) + /// + /// Forces a refresh of all MapAtmosphere tiles on every grid on a map. + /// + public void RefreshAllGridMapAtmospheres(EntityUid map) { - if (!Resolve(uid, ref component)) - return; - - component.Mixture = mixture; - Dirty(uid, component); + DebugTools.Assert(HasComp(map)); + var enumerator = AllEntityQuery(); + while (enumerator.MoveNext(out var grid, out var atmos, out var xform)) + { + if (xform.MapUid == map) + RefreshMapAtmosphereTiles((grid, atmos)); + } } - public void SetMapSpace(EntityUid uid, bool space, MapAtmosphereComponent? component = null) + /// + /// Forces a refresh of all MapAtmosphere tiles on a given grid. + /// + private void RefreshMapAtmosphereTiles(Entity grid) { - if (!Resolve(uid, ref component)) + 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(); + } + + /// + /// Handles updating map-atmospheres when grids move across maps. + /// + private void OnGridParentChanged(Entity 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(args.OldParent) + || HasComp(args.Transform.ParentUid)) + { + RefreshMapAtmosphereTiles((grid, grid)); + } } } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs index 737976a829..dcbc1e86ee 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs @@ -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 ent, TileAtmosphere tile, int cycleNum, GasTileOverlayComponent? visuals) + private void EqualizePressureInZone( + Entity 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 ent, TileAtmosphere tile, int cycleNum, GasTileOverlayComponent? visuals) + private void ExplosivelyDepressurize( + Entity 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; - DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite())); - if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle) continue; + if (otherTile2?.Air == null) + continue; - ConsiderFirelocks((owner, gridAtmosphere), otherTile, otherTile2, visuals, mapGrid); + if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle) + continue; + + var direction = (AtmosDirection) (1 << j); + DebugTools.Assert(otherTile.AdjacentBits.IsFlagSet(direction)); + DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite())); + + 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 ent, TileAtmosphere tile, TileAtmosphere other, GasTileOverlayComponent? visuals, MapGridComponent mapGrid) + private void ConsiderFirelocks( + Entity 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; } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs index 4f8df0af67..1f3ca2145b 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs @@ -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,118 +30,63 @@ namespace Content.Server.Atmos.EntitySystems private int _currentRunAtmosphereIndex; private bool _simulationPaused; - private readonly List> _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> _currentRunAtmosphere = new(); /// /// Revalidates all invalid coordinates in a grid atmosphere. + /// I.e., process any tiles that have had their airtight blockers modified. /// /// The grid atmosphere in question. /// Whether the process succeeded or got paused due to time constrains. - private bool ProcessRevalidate(Entity ent, GasTileOverlayComponent? visuals) + private bool ProcessRevalidate(Entity ent) { - var (owner, atmosphere) = ent; - if (!atmosphere.ProcessingPaused) + if (ent.Comp4.MapUid == null) { - atmosphere.CurrentRunInvalidatedCoordinates.Clear(); - atmosphere.CurrentRunInvalidatedCoordinates.EnsureCapacity(atmosphere.InvalidatedCoords.Count); - foreach (var tile in atmosphere.InvalidatedCoords) - { - atmosphere.CurrentRunInvalidatedCoordinates.Enqueue(tile); - } - atmosphere.InvalidatedCoords.Clear(); + Log.Error($"Attempted to process atmosphere on a map-less grid? Grid: {ToPrettyString(ent)}"); + return true; } - if (!TryComp(owner, out MapGridComponent? mapGridComp)) - return true; + var (uid, atmosphere, visuals, grid, xform) = ent; + var volume = GetVolumeForTiles(grid); + TryComp(xform.MapUid, out MapAtmosphereComponent? mapAtmos); - var mapUid = _mapManager.GetMapEntityIdOrThrow(Transform(owner).MapID); + if (!atmosphere.ProcessingPaused) + { + atmosphere.CurrentRunInvalidatedTiles.Clear(); + atmosphere.CurrentRunInvalidatedTiles.EnsureCapacity(atmosphere.InvalidatedCoords.Count); + foreach (var indices in atmosphere.InvalidatedCoords) + { + var tile = GetOrNewTile(uid, atmosphere, indices); + atmosphere.CurrentRunInvalidatedTiles.Enqueue(tile); - var volume = GetVolumeForTiles(mapGridComp); + // Update tile.IsSpace and tile.MapAtmosphere, and tile.AirtightData. + UpdateTileData(ent, mapAtmos, tile); + } + atmosphere.InvalidatedCoords.Clear(); + + if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime) + return false; + } var number = 0; - while (atmosphere.CurrentRunInvalidatedCoordinates.TryDequeue(out var indices)) + while (atmosphere.CurrentRunInvalidatedTiles.TryDequeue(out var tile)) { - if (!atmosphere.Tiles.TryGetValue(indices, out var tile)) - { - tile = new TileAtmosphere(owner, indices, - new GasMixture(volume) { Temperature = Atmospherics.T20C }); - atmosphere.Tiles[indices] = tile; - } - - var airBlockedEv = new IsTileAirBlockedMethodEvent(owner, indices, MapGridComponent:mapGridComp); - GridIsTileAirBlocked(owner, atmosphere, ref airBlockedEv); - var isAirBlocked = airBlockedEv.Result; - - var oldBlocked = tile.BlockedAirflow; - var updateAdjacentEv = new UpdateAdjacentMethodEvent(owner, indices, mapGridComp); - GridUpdateAdjacent(owner, atmosphere, ref updateAdjacentEv); - - // Blocked airflow changed, rebuild excited groups! - if (tile.Excited && tile.BlockedAirflow != oldBlocked) - { - RemoveActiveTile(atmosphere, tile); - } - - // 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) - { - tile.Air = GetTileMixture(null, mapUid, indices); - tile.MolesArchived = tile.Air != null ? new float[Atmospherics.AdjustedNumberOfGases] : null; - tile.Space = IsTileSpace(null, mapUid, indices, mapGridComp); - } - else if (isAirBlocked) - { - if (airBlockedEv.NoAir) - { - tile.Air = null; - tile.MolesArchived = null; - tile.ArchivedCycle = 0; - tile.LastShare = 0f; - tile.Hotspot = new Hotspot(); - } - } - 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; - } - - tile.Air ??= new GasMixture(volume){Temperature = Atmospherics.T20C}; - tile.MolesArchived ??= new float[Atmospherics.AdjustedNumberOfGases]; - } - - // We activate the tile. - AddActiveTile(atmosphere, tile); - - // 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; - - tile.ThermalConductivity = tileDef?.ThermalConductivity ?? 0.5f; - tile.HeatCapacity = tileDef?.HeatCapacity ?? float.PositiveInfinity; - InvalidateVisuals(owner, indices, visuals); - - for (var i = 0; i < Atmospherics.Directions; i++) - { - var direction = (AtmosDirection) (1 << i); - var otherIndices = indices.Offset(direction); - - if (atmosphere.Tiles.TryGetValue(otherIndices, out var otherTile)) - AddActiveTile(atmosphere, otherTile); - } + DebugTools.Assert(atmosphere.Tiles.GetValueOrDefault(tile.GridIndices) == tile); + UpdateAdjacentTiles(ent, tile, activate: true); + UpdateTileAir(ent, tile, volume); + InvalidateVisuals(uid, tile.GridIndices, visuals); if (number++ < InvalidCoordinatesLagCheckIterations) continue; @@ -149,12 +94,185 @@ namespace Content.Server.Atmos.EntitySystems number = 0; // Process the rest next time. if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime) - { return false; + } + + TrimDisconnectedMapTiles(ent); + return true; + } + + /// + /// This method queued a tile and all of its neighbours up for processing by . + /// + public void QueueTileTrim(GridAtmosphereComponent atmos, TileAtmosphere tile) + { + if (!tile.TrimQueued) + { + 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) + { + adj.TrimQueued = true; + atmos.PossiblyDisconnectedTiles.Add(adj); + } + } + } + + /// + /// Tiles in a 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. + /// + private void TrimDisconnectedMapTiles( + Entity ent) + { + var atmos = ent.Comp1; + + foreach (var tile in atmos.PossiblyDisconnectedTiles) + { + tile.TrimQueued = false; + if (!tile.NoGridTile) + continue; + + var connected = false; + for (var i = 0; i < Atmospherics.Directions; i++) + { + var indices = tile.GridIndices.Offset((AtmosDirection) (1 << i)); + if (_map.TryGetTile(ent.Comp3, indices, out var gridTile) && !gridTile.IsEmpty) + { + connected = true; + break; + } + } + + if (!connected) + { + RemoveActiveTile(atmos, tile); + atmos.Tiles.Remove(tile.GridIndices); } } - return true; + atmos.PossiblyDisconnectedTiles.Clear(); + } + + /// + /// 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" + /// + private void UpdateTileData( + Entity 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) + { + 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); + } + } + + UpdateAirtightData(ent.Owner, ent.Comp1, ent.Comp3, tile); + + if (mapAtmosphere) + { + if (!tile.MapAtmosphere) + { + (tile.Air, tile.Space) = GetDefaultMapAtmosphere(mapAtmos); + tile.MapAtmosphere = true; + ent.Comp1.MapTiles.Add(tile); + } + + DebugTools.AssertNotNull(tile.Air); + DebugTools.Assert(tile.Air?.Immutable ?? false); + return; + } + + if (!tile.MapAtmosphere) + return; + + // Tile used to be exposed to the map's atmosphere, but isn't anymore. + RemoveMapAtmos(ent.Comp1, tile); + } + + 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; + } + + /// + /// Check whether a grid-tile should have an air mixture, and give it one if it doesn't already have one. + /// + private void UpdateTileAir( + Entity ent, + TileAtmosphere tile, + float volume) + { + if (tile.MapAtmosphere) + { + DebugTools.AssertNotNull(tile.Air); + DebugTools.Assert(tile.Air?.Immutable ?? false); + return; + } + + var data = tile.AirtightData; + var fullyBlocked = data.BlockedDirections == AtmosDirection.All; + + 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; + } + + 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 ent, GasTileOverlayComponent? visuals) + private bool ProcessTileEqualize(Entity 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(); - while (query.MoveNext(out var uid, out var grid)) + var query = EntityQueryEnumerator(); + 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; diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Superconductivity.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Superconductivity.cs index 33fa16a6c6..5c73cf1124 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Superconductivity.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Superconductivity.cs @@ -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(other.GridIndex, out var grid) + && _mapSystem.TryGetTileRef(other.GridIndex, grid, other.GridIndices, out var _)) { TemperatureShareOpenToSolid(other, tile); } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs index 699c2a70ae..9b0d0d9670 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs @@ -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; - } - /// /// Gets the volume in liters for a number of tiles, on a specific grid. /// @@ -66,34 +52,44 @@ public partial class AtmosphereSystem return Atmospherics.CellVolume * mapGrid.TileSize * tiles; } - /// - /// Gets all obstructing instances in a specific tile. - /// - /// The grid where to get the tile. - /// The indices of the tile. - /// The enumerator for the airtight components. - public AtmosObstructionEnumerator GetObstructingComponentsEnumerator(MapGridComponent mapGrid, Vector2i tile) - { - var ancEnumerator = mapGrid.GetAnchoredEntitiesEnumerator(tile); - var airQuery = GetEntityQuery(); + public readonly record struct AirtightData(AtmosDirection BlockedDirections, bool NoAirWhenBlocked, + bool FixVacuum); - var enumerator = new AtmosObstructionEnumerator(ancEnumerator, airQuery); - return enumerator; + private void UpdateAirtightData(EntityUid uid, GridAtmosphereComponent atmos, MapGridComponent grid, TileAtmosphere tile) + { + var oldBlocked = tile.AirtightData.BlockedDirections; + + tile.AirtightData = tile.NoGridTile + ? default + : GetAirtightData(uid, grid, tile.GridIndices); + + 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); } /// diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs index dd2a967559..d2f40e7716 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs @@ -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 _atmosQuery; + private EntityQuery _airtightQuery; + private EntityQuery _firelockQuery; private HashSet _entSet = new(); public override void Initialize() @@ -55,6 +59,9 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem InitializeGridAtmosphere(); InitializeMap(); + _atmosQuery = GetEntityQuery(); + _airtightQuery = GetEntityQuery(); + _firelockQuery = GetEntityQuery(); SubscribeLocalEvent(OnTileChanged); diff --git a/Content.Server/Atmos/EntitySystems/AutomaticAtmosSystem.cs b/Content.Server/Atmos/EntitySystems/AutomaticAtmosSystem.cs index 203c747e29..70e3eef3c4 100644 --- a/Content.Server/Atmos/EntitySystems/AutomaticAtmosSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AutomaticAtmosSystem.cs @@ -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; diff --git a/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs b/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs index d5563c170c..2eedb1c6a7 100644 --- a/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs +++ b/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs @@ -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)); } } diff --git a/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs b/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs index 94e095892a..6d49feb018 100644 --- a/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs +++ b/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs @@ -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) diff --git a/Content.Server/Atmos/GasMixture.cs b/Content.Server/Atmos/GasMixture.cs index 0a2ef235a7..77fd701833 100644 --- a/Content.Server/Atmos/GasMixture.cs +++ b/Content.Server/Atmos/GasMixture.cs @@ -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; diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs index 64d02d793b..ad647fad1b 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs @@ -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)}]"); diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCondenserSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCondenserSystem.cs index 491bf60062..852542ec6c 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCondenserSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCondenserSystem.cs @@ -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; diff --git a/Content.Server/Atmos/Serialization/TileAtmosCollectionSerializer.cs b/Content.Server/Atmos/Serialization/TileAtmosCollectionSerializer.cs index 7d385c530a..71e4c2d0de 100644 --- a/Content.Server/Atmos/Serialization/TileAtmosCollectionSerializer.cs +++ b/Content.Server/Atmos/Serialization/TileAtmosCollectionSerializer.cs @@ -183,13 +183,7 @@ public sealed partial class TileAtmosCollectionSerializer : ITypeSerializer + /// Neighbouring tiles to which air can flow. This is a combination of this tile's unblocked direction, and the + /// unblocked directions on adjacent tiles. + /// [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; } + /// + /// 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. + /// [ViewVariables] - public AtmosDirection BlockedAirflow { get; set; } = AtmosDirection.Invalid; + public bool MapAtmosphere; + + /// + /// If true, this tile does not actually exist on the grid, it only exists to represent the map's atmosphere for + /// adjacent grid tiles. + /// + [ViewVariables] + public bool NoGridTile; + + /// + /// If true, this tile is queued for processing in + /// + [ViewVariables] + public bool TrimQueued; + + /// + /// Cached information about airtight entities on this tile. This gets updated anytime a tile gets invalidated + /// (i.e., gets added to ). + /// + 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() + { + } } } diff --git a/Content.Server/Body/Systems/LungSystem.cs b/Content.Server/Body/Systems/LungSystem.cs index b5bac50739..4b60f8814b 100644 --- a/Content.Server/Body/Systems/LungSystem.cs +++ b/Content.Server/Body/Systems/LungSystem.cs @@ -77,7 +77,7 @@ public sealed class LungSystem : EntitySystem foreach (var gas in Enum.GetValues()) { var i = (int) gas; - var moles = lung.Air.Moles[i]; + var moles = lung.Air[i]; if (moles <= 0) continue; var reagent = _atmosphereSystem.GasReagents[i]; diff --git a/Content.Server/Chemistry/ReagentEffects/ModifyLungGas.cs b/Content.Server/Chemistry/ReagentEffects/ModifyLungGas.cs index e12cab80a9..e7466fbc85 100644 --- a/Content.Server/Chemistry/ReagentEffects/ModifyLungGas.cs +++ b/Content.Server/Chemistry/ReagentEffects/ModifyLungGas.cs @@ -16,12 +16,15 @@ public sealed partial class ModifyLungGas : ReagentEffect public override void Effect(ReagentEffectArgs args) { - if (args.EntityManager.TryGetComponent(args.OrganEntity, out var lung)) + if (!args.EntityManager.TryGetComponent(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); } } } diff --git a/Content.Server/Construction/Conditions/ComponentInTile.cs b/Content.Server/Construction/Conditions/ComponentInTile.cs index 91393baf8e..429c4fdabf 100644 --- a/Content.Server/Construction/Conditions/ComponentInTile.cs +++ b/Content.Server/Construction/Conditions/ComponentInTile.cs @@ -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(); var indices = transform.Coordinates.ToVector2i(entityManager, IoCManager.Resolve(), transformSys); var lookup = entityManager.EntitySysManager.GetEntitySystem(); - var entities = indices.GetEntitiesInTile(transform.GridUid.Value, LookupFlags.Approximate | LookupFlags.Static, lookup); + + + if (!entityManager.TryGetComponent(transform.GridUid.Value, out var grid)) + return !HasEntity; + + if (!entityManager.System().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) { diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs index ba937d77ad..85d705846e 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs @@ -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; diff --git a/Content.Server/Parallax/BiomeSystem.cs b/Content.Server/Parallax/BiomeSystem.cs index c4c2300870..83cf9b9cb2 100644 --- a/Content.Server/Parallax/BiomeSystem.cs +++ b/Content.Server/Parallax/BiomeSystem.cs @@ -1001,20 +1001,13 @@ public sealed partial class BiomeSystem : SharedBiomeSystem light.AmbientLightColor = Color.FromHex("#D8B059"); Dirty(mapUid, light, metadata); - // Atmos - var atmos = EnsureComp(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); } /// diff --git a/Content.Server/Power/Generator/GasPowerReceiverSystem.cs b/Content.Server/Power/Generator/GasPowerReceiverSystem.cs index c61599edfc..76cf90c369 100644 --- a/Content.Server/Power/Generator/GasPowerReceiverSystem.cs +++ b/Content.Server/Power/Generator/GasPowerReceiverSystem.cs @@ -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); diff --git a/Content.Server/Salvage/SpawnSalvageMissionJob.cs b/Content.Server/Salvage/SpawnSalvageMissionJob.cs index 2776db2283..e2b17b5872 100644 --- a/Content.Server/Salvage/SpawnSalvageMissionJob.cs +++ b/Content.Server/Salvage/SpawnSalvageMissionJob.cs @@ -125,11 +125,7 @@ public sealed class SpawnSalvageMissionJob : Job air.Gases.CopyTo(moles, 0); var atmos = _entManager.EnsureComponent(mapUid); _entManager.System().SetMapSpace(mapUid, air.Space, atmos); - _entManager.System().SetMapGasMixture(mapUid, new GasMixture(2500) - { - Temperature = mission.Temperature, - Moles = moles, - }, atmos); + _entManager.System().SetMapGasMixture(mapUid, new GasMixture(moles, mission.Temperature), atmos); if (mission.Color != null) { diff --git a/Content.Server/Spreader/SpreaderSystem.cs b/Content.Server/Spreader/SpreaderSystem.cs index 8995171823..5b2f3298a2 100644 --- a/Content.Server/Spreader/SpreaderSystem.cs +++ b/Content.Server/Spreader/SpreaderSystem.cs @@ -18,6 +18,7 @@ namespace Content.Server.Spreader; /// 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> _gridUpdates = []; + private EntityQuery _query; + public const float SpreadCooldownSeconds = 1; [ValidatePrototypeId] @@ -47,6 +50,8 @@ public sealed class SpreaderSystem : EntitySystem SubscribeLocalEvent(OnTerminating); SetupPrototypes(); + + _query = GetEntityQuery(); } 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(neighbor); - } + ActivateSpreadableNeighbors(ev.Entity, ev.Position); } private void OnGridInit(GridInitializeEvent ev) @@ -82,13 +81,7 @@ public sealed class SpreaderSystem : EntitySystem private void OnTerminating(Entity entity, ref EntityTerminatingEvent args) { - var neighbors = GetSpreadableNeighbors(entity); - - foreach (var neighbor in neighbors) - { - if (!TerminatingOrDeleted(neighbor)) - EnsureComp(neighbor); - } + ActivateSpreadableNeighbors(entity); } /// @@ -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 } /// - /// Given an entity, this returns a list of all adjacent entities with a . + /// 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). /// - public List 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(); - 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(); + 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(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(entity.Value); } } - - return neighbors; } } diff --git a/Content.Shared/Atmos/Atmospherics.cs b/Content.Shared/Atmos/Atmospherics.cs index c56edd205b..6e640b287b 100644 --- a/Content.Shared/Atmos/Atmospherics.cs +++ b/Content.Shared/Atmos/Atmospherics.cs @@ -8,11 +8,6 @@ namespace Content.Shared.Atmos /// public static class Atmospherics { - static Atmospherics() - { - AdjustedNumberOfGases = MathHelper.NextMultipleOf(TotalNumberOfGases, 4); - } - #region ATMOS /// /// 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 for SIMD reasons. /// - public static readonly int AdjustedNumberOfGases; + public const int AdjustedNumberOfGases = ((TotalNumberOfGases + 3) / 4) * 4; /// /// Amount of heat released per mole of burnt hydrogen or tritium (hydrogen isotope) diff --git a/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs b/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs index eb0079eb35..f468724db3 100644 --- a/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs +++ b/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs @@ -66,7 +66,10 @@ namespace Content.Shared.Atmos.EntitySystems [Serializable, NetSerializable] public readonly struct GasOverlayData : IEquatable { + [ViewVariables] public readonly byte FireState; + + [ViewVariables] public readonly byte[] Opacity; // TODO change fire color based on temps diff --git a/Content.Shared/Maps/ContentTileDefinition.cs b/Content.Shared/Maps/ContentTileDefinition.cs index 6588735802..839d920df9 100644 --- a/Content.Shared/Maps/ContentTileDefinition.cs +++ b/Content.Shared/Maps/ContentTileDefinition.cs @@ -77,7 +77,11 @@ namespace Content.Shared.Maps [DataField("itemDrop", customTypeSerializer:typeof(PrototypeIdSerializer))] public string ItemDropPrototypeName { get; private set; } = "FloorTileItemSteel"; - [DataField("isSpace")] public bool IsSpace { get; private set; } + // TODO rename data-field in yaml + /// + /// Whether or not the tile is exposed to the map's atmosphere. + /// + [DataField("isSpace")] public bool MapAtmosphere { get; private set; } /// /// Friction override for mob mover in diff --git a/Content.Shared/Maps/TurfHelpers.cs b/Content.Shared/Maps/TurfHelpers.cs index 1bbb06162c..9a0b273c29 100644 --- a/Content.Shared/Maps/TurfHelpers.cs +++ b/Content.Shared/Maps/TurfHelpers.cs @@ -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 { - /// - /// Attempts to get the turf at map indices with grid id or null if no such turf is found. - /// - public static TileRef GetTileRef(this Vector2i vector2i, EntityUid gridId, IEntityManager? entityManager = null) - { - entityManager ??= IoCManager.Resolve(); - - if (!entityManager.TryGetComponent(gridId, out var grid)) - return default; - - if (!grid.TryGetTileRef(vector2i, out var tile)) - return default; - - return tile; - } - /// /// Attempts to get the turf at a certain coordinates or null if no such turf is found. /// @@ -68,7 +52,7 @@ namespace Content.Shared.Maps /// public static bool IsSpace(this Tile tile, ITileDefinitionManager? tileDefinitionManager = null) { - return tile.GetContentTileDefinition(tileDefinitionManager).IsSpace; + return tile.GetContentTileDefinition(tileDefinitionManager).MapAtmosphere; } /// @@ -116,15 +100,6 @@ namespace Content.Shared.Maps return GetEntitiesInTile(turf.Value, flags, lookupSystem); } - /// - /// Helper that returns all entities in a turf. - /// - [Obsolete("Use the lookup system")] - public static IEnumerable GetEntitiesInTile(this Vector2i indices, EntityUid gridId, LookupFlags flags = LookupFlags.Static, EntityLookupSystem? lookupSystem = null) - { - return GetEntitiesInTile(indices.GetTileRef(gridId), flags, lookupSystem); - } - /// /// Checks if a turf has something dense on it. /// diff --git a/Resources/Locale/en-US/atmos/commands.ftl b/Resources/Locale/en-US/atmos/commands.ftl new file mode 100644 index 0000000000..692908d42a --- /dev/null +++ b/Resources/Locale/en-US/atmos/commands.ftl @@ -0,0 +1,8 @@ +cmd-set-map-atmos-desc = Sets a map's atmosphere +cmd-set-map-atmos-help = setmapatmos [ [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 = +cmd-set-map-atmos-hint-space = +cmd-set-map-atmos-hint-temp = (float) +cmd-set-map-atmos-hint-gas = <{$gas} moles> (float)