diff --git a/Content.Client/Atmos/Overlays/GasTileOverlay.cs b/Content.Client/Atmos/Overlays/GasTileOverlay.cs index ef65d43fe85..f4dc274a4e5 100644 --- a/Content.Client/Atmos/Overlays/GasTileOverlay.cs +++ b/Content.Client/Atmos/Overlays/GasTileOverlay.cs @@ -8,7 +8,6 @@ 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 @@ public sealed class GasTileOverlay : Overlay 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 @@ public GasTileOverlay(GasTileOverlaySystem system, IEntityManager entManager, IR 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 @@ protected override void FrameUpdate(FrameEventArgs args) 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 @@ protected override void FrameUpdate(FrameEventArgs args) 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 @@ protected override void Draw(in OverlayDrawArgs args) 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(); - - for (var x = bottomLeft.X; x <= topRight.X; x++) - { - for (var y = bottomLeft.Y; y <= topRight.Y; y++) - { - var tilePosition = new Vector2(x, y); - - for (var i = 0; i < atmos.OverlayData.Opacity.Length; i++) - { - var opacity = atmos.OverlayData.Opacity[i]; + DrawMapOverlay(drawHandle, args, mapUid, atmos); - if (opacity > 0) - args.WorldHandle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity)); - } - } - } - } + if (args.Space != OverlaySpace.WorldSpaceEntities) + return; // TODO: WorldBounds callback. _mapManager.FindGridsIntersecting(args.MapId, args.WorldAABB, ref gridState, @@ -265,5 +251,41 @@ protected override void Draw(in OverlayDrawArgs args) 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 4456be36a65..8daf193dfeb 100644 --- a/Content.Client/Mapping/MappingSystem.cs +++ b/Content.Client/Mapping/MappingSystem.cs @@ -83,7 +83,7 @@ private void OnFillActionSlot(FillActionSlotEvent ev) 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 d0325480acd..f2e19849b00 100644 --- a/Content.IntegrationTests/Tests/Body/LungTest.cs +++ b/Content.IntegrationTests/Tests/Body/LungTest.cs @@ -128,7 +128,7 @@ await server.WaitAssertion(() => 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 84e1afaf45c..88448e7b800 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs @@ -1006,15 +1006,10 @@ protected async Task AddAtmosphere(EntityUid? uid = null) 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 00000000000..6f04cfb2da6 --- /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 897981724c9..ca107eafbe8 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 @@ public sealed partial class AirtightComponent : Component [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 7fcd63bc5d8..e682fd09644 100644 --- a/Content.Server/Atmos/Components/GridAtmosphereComponent.cs +++ b/Content.Server/Atmos/Components/GridAtmosphereComponent.cs @@ -28,6 +28,9 @@ public sealed partial class GridAtmosphereComponent : Component [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 @@ public sealed partial class GridAtmosphereComponent : Component 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 bbf5ea6403e..6bdef901d44 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 97dccbaabb7..548d6a36926 100644 --- a/Content.Server/Atmos/EntitySystems/AirtightSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AirtightSystem.cs @@ -2,7 +2,6 @@ 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 @@ public void UpdatePosition(Entity ent, TransformComponent? xf 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 4af32fce58f..c0284f26c90 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs @@ -97,22 +97,19 @@ private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) } } - 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 aed009e9a12..00000000000 --- 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 310e602336a..cece99cacf6 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs @@ -85,10 +85,10 @@ public IEnumerable GetAllMixtures(EntityUid gridUid, bool excite = f 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 ReactionResult ReactTile(EntityUid gridId, Vector2i tile) 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 IEnumerable GetAdjacentTileMixtures(EntityUid gridUid, Vector 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 bool IsHotspotActive(EntityUid gridUid, Vector2i tile) 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 @@ [ByRefEvent] private record struct IsSimulatedGridMethodEvent [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 @@ [ByRefEvent] private record struct GetTileMixtureMethodEvent [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 @@ [ByRefEvent] private record struct GetAdjacentTileMixturesMethodEvent (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 @@ [ByRefEvent] private record struct HotspotExtinguishMethodEvent [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 1d809dcd032..de4c9199cf7 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs @@ -72,7 +72,8 @@ private void ExcitedGroupSelfBreakdown(GridAtmosphereComponent gridAtmosphere, E var tileSize = excitedGroup.Tiles.Count; - if (excitedGroup.Disposed) return; + if (excitedGroup.Disposed) + return; if (tileSize == 0) { @@ -98,7 +99,9 @@ private void ExcitedGroupSelfBreakdown(GridAtmosphereComponent gridAtmosphere, E 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 @@ private void ExcitedGroupSelfBreakdown(GridAtmosphereComponent gridAtmosphere, E 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 @@ private void ExcitedGroupDispose(GridAtmosphereComponent gridAtmosphere, Excited 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 1f1a208b24b..d43cc81b0f8 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 @@ private void InitializeGridAtmosphere() 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 @@ private void OnAtmosphereRemove(EntityUid uid, GridAtmosphereComponent component } } - private void OnGridAtmosphereInit(EntityUid uid, GridAtmosphereComponent gridAtmosphere, ComponentInit args) + private void OnGridAtmosphereInit(EntityUid uid, GridAtmosphereComponent component, ComponentInit args) { base.Initialize(); - if (!TryComp(uid, out MapGridComponent? mapGrid)) - return; - EnsureComp(uid); - - foreach (var (indices, tile) in gridAtmosphere.Tiles) + foreach (var tile in component.Tiles.Values) { - gridAtmosphere.InvalidatedCoords.Add(indices); tile.GridIndex = uid; } + } + + private void OnGridAtmosphereStartup(EntityUid uid, GridAtmosphereComponent component, ComponentStartup args) + { + if (!TryComp(uid, out MapGridComponent? mapGrid)) + return; - GridRepopulateTiles((uid, mapGrid, gridAtmosphere)); + InvalidateAllTiles((uid, mapGrid, component)); } private void OnGridSplit(EntityUid uid, GridAtmosphereComponent originalGridAtmos, ref GridSplitEvent args) @@ -104,8 +102,7 @@ private void OnGridSplit(EntityUid uid, GridAtmosphereComponent originalGridAtmo 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 @@ IEnumerable EnumerateMixtures(EntityUid gridUid, GridAtmosphereCompo 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 @@ private void GridReactTile(EntityUid uid, GridAtmosphereComponent component, ref 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 @@ IEnumerable EnumerateAdjacent(GridAtmosphereComponent grid, TileAtmo 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) + else if (!atmos.Tiles.TryGetValue(adjacentIndices, out adjacent)) { - adjacent.AdjacentBits |= oppositeDirection; - adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = tile; + tile.AdjacentBits &= ~direction; + tile.AdjacentTiles[i] = null; + continue; } - else + + var adjBlockDirs = adjacent.AirtightData.BlockedDirections; + if (activate) + AddActiveTile(atmos, adjacent); + + var oppositeDirection = direction.GetOpposite(); + if (adjBlockDirs.IsFlagSet(oppositeDirection) || blockedDirs.IsFlagSet(direction)) { + // Adjacency is blocked by some airtight entity. + tile.AdjacentBits &= ~direction; adjacent.AdjacentBits &= ~oppositeDirection; + tile.AdjacentTiles[i] = null; adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = null; } - - if (!tile.BlockedAirflow.IsFlagSet(direction) && !adjacentBlockedEv.Result) - { - tile.AdjacentBits |= direction; - tile.AdjacentTiles[direction.ToIndex()] = adjacent; - } else { - tile.AdjacentBits &= ~direction; - tile.AdjacentTiles[direction.ToIndex()] = null; + // No airtight entity in the way. + tile.AdjacentBits |= direction; + adjacent.AdjacentBits |= oppositeDirection; + tile.AdjacentTiles[i] = adjacent; + adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = tile; } DebugTools.Assert(!(tile.AdjacentBits.IsFlagSet(direction) ^ @@ -409,6 +347,16 @@ private void GridUpdateAdjacent(EntityUid uid, GridAtmosphereComponent component 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 @@ private void GridIsHotspotActive(EntityUid uid, GridAtmosphereComponent componen 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; + DebugTools.AssertNotNull(tile.Air); + DebugTools.Assert(tile.Air?.Immutable == false ); + Array.Clear(tile.MolesArchived); + tile.ArchivedCycle = 0; - tile.Air = new GasMixture + var count = 0; + foreach (var adj in tile.AdjacentTiles) { - Volume = GetVolumeForTiles(mapGridComp, 1), - Temperature = Atmospherics.T20C - }; - - tile.MolesArchived = new float[Atmospherics.AdjustedNumberOfGases]; - tile.ArchivedCycle = 0; + if (adj?.Air != null) + count++; + } - var ratio = 1f / adjacent.Length; + var ratio = 1f / count; var totalTemperature = 0f; - foreach (var adj in adjacent) + foreach (var adj in tile.AdjacentTiles) { + if (adj?.Air == null) + continue; + totalTemperature += adj.Temperature; + // TODO ATMOS. Why is this removing and then re-adding air to the neighbouring tiles? + // Is it some rounding issue to do with Atmospherics.GasMinMoles? because otherwise this is just unnecessary. + // if we get rid of this, then this could also just add moles and then multiply by ratio at the end, rather + // than having to iterate over adjacent tiles twice. + // Remove a bit of gas from the adjacent ratio... - var mix = adj.RemoveRatio(ratio); + var mix = adj.Air.RemoveRatio(ratio); // And merge it to the new tile air. Merge(tile.Air, mix); // Return removed gas to its original mixture. - Merge(adj, mix); + Merge(adj.Air, mix); } // New temperature is the arithmetic mean of the sum of the adjacent temperatures... - tile.Air.Temperature = totalTemperature / adjacent.Length; + tile.Air.Temperature = totalTemperature / count; } private void GridAddPipeNet(EntityUid uid, GridAtmosphereComponent component, ref AddPipeNetMethodEvent args) @@ -547,30 +491,21 @@ private void GridRemoveAtmosDevice(EntityUid uid, GridAtmosphereComponent compon /// /// 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 53035e1ed3c..b0c823426d6 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 795c6e0547e..c27e18b55b0 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 @@ private void ProcessCell(GridAtmosphereComponent gridAtmosphere, TileAtmosphere 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 @@ private void AddActiveTile(GridAtmosphereComponent gridAtmosphere, TileAtmospher /// 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 @@ public float GetHeatCapacityArchived(TileAtmosphere tile) 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 916191cb050..ed105c8d33f 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 @@ private void MapGetTileMixture(EntityUid uid, MapAtmosphereComponent component, if (args.Handled) return; - // Clone the mixture, if possible. - args.Mixture = component.Mixture?.Clone(); + args.Mixture = component.Mixture; args.Handled = true; } private void MapGetTileMixtures(EntityUid uid, MapAtmosphereComponent component, ref GetTileMixturesMethodEvent args) { - if (args.Handled || component.Mixture == null) + if (args.Handled) return; args.Handled = true; args.Mixtures ??= new GasMixture?[args.Tiles.Count]; for (var i = 0; i < args.Tiles.Count; i++) { - args.Mixtures[i] ??= component.Mixture.Clone(); + args.Mixtures[i] ??= component.Mixture; } } private void OnMapGetState(EntityUid uid, MapAtmosphereComponent component, ref ComponentGetState args) { - args.State = new MapAtmosphereComponentState(_gasTileOverlaySystem.GetOverlayData(component.Mixture)); + args.State = new MapAtmosphereComponentState(component.Overlay); + } + + public void SetMapAtmosphere(EntityUid uid, bool space, GasMixture mixture) + { + DebugTools.Assert(HasComp(uid)); + var component = EnsureComp(uid); + SetMapGasMixture(uid, mixture, component, false); + SetMapSpace(uid, space, component, false); + RefreshAllGridMapAtmospheres(uid); } - public void SetMapAtmosphere(EntityUid uid, bool space, GasMixture mixture, MapAtmosphereComponent? component = null) + public void SetMapGasMixture(EntityUid uid, GasMixture mixture, MapAtmosphereComponent? component = null, bool updateTiles = true) { if (!Resolve(uid, ref component)) return; - component.Space = space; + if (!mixture.Immutable) + { + mixture = mixture.Clone(); + mixture.MarkImmutable(); + } + component.Mixture = mixture; - Dirty(component); + component.Overlay = _gasTileOverlaySystem.GetOverlayData(component.Mixture); + Dirty(uid, component); + if (updateTiles) + RefreshAllGridMapAtmospheres(uid); } - public void SetMapGasMixture(EntityUid uid, GasMixture? mixture, MapAtmosphereComponent? component = null) + public void SetMapSpace(EntityUid uid, bool space, MapAtmosphereComponent? component = null, bool updateTiles = true) { if (!Resolve(uid, ref component)) return; - component.Mixture = mixture; - Dirty(component); + if (component.Space == space) + return; + + component.Space = space; + + if (updateTiles) + RefreshAllGridMapAtmospheres(uid); } - public void SetMapSpace(EntityUid uid, bool space, 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)) + 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)); + } + } + + /// + /// Forces a refresh of all MapAtmosphere tiles on a given grid. + /// + private void RefreshMapAtmosphereTiles(Entity grid) + { + if (!Resolve(grid.Owner, ref grid.Comp)) return; - component.Space = space; - Dirty(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 aceda3cd332..f156125b0ff 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs @@ -5,7 +5,6 @@ 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 @@ public sealed partial class AtmosphereSystem 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 @@ private void EqualizePressureInZone(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 @@ private void ExplosivelyDepressurize(Entity= limit) break; + if (tileCount >= limit) + break; } } else @@ -437,13 +458,21 @@ private void ExplosivelyDepressurize(Entity 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 @@ private void ExplosivelyDepressurize(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 @@ private void AdjustEqMovement(TileAtmosphere tile, AtmosDirection direction, flo if (adj == null) { var nonNull = tile.AdjacentTiles.Where(x => x != null).Count(); - Logger.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 4f8df0af670..1f3ca2145b9 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.Components; using Robust.Shared.Physics.Components; using Robust.Shared.Timing; +using Robust.Shared.Utility; namespace Content.Server.Atmos.EntitySystems { @@ -30,131 +30,249 @@ public sealed partial class AtmosphereSystem 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 (ent.Comp4.MapUid == null) + { + Log.Error($"Attempted to process atmosphere on a map-less grid? Grid: {ToPrettyString(ent)}"); + return true; + } + + var (uid, atmosphere, visuals, grid, xform) = ent; + var volume = GetVolumeForTiles(grid); + TryComp(xform.MapUid, out MapAtmosphereComponent? mapAtmos); + if (!atmosphere.ProcessingPaused) { - atmosphere.CurrentRunInvalidatedCoordinates.Clear(); - atmosphere.CurrentRunInvalidatedCoordinates.EnsureCapacity(atmosphere.InvalidatedCoords.Count); - foreach (var tile in atmosphere.InvalidatedCoords) + atmosphere.CurrentRunInvalidatedTiles.Clear(); + atmosphere.CurrentRunInvalidatedTiles.EnsureCapacity(atmosphere.InvalidatedCoords.Count); + foreach (var indices in atmosphere.InvalidatedCoords) { - atmosphere.CurrentRunInvalidatedCoordinates.Enqueue(tile); + var tile = GetOrNewTile(uid, atmosphere, indices); + atmosphere.CurrentRunInvalidatedTiles.Enqueue(tile); + + // Update tile.IsSpace and tile.MapAtmosphere, and tile.AirtightData. + UpdateTileData(ent, mapAtmos, tile); } atmosphere.InvalidatedCoords.Clear(); + + if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime) + return false; } - if (!TryComp(owner, out MapGridComponent? mapGridComp)) - return true; + var number = 0; + while (atmosphere.CurrentRunInvalidatedTiles.TryDequeue(out var tile)) + { + DebugTools.Assert(atmosphere.Tiles.GetValueOrDefault(tile.GridIndices) == tile); + UpdateAdjacentTiles(ent, tile, activate: true); + UpdateTileAir(ent, tile, volume); + InvalidateVisuals(uid, tile.GridIndices, visuals); - var mapUid = _mapManager.GetMapEntityIdOrThrow(Transform(owner).MapID); + if (number++ < InvalidCoordinatesLagCheckIterations) + continue; - var volume = GetVolumeForTiles(mapGridComp); + number = 0; + // Process the rest next time. + if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime) + return false; + } - var number = 0; - while (atmosphere.CurrentRunInvalidatedCoordinates.TryDequeue(out var indices)) + TrimDisconnectedMapTiles(ent); + return true; + } + + /// + /// This method queued a tile and all of its neighbours up for processing by . + /// + public void QueueTileTrim(GridAtmosphereComponent atmos, TileAtmosphere tile) + { + if (!tile.TrimQueued) { - if (!atmosphere.Tiles.TryGetValue(indices, out var tile)) + tile.TrimQueued = true; + atmos.PossiblyDisconnectedTiles.Add(tile); + } + + for (var i = 0; i < Atmospherics.Directions; i++) + { + var direction = (AtmosDirection) (1 << i); + var indices = tile.GridIndices.Offset(direction); + if (atmos.Tiles.TryGetValue(indices, out var adj) + && adj.NoGridTile + && !adj.TrimQueued) { - tile = new TileAtmosphere(owner, indices, - new GasMixture(volume) { Temperature = Atmospherics.T20C }); - atmosphere.Tiles[indices] = tile; + adj.TrimQueued = true; + atmos.PossiblyDisconnectedTiles.Add(adj); } + } + } - var airBlockedEv = new IsTileAirBlockedMethodEvent(owner, indices, MapGridComponent:mapGridComp); - GridIsTileAirBlocked(owner, atmosphere, ref airBlockedEv); - var isAirBlocked = airBlockedEv.Result; + /// + /// 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; - var oldBlocked = tile.BlockedAirflow; - var updateAdjacentEv = new UpdateAdjacentMethodEvent(owner, indices, mapGridComp); - GridUpdateAdjacent(owner, atmosphere, ref updateAdjacentEv); + foreach (var tile in atmos.PossiblyDisconnectedTiles) + { + tile.TrimQueued = false; + if (!tile.NoGridTile) + continue; - // Blocked airflow changed, rebuild excited groups! - if (tile.Excited && tile.BlockedAirflow != oldBlocked) + var connected = false; + for (var i = 0; i < Atmospherics.Directions; i++) { - RemoveActiveTile(atmosphere, tile); + var indices = tile.GridIndices.Offset((AtmosDirection) (1 << i)); + if (_map.TryGetTile(ent.Comp3, indices, out var gridTile) && !gridTile.IsEmpty) + { + connected = true; + break; + } } - // Call this instead of the grid method as the map has a say on whether the tile is space or not. - if ((!mapGridComp.TryGetTileRef(indices, out var t) || t.IsSpace(_tileDefinitionManager)) && !isAirBlocked) + if (!connected) { - tile.Air = GetTileMixture(null, mapUid, indices); - tile.MolesArchived = tile.Air != null ? new float[Atmospherics.AdjustedNumberOfGases] : null; - tile.Space = IsTileSpace(null, mapUid, indices, mapGridComp); + RemoveActiveTile(atmos, tile); + atmos.Tiles.Remove(tile.GridIndices); } - else if (isAirBlocked) + } + + atmos.PossiblyDisconnectedTiles.Clear(); + } + + /// + /// 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) { - if (airBlockedEv.NoAir) - { - tile.Air = null; - tile.MolesArchived = null; - tile.ArchivedCycle = 0; - tile.LastShare = 0f; - tile.Hotspot = new Hotspot(); - } + tile.NoGridTile = true; + + // This tile just became a non-grid atmos tile. + // It, or one of its neighbours, might now be completely disconnected from the grid. + QueueTileTrim(ent.Comp1, tile); } - else - { - if (tile.Air == null && NeedsVacuumFixing(mapGridComp, indices)) - { - var vacuumEv = new FixTileVacuumMethodEvent(owner, indices); - GridFixTileVacuum(owner, atmosphere, ref vacuumEv); - } + } - // Tile used to be space, but isn't anymore. - if (tile.Space || (tile.Air?.Immutable ?? false)) - { - tile.Air = null; - tile.MolesArchived = null; - tile.ArchivedCycle = 0; - tile.LastShare = 0f; - tile.Space = false; - } + UpdateAirtightData(ent.Owner, ent.Comp1, ent.Comp3, tile); - tile.Air ??= new GasMixture(volume){Temperature = Atmospherics.T20C}; - tile.MolesArchived ??= new float[Atmospherics.AdjustedNumberOfGases]; + if (mapAtmosphere) + { + if (!tile.MapAtmosphere) + { + (tile.Air, tile.Space) = GetDefaultMapAtmosphere(mapAtmos); + tile.MapAtmosphere = true; + ent.Comp1.MapTiles.Add(tile); } - // We activate the tile. - AddActiveTile(atmosphere, tile); + DebugTools.AssertNotNull(tile.Air); + DebugTools.Assert(tile.Air?.Immutable ?? false); + return; + } - // TODO ATMOS: Query all the contents of this tile (like walls) and calculate the correct thermal conductivity and heat capacity - var tileDef = mapGridComp.TryGetTileRef(indices, out var tileRef) - ? tileRef.GetContentTileDefinition(_tileDefinitionManager) - : null; + if (!tile.MapAtmosphere) + return; - tile.ThermalConductivity = tileDef?.ThermalConductivity ?? 0.5f; - tile.HeatCapacity = tileDef?.HeatCapacity ?? float.PositiveInfinity; - InvalidateVisuals(owner, indices, visuals); + // Tile used to be exposed to the map's atmosphere, but isn't anymore. + RemoveMapAtmos(ent.Comp1, tile); + } - for (var i = 0; i < Atmospherics.Directions; i++) - { - var direction = (AtmosDirection) (1 << i); - var otherIndices = indices.Offset(direction); + private void RemoveMapAtmos(GridAtmosphereComponent atmos, TileAtmosphere tile) + { + DebugTools.Assert(tile.MapAtmosphere); + DebugTools.AssertNotNull(tile.Air); + DebugTools.Assert(tile.Air?.Immutable ?? false); + tile.MapAtmosphere = false; + atmos.MapTiles.Remove(tile); + tile.Air = null; + Array.Clear(tile.MolesArchived); + tile.ArchivedCycle = 0; + tile.LastShare = 0f; + tile.Space = false; + } - if (atmosphere.Tiles.TryGetValue(otherIndices, out var otherTile)) - AddActiveTile(atmosphere, otherTile); - } + /// + /// 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; + } - if (number++ < InvalidCoordinatesLagCheckIterations) - continue; + var data = tile.AirtightData; + var fullyBlocked = data.BlockedDirections == AtmosDirection.All; - number = 0; - // Process the rest next time. - if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime) - { - return false; - } + if (fullyBlocked && data.NoAirWhenBlocked) + { + if (tile.Air == null) + return; + + tile.Air = null; + Array.Clear(tile.MolesArchived); + tile.ArchivedCycle = 0; + tile.LastShare = 0f; + tile.Hotspot = new Hotspot(); + return; } - return true; + if (tile.Air != null) + return; + + tile.Air = new GasMixture(volume){Temperature = Atmospherics.T20C}; + + if (data.FixVacuum) + GridFixTileVacuum(ent, tile, volume); } private void QueueRunTiles( @@ -170,19 +288,16 @@ private void QueueRunTiles( } } - 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 @@ private bool ProcessTileEqualize(Entity ent, GasTileOve 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 @@ private bool ProcessExcitedGroups(GridAtmosphereComponent gridAtmosphere) 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 @@ private void UpdateProcessing(float frameTime) _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 @@ private void UpdateProcessing(float frameTime) 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 @@ private void UpdateProcessing(float frameTime) 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 @@ private void UpdateProcessing(float frameTime) : AtmosphereProcessingState.ActiveTiles; continue; case AtmosphereProcessingState.TileEqualize: - if (!ProcessTileEqualize(ent, visuals)) + if (!ProcessTileEqualize(ent)) { atmosphere.ProcessingPaused = true; return; @@ -499,7 +614,7 @@ private void UpdateProcessing(float frameTime) atmosphere.State = AtmosphereProcessingState.ActiveTiles; continue; case AtmosphereProcessingState.ActiveTiles: - if (!ProcessActiveTiles(atmosphere, visuals)) + if (!ProcessActiveTiles(ent, ent)) { atmosphere.ProcessingPaused = true; return; @@ -520,7 +635,7 @@ private void UpdateProcessing(float frameTime) 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 33fa16a6c68..5c73cf11246 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 @@ private void Superconduct(GridAtmosphereComponent gridAtmosphere, TileAtmosphere 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 @@ public void NeighborConductWithSource(GridAtmosphereComponent gridAtmosphere, Ti { 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 699c2a70aef..9b0d0d9670d 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs @@ -41,20 +41,6 @@ public void InvalidateVisuals(EntityUid gridUid, Vector2i tile, GasTileOverlayCo _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 @@ private float GetVolumeForTiles(MapGridComponent mapGrid, int tiles = 1) 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) + public readonly record struct AirtightData(AtmosDirection BlockedDirections, bool NoAirWhenBlocked, + bool FixVacuum); + + private void UpdateAirtightData(EntityUid uid, GridAtmosphereComponent atmos, MapGridComponent grid, TileAtmosphere tile) { - var ancEnumerator = mapGrid.GetAnchoredEntitiesEnumerator(tile); - var airQuery = GetEntityQuery(); + var oldBlocked = tile.AirtightData.BlockedDirections; + + tile.AirtightData = tile.NoGridTile + ? default + : GetAirtightData(uid, grid, tile.GridIndices); - var enumerator = new AtmosObstructionEnumerator(ancEnumerator, airQuery); - return enumerator; + if (tile.AirtightData.BlockedDirections != oldBlocked && tile.ExcitedGroup != null) + ExcitedGroupDispose(atmos, tile.ExcitedGroup); } - private AtmosDirection GetBlockedDirections(MapGridComponent mapGrid, Vector2i indices) + private AirtightData GetAirtightData(EntityUid uid, MapGridComponent grid, Vector2i tile) { - var value = AtmosDirection.Invalid; + var blockedDirs = AtmosDirection.Invalid; + var noAirWhenBlocked = false; + var fixVacuum = false; - var enumerator = GetObstructingComponentsEnumerator(mapGrid, indices); - - while (enumerator.MoveNext(out var airtight)) + foreach (var ent in _map.GetAnchoredEntities(uid, grid, tile)) { - if(airtight.AirBlocked) - value |= airtight.AirBlockedDirection; + if (!_airtightQuery.TryGetComponent(ent, out var airtight)) + continue; + + if(!airtight.AirBlocked) + continue; + + blockedDirs |= airtight.AirBlockedDirection; + noAirWhenBlocked |= airtight.NoAirWhenFullyAirBlocked; + fixVacuum |= airtight.FixVacuum; + + if (blockedDirs == AtmosDirection.All && noAirWhenBlocked && fixVacuum) + break; } - return value; + return new AirtightData(blockedDirs, noAirWhenBlocked, fixVacuum); } /// diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs index dd2a9675591..d2f40e77169 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs @@ -4,6 +4,7 @@ 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 override void Initialize() 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 5d60ae57d9b..abab660fd24 100644 --- a/Content.Server/Atmos/EntitySystems/AutomaticAtmosSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AutomaticAtmosSystem.cs @@ -27,8 +27,12 @@ private void OnTileChanged(ref TileChangedEvent ev) // 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 6a2c8f0a7e5..552ee142329 100644 --- a/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs +++ b/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs @@ -260,13 +260,13 @@ private GasEntry[] GenerateGasEntryArray(GasMixture? mixture) { 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 8ae9517379e..397f0167a9b 100644 --- a/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs +++ b/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs @@ -171,7 +171,7 @@ public GasOverlayData GetOverlayData(GasMixture? mixture) { 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) @@ -216,13 +216,13 @@ private bool UpdateChunkTile(GridAtmosphereComponent gridAtmosphere, GasOverlayC 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 0a2ef235a71..77fd7018333 100644 --- a/Content.Server/Atmos/GasMixture.cs +++ b/Content.Server/Atmos/GasMixture.cs @@ -3,6 +3,7 @@ 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 @@ public sealed partial class GasMixture : IEquatable, ISerializationH { 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 @@ public GasMixture(float volume = 0f) 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 @@ public void SetMoles(Gas gas, float quantity) [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 @@ public GasMixture RemoveRatio(float ratio) { 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 @@ public void Multiply(float multiplier) 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 @@ public override bool Equals(object? obj) 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 @@ public override int GetHashCode() 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 64d02d793bc..ad647fad1b8 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs @@ -136,7 +136,7 @@ private void OnCanisterChangeReleaseValve(EntityUid uid, GasCanisterComponent ca 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 491bf600627..852542ec6cd 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCondenserSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCondenserSystem.cs @@ -45,7 +45,7 @@ private void OnCondenserUpdated(Entity entity, ref AtmosD 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 7d385c530a9..71e4c2d0def 100644 --- a/Content.Server/Atmos/Serialization/TileAtmosCollectionSerializer.cs +++ b/Content.Server/Atmos/Serialization/TileAtmosCollectionSerializer.cs @@ -183,13 +183,7 @@ public void CopyTo(ISerializationManager serializationManager, Dictionary + /// 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 @@ public sealed class TileAtmosphere : IGasMixtureHolder 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 @@ public sealed class TileAtmosphere : IGasMixtureHolder public float LastShare; [ViewVariables] - public float[]? MolesArchived; + public readonly float[] MolesArchived = new float[Atmospherics.AdjustedNumberOfGases]; GasMixture IGasMixtureHolder.Air { @@ -103,8 +105,31 @@ GasMixture IGasMixtureHolder.Air [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 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 AtmosDirection BlockedAirflow { get; set; } = AtmosDirection.Invalid; + 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 @@ public TileAtmosphere(EntityUid gridIndex, Vector2i gridIndices, GasMixture? mix 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 b5bac507391..4b60f8814b5 100644 --- a/Content.Server/Body/Systems/LungSystem.cs +++ b/Content.Server/Body/Systems/LungSystem.cs @@ -77,7 +77,7 @@ public void GasToReagent(EntityUid uid, LungComponent lung) 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 e12cab80a91..e7466fbc85d 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 8ab4046a721..36705e4c0ee 100644 --- a/Content.Server/Construction/Conditions/ComponentInTile.cs +++ b/Content.Server/Construction/Conditions/ComponentInTile.cs @@ -3,6 +3,7 @@ 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 @@ -48,7 +49,15 @@ public bool Condition(EntityUid uid, IEntityManager entityManager) var indices = transform.Coordinates.ToVector2i(entityManager, IoCManager.Resolve()); 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 2bfc753b74a..af1fbedb829 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs @@ -475,7 +475,7 @@ public void DamageFloorTile(TileRef tileRef, 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; @@ -491,7 +491,7 @@ public void DamageFloorTile(TileRef tileRef, 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 c4c2300870d..83cf9b9cb2d 100644 --- a/Content.Server/Parallax/BiomeSystem.cs +++ b/Content.Server/Parallax/BiomeSystem.cs @@ -1001,20 +1001,13 @@ public void EnsurePlanet(EntityUid mapUid, BiomeTemplatePrototype biomeTemplate, 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 c61599edfc9..76cf90c3693 100644 --- a/Content.Server/Power/Generator/GasPowerReceiverSystem.cs +++ b/Content.Server/Power/Generator/GasPowerReceiverSystem.cs @@ -37,7 +37,7 @@ private void OnDeviceUpdated(EntityUid uid, GasPowerReceiverComponent component, 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 2776db2283a..e2b17b58724 100644 --- a/Content.Server/Salvage/SpawnSalvageMissionJob.cs +++ b/Content.Server/Salvage/SpawnSalvageMissionJob.cs @@ -125,11 +125,7 @@ protected override async Task Process() 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 269d35d12ff..9cbb295c004 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 override void Initialize() SubscribeLocalEvent(OnTerminating); SetupPrototypes(); + + _query = GetEntityQuery(); } private void OnPrototypeReload(PrototypesReloadedEventArgs obj) @@ -66,13 +71,7 @@ private void SetupPrototypes() 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 @@ private void OnGridInit(GridInitializeEvent ev) 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 void GetNeighbors(EntityUid uid, TransformComponent comp, ProtoId - /// 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 List GetSpreadableNeighbors(EntityUid uid, AirtightComponent? { 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 7460e08e46f..6a8587ca239 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 @@ static Atmospherics() /// 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 eb0079eb358..f468724db33 100644 --- a/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs +++ b/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs @@ -66,7 +66,10 @@ public static Vector2i GetGasChunkIndices(Vector2i indices) [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 ce7980509eb..32f5db0e821 100644 --- a/Content.Shared/Maps/ContentTileDefinition.cs +++ b/Content.Shared/Maps/ContentTileDefinition.cs @@ -81,7 +81,11 @@ public sealed partial class ContentTileDefinition : IPrototype, IInheritingProto [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 a87b8c97d15..58a5d133b55 100644 --- a/Content.Shared/Maps/TurfHelpers.cs +++ b/Content.Shared/Maps/TurfHelpers.cs @@ -11,22 +11,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, IMapManager? mapManager = null) - { - mapManager ??= IoCManager.Resolve(); - - if (!mapManager.TryGetGrid(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. /// @@ -67,7 +51,7 @@ public static ContentTileDefinition GetContentTileDefinition(this Tile tile, ITi /// public static bool IsSpace(this Tile tile, ITileDefinitionManager? tileDefinitionManager = null) { - return tile.GetContentTileDefinition(tileDefinitionManager).IsSpace; + return tile.GetContentTileDefinition(tileDefinitionManager).MapAtmosphere; } /// @@ -115,15 +99,6 @@ public static IEnumerable GetEntitiesInTile(this EntityCoordinates co 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 00000000000..692908d42ad --- /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)