Partial atmos refactor (#22521)

* Reduce atmos component queries

* Remove method events

* Cache airtight data

* Make MolesArchived nullable

* Fix airtight cache

* only get tile def once

* Immutable mixtures

* firelock queries

* misc

* misc cleanup

* Trim disconnected tiles

* Fix merge issues and bugs

* Why does the PR keep increasing in scope

* debug overlay

* Fix bugs

* Fix test, remove unused events

* Add setmapatmos command

* Fix overlays

* Add map check

* A

* Resolve conflicts with #26102

* Remove some obsolete methods
This commit is contained in:
Leon Friedrich
2024-03-24 03:34:56 +11:00
committed by GitHub
parent 05f282f5ce
commit 18a35e7e83
43 changed files with 922 additions and 666 deletions

View File

@@ -8,7 +8,6 @@ using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.ResourceManagement; using Robust.Client.ResourceManagement;
using Robust.Shared.Enums; using Robust.Shared.Enums;
using Robust.Shared.Graphics;
using Robust.Shared.Graphics.RSI; using Robust.Shared.Graphics.RSI;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
@@ -23,7 +22,7 @@ namespace Content.Client.Atmos.Overlays
private readonly IEntityManager _entManager; private readonly IEntityManager _entManager;
private readonly IMapManager _mapManager; private readonly IMapManager _mapManager;
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities; public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld;
private readonly ShaderInstance _shader; private readonly ShaderInstance _shader;
// Gas overlays // Gas overlays
@@ -79,7 +78,8 @@ namespace Content.Client.Atmos.Overlays
var rsi = resourceCache.GetResource<RSIResource>(animated.RsiPath).RSI; var rsi = resourceCache.GetResource<RSIResource>(animated.RsiPath).RSI;
var stateId = animated.RsiState; 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); _frames[i] = state.GetFrames(RsiDirection.South);
_frameDelays[i] = state.GetDelays(); _frameDelays[i] = state.GetDelays();
@@ -111,7 +111,8 @@ namespace Content.Client.Atmos.Overlays
for (var i = 0; i < _gasCount; i++) for (var i = 0; i < _gasCount; i++)
{ {
var delays = _frameDelays[i]; var delays = _frameDelays[i];
if (delays.Length == 0) continue; if (delays.Length == 0)
continue;
var frameCount = _frameCounter[i]; var frameCount = _frameCounter[i];
_timer[i] += args.DeltaSeconds; _timer[i] += args.DeltaSeconds;
@@ -127,7 +128,8 @@ namespace Content.Client.Atmos.Overlays
for (var i = 0; i < FireStates; i++) for (var i = 0; i < FireStates; i++)
{ {
var delays = _fireFrameDelays[i]; var delays = _fireFrameDelays[i];
if (delays.Length == 0) continue; if (delays.Length == 0)
continue;
var frameCount = _fireFrameCounter[i]; var frameCount = _fireFrameCounter[i];
_fireTimer[i] += args.DeltaSeconds; _fireTimer[i] += args.DeltaSeconds;
@@ -161,26 +163,10 @@ namespace Content.Client.Atmos.Overlays
var mapUid = _mapManager.GetMapEntityId(args.MapId); var mapUid = _mapManager.GetMapEntityId(args.MapId);
if (_entManager.TryGetComponent<MapAtmosphereComponent>(mapUid, out var atmos)) if (_entManager.TryGetComponent<MapAtmosphereComponent>(mapUid, out var atmos))
{ DrawMapOverlay(drawHandle, args, mapUid, atmos);
var bottomLeft = args.WorldAABB.BottomLeft.Floored();
var topRight = args.WorldAABB.TopRight.Ceiled();
for (var x = bottomLeft.X; x <= topRight.X; x++) if (args.Space != OverlaySpace.WorldSpaceEntities)
{ return;
for (var y = bottomLeft.Y; y <= topRight.Y; y++)
{
var tilePosition = new Vector2(x, y);
for (var i = 0; i < atmos.OverlayData.Opacity.Length; i++)
{
var opacity = atmos.OverlayData.Opacity[i];
if (opacity > 0)
args.WorldHandle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity));
}
}
}
}
// TODO: WorldBounds callback. // TODO: WorldBounds callback.
_mapManager.FindGridsIntersecting(args.MapId, args.WorldAABB, ref gridState, _mapManager.FindGridsIntersecting(args.MapId, args.WorldAABB, ref gridState,
@@ -265,5 +251,41 @@ namespace Content.Client.Atmos.Overlays
drawHandle.UseShader(null); drawHandle.UseShader(null);
drawHandle.SetTransform(Matrix3.Identity); drawHandle.SetTransform(Matrix3.Identity);
} }
private void DrawMapOverlay(
DrawingHandleWorld handle,
OverlayDrawArgs args,
EntityUid map,
MapAtmosphereComponent atmos)
{
var mapGrid = _entManager.HasComponent<MapGridComponent>(map);
// map-grid atmospheres get drawn above grids
if (mapGrid && args.Space != OverlaySpace.WorldSpaceEntities)
return;
// Normal map atmospheres get drawn below grids
if (!mapGrid && args.Space != OverlaySpace.WorldSpaceBelowWorld)
return;
var bottomLeft = args.WorldAABB.BottomLeft.Floored();
var topRight = args.WorldAABB.TopRight.Ceiled();
for (var x = bottomLeft.X; x <= topRight.X; x++)
{
for (var y = bottomLeft.Y; y <= topRight.Y; y++)
{
var tilePosition = new Vector2(x, y);
for (var i = 0; i < atmos.OverlayData.Opacity.Length; i++)
{
var opacity = atmos.OverlayData.Opacity[i];
if (opacity > 0)
handle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity));
}
}
}
}
} }
} }

View File

@@ -83,7 +83,7 @@ public sealed partial class MappingSystem : EntitySystem
if (tileDef is not ContentTileDefinition contentTileDef) if (tileDef is not ContentTileDefinition contentTileDef)
return; return;
var tileIcon = contentTileDef.IsSpace var tileIcon = contentTileDef.MapAtmosphere
? _spaceIcon ? _spaceIcon
: new Texture(contentTileDef.Sprite!.Value); : new Texture(contentTileDef.Sprite!.Value);

View File

@@ -128,7 +128,7 @@ namespace Content.IntegrationTests.Tests.Body
metaSys.Update(1.0f); metaSys.Update(1.0f);
metaSys.Update(1.0f); metaSys.Update(1.0f);
respSys.Update(2.0f); respSys.Update(2.0f);
Assert.That(GetMapMoles(), Is.EqualTo(startingMoles).Within(0.0001)); Assert.That(GetMapMoles(), Is.EqualTo(startingMoles).Within(0.0002));
}); });
} }

View File

@@ -1006,15 +1006,10 @@ public abstract partial class InteractionTest
await Server.WaitPost(() => await Server.WaitPost(() =>
{ {
var atmosSystem = SEntMan.System<AtmosphereSystem>(); var atmosSystem = SEntMan.System<AtmosphereSystem>();
var atmos = SEntMan.EnsureComponent<MapAtmosphereComponent>(target);
var moles = new float[Atmospherics.AdjustedNumberOfGases]; var moles = new float[Atmospherics.AdjustedNumberOfGases];
moles[(int) Gas.Oxygen] = 21.824779f; moles[(int) Gas.Oxygen] = 21.824779f;
moles[(int) Gas.Nitrogen] = 82.10312f; moles[(int) Gas.Nitrogen] = 82.10312f;
atmosSystem.SetMapAtmosphere(target, false, new GasMixture(2500) atmosSystem.SetMapAtmosphere(target, false, new GasMixture(moles, Atmospherics.T20C));
{
Temperature = 293.15f,
Moles = moles,
}, atmos);
}); });
} }

View File

@@ -0,0 +1,94 @@
using Content.Server.Administration;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Administration;
using Content.Shared.Atmos;
using Robust.Shared.Console;
using Robust.Shared.Map;
namespace Content.Server.Atmos.Commands;
[AdminCommand(AdminFlags.Admin)]
public sealed class AddMapAtmosCommand : LocalizedCommands
{
[Dependency] private readonly IEntityManager _entities = default!;
[Dependency] private readonly IMapManager _map = default!;
private const string _cmd = "cmd-set-map-atmos";
public override string Command => "setmapatmos";
public override string Description => Loc.GetString($"{_cmd}-desc");
public override string Help => Loc.GetString($"{_cmd}-help");
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length < 2)
{
shell.WriteLine(Help);
return;
}
int.TryParse(args[0], out var id);
var map = _map.GetMapEntityId(new MapId(id));
if (!map.IsValid())
{
shell.WriteError(Loc.GetString("cmd-parse-failure-mapid", ("arg", args[0])));
return;
}
if (!bool.TryParse(args[1], out var space))
{
shell.WriteError(Loc.GetString("cmd-parse-failure-bool", ("arg", args[1])));
return;
}
if (space || args.Length < 4)
{
_entities.RemoveComponent<MapAtmosphereComponent>(map);
shell.WriteLine(Loc.GetString($"{_cmd}-removed", ("map", id)));
return;
}
if (!float.TryParse(args[2], out var temp))
{
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[2])));
return;
}
var mix = new GasMixture(Atmospherics.CellVolume) {Temperature = Math.Min(temp, Atmospherics.TCMB)};
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
{
if (args.Length == 3 + i)
break;
if (!float.TryParse(args[3+i], out var moles))
{
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[3+i])));
return;
}
mix.AdjustMoles(i, moles);
}
var atmos = _entities.EntitySysManager.GetEntitySystem<AtmosphereSystem>();
atmos.SetMapAtmosphere(map, space, mix);
shell.WriteLine(Loc.GetString($"{_cmd}-updated", ("map", id)));
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length == 1)
return CompletionResult.FromHintOptions(CompletionHelper.MapIds(_entities), Loc.GetString($"{_cmd}-hint-map"));
if (args.Length == 2)
return CompletionResult.FromHintOptions(new[]{ "false", "true"}, Loc.GetString($"{_cmd}-hint-space"));
if (!bool.TryParse(args[1], out var space) || space)
return CompletionResult.Empty;
if (args.Length == 3)
return CompletionResult.FromHint(Loc.GetString($"{_cmd}-hint-temp"));
var gas = (Gas) args.Length - 4;
return CompletionResult.FromHint(Loc.GetString($"{_cmd}-hint-gas" , ("gas", gas.ToString())));
}
}

View File

@@ -1,9 +1,10 @@
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.Atmos.Components namespace Content.Server.Atmos.Components
{ {
[RegisterComponent] [RegisterComponent, Access(typeof(AirtightSystem))]
public sealed partial class AirtightComponent : Component public sealed partial class AirtightComponent : Component
{ {
public (EntityUid Grid, Vector2i Tile) LastPosition { get; set; } public (EntityUid Grid, Vector2i Tile) LastPosition { get; set; }
@@ -29,6 +30,7 @@ namespace Content.Server.Atmos.Components
[DataField("noAirWhenFullyAirBlocked")] [DataField("noAirWhenFullyAirBlocked")]
public bool NoAirWhenFullyAirBlocked { get; set; } = true; public bool NoAirWhenFullyAirBlocked { get; set; } = true;
[Access(Other = AccessPermissions.ReadWriteExecute)]
public AtmosDirection AirBlockedDirection => (AtmosDirection)CurrentAirBlockedDirection; public AtmosDirection AirBlockedDirection => (AtmosDirection)CurrentAirBlockedDirection;
} }
} }

View File

@@ -28,6 +28,9 @@ namespace Content.Server.Atmos.Components
[IncludeDataField(customTypeSerializer:typeof(TileAtmosCollectionSerializer))] [IncludeDataField(customTypeSerializer:typeof(TileAtmosCollectionSerializer))]
public Dictionary<Vector2i, TileAtmosphere> Tiles = new(1000); public Dictionary<Vector2i, TileAtmosphere> Tiles = new(1000);
[ViewVariables]
public HashSet<TileAtmosphere> MapTiles = new(1000);
[ViewVariables] [ViewVariables]
public readonly HashSet<TileAtmosphere> ActiveTiles = new(1000); public readonly HashSet<TileAtmosphere> ActiveTiles = new(1000);
@@ -80,7 +83,10 @@ namespace Content.Server.Atmos.Components
public readonly HashSet<Vector2i> InvalidatedCoords = new(1000); public readonly HashSet<Vector2i> InvalidatedCoords = new(1000);
[ViewVariables] [ViewVariables]
public readonly Queue<Vector2i> CurrentRunInvalidatedCoordinates = new(); public readonly Queue<TileAtmosphere> CurrentRunInvalidatedTiles = new();
[ViewVariables]
public readonly List<TileAtmosphere> PossiblyDisconnectedTiles = new(100);
[ViewVariables] [ViewVariables]
public int InvalidatedCoordsCount => InvalidatedCoords.Count; public int InvalidatedCoordsCount => InvalidatedCoords.Count;

View File

@@ -12,12 +12,14 @@ public sealed partial class MapAtmosphereComponent : SharedMapAtmosphereComponen
/// <summary> /// <summary>
/// The default GasMixture a map will have. Space mixture by default. /// The default GasMixture a map will have. Space mixture by default.
/// </summary> /// </summary>
[DataField("mixture"), ViewVariables(VVAccess.ReadWrite)] [DataField, ViewVariables(VVAccess.ReadWrite)]
public GasMixture? Mixture = GasMixture.SpaceGas; public GasMixture Mixture = GasMixture.SpaceGas;
/// <summary> /// <summary>
/// Whether empty tiles will be considered space or not. /// Whether empty tiles will be considered space or not.
/// </summary> /// </summary>
[DataField("space"), ViewVariables(VVAccess.ReadWrite)] [DataField, ViewVariables(VVAccess.ReadWrite)]
public bool Space = true; public bool Space = true;
public SharedGasTileOverlaySystem.GasOverlayData Overlay;
} }

View File

@@ -2,7 +2,6 @@ using Content.Server.Atmos.Components;
using Content.Server.Explosion.EntitySystems; using Content.Server.Explosion.EntitySystems;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Map;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
namespace Content.Server.Atmos.EntitySystems namespace Content.Server.Atmos.EntitySystems
@@ -10,7 +9,7 @@ namespace Content.Server.Atmos.EntitySystems
[UsedImplicitly] [UsedImplicitly]
public sealed class AirtightSystem : EntitySystem 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 AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly ExplosionSystem _explosionSystem = default!; [Dependency] private readonly ExplosionSystem _explosionSystem = default!;
@@ -121,19 +120,16 @@ namespace Content.Server.Atmos.EntitySystems
if (!xform.Anchored || !TryComp(xform.GridUid, out MapGridComponent? grid)) if (!xform.Anchored || !TryComp(xform.GridUid, out MapGridComponent? grid))
return; return;
airtight.LastPosition = (xform.GridUid.Value, grid.TileIndicesFor(xform.Coordinates)); var indices = _transform.GetGridTilePositionOrDefault((ent, xform), grid);
InvalidatePosition(airtight.LastPosition.Item1, airtight.LastPosition.Item2, airtight.FixVacuum && !airtight.AirBlocked); airtight.LastPosition = (xform.GridUid.Value, indices);
InvalidatePosition((xform.GridUid.Value, grid), indices);
} }
public void InvalidatePosition(EntityUid gridId, Vector2i pos, bool fixVacuum = false) public void InvalidatePosition(Entity<MapGridComponent?> grid, Vector2i pos)
{ {
if (!TryComp(gridId, out MapGridComponent? grid))
return;
var query = EntityManager.GetEntityQuery<AirtightComponent>(); var query = EntityManager.GetEntityQuery<AirtightComponent>();
_explosionSystem.UpdateAirtightMap(gridId, pos, grid, query); _explosionSystem.UpdateAirtightMap(grid, pos, grid, query);
// TODO make atmos system use query _atmosphereSystem.InvalidateTile(grid.Owner, pos);
_atmosphereSystem.InvalidateTile(gridId, pos);
} }
private AtmosDirection Rotate(AtmosDirection myDirection, Angle myAngle) private AtmosDirection Rotate(AtmosDirection myDirection, Angle myAngle)

View File

@@ -97,22 +97,19 @@ namespace Content.Server.Atmos.EntitySystems
} }
} }
private AtmosDebugOverlayData ConvertTileToData(TileAtmosphere? tile) private AtmosDebugOverlayData? ConvertTileToData(TileAtmosphere tile)
{ {
if (tile == null)
return default;
return new AtmosDebugOverlayData( return new AtmosDebugOverlayData(
tile.GridIndices, tile.GridIndices,
tile.Air?.Temperature ?? default, tile.Air?.Temperature ?? default,
tile.Air?.Moles, tile.Air?.Moles,
tile.PressureDirection, tile.PressureDirection,
tile.LastPressureDirection, tile.LastPressureDirection,
tile.BlockedAirflow, tile.AirtightData.BlockedDirections,
tile.ExcitedGroup?.GetHashCode(), tile.ExcitedGroup?.GetHashCode(),
tile.Space, tile.Space,
false, tile.MapAtmosphere,
false); tile.NoGridTile);
} }
public override void Update(float frameTime) public override void Update(float frameTime)

View File

@@ -1,37 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using Content.Server.Atmos.Components;
using Robust.Shared.Map;
using Robust.Shared.Map.Enumerators;
namespace Content.Server.Atmos.EntitySystems;
public struct AtmosObstructionEnumerator
{
private AnchoredEntitiesEnumerator _enumerator;
private EntityQuery<AirtightComponent> _query;
public AtmosObstructionEnumerator(AnchoredEntitiesEnumerator enumerator, EntityQuery<AirtightComponent> query)
{
_enumerator = enumerator;
_query = query;
}
public bool MoveNext([NotNullWhen(true)] out AirtightComponent? airtight)
{
if (!_enumerator.MoveNext(out var uid))
{
airtight = null;
return false;
}
// No rider, it makes it uglier.
// ReSharper disable once ConvertIfStatementToReturnStatement
if (!_query.TryGetComponent(uid.Value, out airtight))
{
// ReSharper disable once TailRecursiveCall
return MoveNext(out airtight);
}
return true;
}
}

View File

@@ -85,10 +85,10 @@ public partial class AtmosphereSystem
return ev.Mixtures!; return ev.Mixtures!;
} }
public void InvalidateTile(EntityUid gridUid, Vector2i tile) public void InvalidateTile(Entity<GridAtmosphereComponent?> entity, Vector2i tile)
{ {
var ev = new InvalidateTileMethodEvent(gridUid, tile); if (_atmosQuery.Resolve(entity.Owner, ref entity.Comp, false))
RaiseLocalEvent(gridUid, ref ev); entity.Comp.InvalidatedCoords.Add(tile);
} }
public GasMixture?[]? GetTileMixtures(EntityUid? gridUid, EntityUid? mapUid, List<Vector2i> tiles, bool excite = false) public GasMixture?[]? GetTileMixtures(EntityUid? gridUid, EntityUid? mapUid, List<Vector2i> tiles, bool excite = false)
@@ -176,11 +176,11 @@ public partial class AtmosphereSystem
public bool IsTileAirBlocked(EntityUid gridUid, Vector2i tile, AtmosDirection directions = AtmosDirection.All, MapGridComponent? mapGridComp = null) public bool IsTileAirBlocked(EntityUid gridUid, Vector2i tile, AtmosDirection directions = AtmosDirection.All, MapGridComponent? mapGridComp = null)
{ {
var ev = new IsTileAirBlockedMethodEvent(gridUid, tile, directions, mapGridComp); if (!Resolve(gridUid, ref mapGridComp))
RaiseLocalEvent(gridUid, ref ev); return false;
// If nothing handled the event, it'll default to true. var data = GetAirtightData(gridUid, mapGridComp, tile);
return ev.Result; return data.BlockedDirections.IsFlagSet(directions);
} }
public bool IsTileSpace(EntityUid? gridUid, EntityUid? mapUid, Vector2i tile, MapGridComponent? mapGridComp = null) public bool IsTileSpace(EntityUid? gridUid, EntityUid? mapUid, Vector2i tile, MapGridComponent? mapGridComp = null)
@@ -231,12 +231,6 @@ public partial class AtmosphereSystem
return ev.Result ?? Enumerable.Empty<GasMixture>(); return ev.Result ?? Enumerable.Empty<GasMixture>();
} }
public void UpdateAdjacent(EntityUid gridUid, Vector2i tile, MapGridComponent? mapGridComp = null)
{
var ev = new UpdateAdjacentMethodEvent(gridUid, tile, mapGridComp);
RaiseLocalEvent(gridUid, ref ev);
}
public void HotspotExpose(EntityUid gridUid, Vector2i tile, float exposedTemperature, float exposedVolume, public void HotspotExpose(EntityUid gridUid, Vector2i tile, float exposedTemperature, float exposedVolume,
EntityUid? sparkSourceUid = null, bool soh = false) EntityUid? sparkSourceUid = null, bool soh = false)
{ {
@@ -259,12 +253,6 @@ public partial class AtmosphereSystem
return ev.Result; 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) public void AddPipeNet(EntityUid gridUid, PipeNet pipeNet)
{ {
var ev = new AddPipeNetMethodEvent(gridUid, pipeNet); var ev = new AddPipeNetMethodEvent(gridUid, pipeNet);
@@ -307,9 +295,6 @@ public partial class AtmosphereSystem
[ByRefEvent] private record struct GetAllMixturesMethodEvent [ByRefEvent] private record struct GetAllMixturesMethodEvent
(EntityUid Grid, bool Excite = false, IEnumerable<GasMixture>? Mixtures = null, bool Handled = false); (EntityUid Grid, bool Excite = false, IEnumerable<GasMixture>? Mixtures = null, bool Handled = false);
[ByRefEvent] private record struct InvalidateTileMethodEvent
(EntityUid Grid, Vector2i Tile, bool Handled = false);
[ByRefEvent] private record struct GetTileMixturesMethodEvent [ByRefEvent] private record struct GetTileMixturesMethodEvent
(EntityUid? GridUid, EntityUid? MapUid, List<Vector2i> Tiles, bool Excite = false, GasMixture?[]? Mixtures = null, bool Handled = false); (EntityUid? GridUid, EntityUid? MapUid, List<Vector2i> Tiles, bool Excite = false, GasMixture?[]? Mixtures = null, bool Handled = false);
@@ -319,16 +304,6 @@ public partial class AtmosphereSystem
[ByRefEvent] private record struct ReactTileMethodEvent [ByRefEvent] private record struct ReactTileMethodEvent
(EntityUid GridId, Vector2i Tile, ReactionResult Result = default, bool Handled = false); (EntityUid GridId, Vector2i Tile, ReactionResult Result = default, bool Handled = false);
[ByRefEvent] private record struct IsTileAirBlockedMethodEvent
(EntityUid Grid, Vector2i Tile, AtmosDirection Direction = AtmosDirection.All, MapGridComponent? MapGridComponent = null, bool Result = false, bool Handled = false)
{
/// <summary>
/// True if one of the enabled blockers has <see cref="AirtightComponent.NoAirWhenFullyAirBlocked"/>. Note
/// that this does not actually check if all directions are blocked.
/// </summary>
public bool NoAir = false;
}
[ByRefEvent] private record struct IsTileSpaceMethodEvent [ByRefEvent] private record struct IsTileSpaceMethodEvent
(EntityUid? Grid, EntityUid? Map, Vector2i Tile, MapGridComponent? MapGridComponent = null, bool Result = true, bool Handled = false); (EntityUid? Grid, EntityUid? Map, Vector2i Tile, MapGridComponent? MapGridComponent = null, bool Result = true, bool Handled = false);
@@ -339,9 +314,6 @@ public partial class AtmosphereSystem
(EntityUid Grid, Vector2i Tile, bool IncludeBlocked, bool Excite, (EntityUid Grid, Vector2i Tile, bool IncludeBlocked, bool Excite,
IEnumerable<GasMixture>? Result = null, bool Handled = false); IEnumerable<GasMixture>? Result = null, bool Handled = false);
[ByRefEvent] private record struct UpdateAdjacentMethodEvent
(EntityUid Grid, Vector2i Tile, MapGridComponent? MapGridComponent = null, bool Handled = false);
[ByRefEvent] private record struct HotspotExposeMethodEvent [ByRefEvent] private record struct HotspotExposeMethodEvent
(EntityUid Grid, EntityUid? SparkSourceUid, Vector2i Tile, float ExposedTemperature, float ExposedVolume, bool soh, bool Handled = false); (EntityUid Grid, EntityUid? SparkSourceUid, Vector2i Tile, float ExposedTemperature, float ExposedVolume, bool soh, bool Handled = false);
@@ -351,9 +323,6 @@ public partial class AtmosphereSystem
[ByRefEvent] private record struct IsHotspotActiveMethodEvent [ByRefEvent] private record struct IsHotspotActiveMethodEvent
(EntityUid Grid, Vector2i Tile, bool Result = false, bool Handled = false); (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 [ByRefEvent] private record struct AddPipeNetMethodEvent
(EntityUid Grid, PipeNet PipeNet, bool Handled = false); (EntityUid Grid, PipeNet PipeNet, bool Handled = false);

View File

@@ -72,7 +72,8 @@ namespace Content.Server.Atmos.EntitySystems
var tileSize = excitedGroup.Tiles.Count; var tileSize = excitedGroup.Tiles.Count;
if (excitedGroup.Disposed) return; if (excitedGroup.Disposed)
return;
if (tileSize == 0) if (tileSize == 0)
{ {
@@ -98,7 +99,9 @@ namespace Content.Server.Atmos.EntitySystems
foreach (var tile in excitedGroup.Tiles) foreach (var tile in excitedGroup.Tiles)
{ {
if (tile?.Air == null) continue; if (tile?.Air == null)
continue;
tile.Air.CopyFromMutable(combined); tile.Air.CopyFromMutable(combined);
InvalidateVisuals(tile.GridIndex, tile.GridIndices); InvalidateVisuals(tile.GridIndex, tile.GridIndices);
} }
@@ -106,21 +109,23 @@ namespace Content.Server.Atmos.EntitySystems
excitedGroup.BreakdownCooldown = 0; excitedGroup.BreakdownCooldown = 0;
} }
private void ExcitedGroupDismantle(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup, bool unexcite = true) /// <summary>
/// This de-activates and removes all tiles in an excited group.
/// </summary>
private void DeactivateGroupTiles(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup)
{ {
foreach (var tile in excitedGroup.Tiles) foreach (var tile in excitedGroup.Tiles)
{ {
tile.ExcitedGroup = null; tile.ExcitedGroup = null;
if (!unexcite)
continue;
RemoveActiveTile(gridAtmosphere, tile); RemoveActiveTile(gridAtmosphere, tile);
} }
excitedGroup.Tiles.Clear(); excitedGroup.Tiles.Clear();
} }
/// <summary>
/// This removes an excited group without de-activating its tiles.
/// </summary>
private void ExcitedGroupDispose(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup) private void ExcitedGroupDispose(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup)
{ {
if (excitedGroup.Disposed) if (excitedGroup.Disposed)
@@ -129,9 +134,14 @@ namespace Content.Server.Atmos.EntitySystems
DebugTools.Assert(gridAtmosphere.ExcitedGroups.Contains(excitedGroup), "Grid Atmosphere does not contain Excited Group!"); DebugTools.Assert(gridAtmosphere.ExcitedGroups.Contains(excitedGroup), "Grid Atmosphere does not contain Excited Group!");
excitedGroup.Disposed = true; excitedGroup.Disposed = true;
gridAtmosphere.ExcitedGroups.Remove(excitedGroup); gridAtmosphere.ExcitedGroups.Remove(excitedGroup);
ExcitedGroupDismantle(gridAtmosphere, excitedGroup, false);
foreach (var tile in excitedGroup.Tiles)
{
tile.ExcitedGroup = null;
}
excitedGroup.Tiles.Clear();
} }
} }
} }

View File

@@ -14,6 +14,7 @@ public sealed partial class AtmosphereSystem
private void InitializeGridAtmosphere() private void InitializeGridAtmosphere()
{ {
SubscribeLocalEvent<GridAtmosphereComponent, ComponentInit>(OnGridAtmosphereInit); SubscribeLocalEvent<GridAtmosphereComponent, ComponentInit>(OnGridAtmosphereInit);
SubscribeLocalEvent<GridAtmosphereComponent, ComponentStartup>(OnGridAtmosphereStartup);
SubscribeLocalEvent<GridAtmosphereComponent, ComponentRemove>(OnAtmosphereRemove); SubscribeLocalEvent<GridAtmosphereComponent, ComponentRemove>(OnAtmosphereRemove);
SubscribeLocalEvent<GridAtmosphereComponent, GridSplitEvent>(OnGridSplit); SubscribeLocalEvent<GridAtmosphereComponent, GridSplitEvent>(OnGridSplit);
@@ -22,19 +23,15 @@ public sealed partial class AtmosphereSystem
SubscribeLocalEvent<GridAtmosphereComponent, HasAtmosphereMethodEvent>(GridHasAtmosphere); SubscribeLocalEvent<GridAtmosphereComponent, HasAtmosphereMethodEvent>(GridHasAtmosphere);
SubscribeLocalEvent<GridAtmosphereComponent, IsSimulatedGridMethodEvent>(GridIsSimulated); SubscribeLocalEvent<GridAtmosphereComponent, IsSimulatedGridMethodEvent>(GridIsSimulated);
SubscribeLocalEvent<GridAtmosphereComponent, GetAllMixturesMethodEvent>(GridGetAllMixtures); SubscribeLocalEvent<GridAtmosphereComponent, GetAllMixturesMethodEvent>(GridGetAllMixtures);
SubscribeLocalEvent<GridAtmosphereComponent, InvalidateTileMethodEvent>(GridInvalidateTile);
SubscribeLocalEvent<GridAtmosphereComponent, GetTileMixtureMethodEvent>(GridGetTileMixture); SubscribeLocalEvent<GridAtmosphereComponent, GetTileMixtureMethodEvent>(GridGetTileMixture);
SubscribeLocalEvent<GridAtmosphereComponent, GetTileMixturesMethodEvent>(GridGetTileMixtures); SubscribeLocalEvent<GridAtmosphereComponent, GetTileMixturesMethodEvent>(GridGetTileMixtures);
SubscribeLocalEvent<GridAtmosphereComponent, ReactTileMethodEvent>(GridReactTile); SubscribeLocalEvent<GridAtmosphereComponent, ReactTileMethodEvent>(GridReactTile);
SubscribeLocalEvent<GridAtmosphereComponent, IsTileAirBlockedMethodEvent>(GridIsTileAirBlocked);
SubscribeLocalEvent<GridAtmosphereComponent, IsTileSpaceMethodEvent>(GridIsTileSpace); SubscribeLocalEvent<GridAtmosphereComponent, IsTileSpaceMethodEvent>(GridIsTileSpace);
SubscribeLocalEvent<GridAtmosphereComponent, GetAdjacentTilesMethodEvent>(GridGetAdjacentTiles); SubscribeLocalEvent<GridAtmosphereComponent, GetAdjacentTilesMethodEvent>(GridGetAdjacentTiles);
SubscribeLocalEvent<GridAtmosphereComponent, GetAdjacentTileMixturesMethodEvent>(GridGetAdjacentTileMixtures); SubscribeLocalEvent<GridAtmosphereComponent, GetAdjacentTileMixturesMethodEvent>(GridGetAdjacentTileMixtures);
SubscribeLocalEvent<GridAtmosphereComponent, UpdateAdjacentMethodEvent>(GridUpdateAdjacent);
SubscribeLocalEvent<GridAtmosphereComponent, HotspotExposeMethodEvent>(GridHotspotExpose); SubscribeLocalEvent<GridAtmosphereComponent, HotspotExposeMethodEvent>(GridHotspotExpose);
SubscribeLocalEvent<GridAtmosphereComponent, HotspotExtinguishMethodEvent>(GridHotspotExtinguish); SubscribeLocalEvent<GridAtmosphereComponent, HotspotExtinguishMethodEvent>(GridHotspotExtinguish);
SubscribeLocalEvent<GridAtmosphereComponent, IsHotspotActiveMethodEvent>(GridIsHotspotActive); SubscribeLocalEvent<GridAtmosphereComponent, IsHotspotActiveMethodEvent>(GridIsHotspotActive);
SubscribeLocalEvent<GridAtmosphereComponent, FixTileVacuumMethodEvent>(GridFixTileVacuum);
SubscribeLocalEvent<GridAtmosphereComponent, AddPipeNetMethodEvent>(GridAddPipeNet); SubscribeLocalEvent<GridAtmosphereComponent, AddPipeNetMethodEvent>(GridAddPipeNet);
SubscribeLocalEvent<GridAtmosphereComponent, RemovePipeNetMethodEvent>(GridRemovePipeNet); SubscribeLocalEvent<GridAtmosphereComponent, RemovePipeNetMethodEvent>(GridRemovePipeNet);
SubscribeLocalEvent<GridAtmosphereComponent, AddAtmosDeviceMethodEvent>(GridAddAtmosDevice); SubscribeLocalEvent<GridAtmosphereComponent, AddAtmosDeviceMethodEvent>(GridAddAtmosDevice);
@@ -56,22 +53,23 @@ public sealed partial class AtmosphereSystem
} }
} }
private void OnGridAtmosphereInit(EntityUid uid, GridAtmosphereComponent gridAtmosphere, ComponentInit args) private void OnGridAtmosphereInit(EntityUid uid, GridAtmosphereComponent component, ComponentInit args)
{ {
base.Initialize(); base.Initialize();
EnsureComp<GasTileOverlayComponent>(uid);
foreach (var tile in component.Tiles.Values)
{
tile.GridIndex = uid;
}
}
private void OnGridAtmosphereStartup(EntityUid uid, GridAtmosphereComponent component, ComponentStartup args)
{
if (!TryComp(uid, out MapGridComponent? mapGrid)) if (!TryComp(uid, out MapGridComponent? mapGrid))
return; return;
EnsureComp<GasTileOverlayComponent>(uid); InvalidateAllTiles((uid, mapGrid, component));
foreach (var (indices, tile) in gridAtmosphere.Tiles)
{
gridAtmosphere.InvalidatedCoords.Add(indices);
tile.GridIndex = uid;
}
GridRepopulateTiles((uid, mapGrid, gridAtmosphere));
} }
private void OnGridSplit(EntityUid uid, GridAtmosphereComponent originalGridAtmos, ref GridSplitEvent args) private void OnGridSplit(EntityUid uid, GridAtmosphereComponent originalGridAtmos, ref GridSplitEvent args)
@@ -104,8 +102,7 @@ public sealed partial class AtmosphereSystem
continue; continue;
// Copy a bunch of data over... Not great, maybe put this in TileAtmosphere? // Copy a bunch of data over... Not great, maybe put this in TileAtmosphere?
newTileAtmosphere.Air = tileAtmosphere.Air?.Clone() ?? null; newTileAtmosphere.Air = tileAtmosphere.Air?.Clone();
newTileAtmosphere.MolesArchived = newTileAtmosphere.Air == null ? null : new float[Atmospherics.AdjustedNumberOfGases];
newTileAtmosphere.Hotspot = tileAtmosphere.Hotspot; newTileAtmosphere.Hotspot = tileAtmosphere.Hotspot;
newTileAtmosphere.HeatCapacity = tileAtmosphere.HeatCapacity; newTileAtmosphere.HeatCapacity = tileAtmosphere.HeatCapacity;
newTileAtmosphere.Temperature = tileAtmosphere.Temperature; newTileAtmosphere.Temperature = tileAtmosphere.Temperature;
@@ -170,15 +167,6 @@ public sealed partial class AtmosphereSystem
args.Handled = true; 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, private void GridGetTileMixture(EntityUid uid, GridAtmosphereComponent component,
ref GetTileMixtureMethodEvent args) ref GetTileMixtureMethodEvent args)
{ {
@@ -233,43 +221,6 @@ public sealed partial class AtmosphereSystem
args.Handled = true; 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) private void GridIsTileSpace(EntityUid uid, GridAtmosphereComponent component, ref IsTileSpaceMethodEvent args)
{ {
if (args.Handled) if (args.Handled)
@@ -331,71 +282,58 @@ public sealed partial class AtmosphereSystem
args.Handled = true; args.Handled = true;
} }
private void GridUpdateAdjacent(EntityUid uid, GridAtmosphereComponent component, /// <summary>
ref UpdateAdjacentMethodEvent args) /// Update array of adjacent tiles and the adjacency flags. Optionally activates all tiles with modified adjacencies.
/// </summary>
private void UpdateAdjacentTiles(
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
TileAtmosphere tile,
bool activate = false)
{ {
if (args.Handled) var uid = ent.Owner;
return; var atmos = ent.Comp1;
var blockedDirs = tile.AirtightData.BlockedDirections;
var mapGridComp = args.MapGridComponent; if (activate)
AddActiveTile(atmos, tile);
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;
tile.AdjacentBits = AtmosDirection.Invalid; tile.AdjacentBits = AtmosDirection.Invalid;
tile.BlockedAirflow = GetBlockedDirections(mapGridComp, tile.GridIndices);
for (var i = 0; i < Atmospherics.Directions; i++) for (var i = 0; i < Atmospherics.Directions; i++)
{ {
var direction = (AtmosDirection) (1 << i); var direction = (AtmosDirection) (1 << i);
var adjacentIndices = tile.GridIndices.Offset(direction);
var otherIndices = tile.GridIndices.Offset(direction); TileAtmosphere? adjacent;
if (!tile.NoGridTile)
if (!component.Tiles.TryGetValue(otherIndices, out var adjacent))
{ {
adjacent = new TileAtmosphere(tile.GridIndex, otherIndices, adjacent = GetOrNewTile(uid, atmos, adjacentIndices);
GetTileMixture(null, mapUid, otherIndices),
space: IsTileSpace(null, mapUid, otherIndices, mapGridComp));
} }
else if (!atmos.Tiles.TryGetValue(adjacentIndices, out adjacent))
var oppositeDirection = direction.GetOpposite();
adjacent.BlockedAirflow = GetBlockedDirections(mapGridComp, adjacent.GridIndices);
// Pass in MapGridComponent so we don't have to resolve it for every adjacent direction.
var tileBlockedEv = new IsTileAirBlockedMethodEvent(uid, tile.GridIndices, direction, mapGridComp);
GridIsTileAirBlocked(uid, component, ref tileBlockedEv);
var adjacentBlockedEv =
new IsTileAirBlockedMethodEvent(uid, adjacent.GridIndices, oppositeDirection, mapGridComp);
GridIsTileAirBlocked(uid, component, ref adjacentBlockedEv);
if (!adjacent.BlockedAirflow.IsFlagSet(oppositeDirection) && !tileBlockedEv.Result)
{
adjacent.AdjacentBits |= oppositeDirection;
adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = tile;
}
else
{
adjacent.AdjacentBits &= ~oppositeDirection;
adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = null;
}
if (!tile.BlockedAirflow.IsFlagSet(direction) && !adjacentBlockedEv.Result)
{
tile.AdjacentBits |= direction;
tile.AdjacentTiles[direction.ToIndex()] = adjacent;
}
else
{ {
tile.AdjacentBits &= ~direction; tile.AdjacentBits &= ~direction;
tile.AdjacentTiles[direction.ToIndex()] = null; tile.AdjacentTiles[i] = null;
continue;
}
var adjBlockDirs = adjacent.AirtightData.BlockedDirections;
if (activate)
AddActiveTile(atmos, adjacent);
var oppositeDirection = direction.GetOpposite();
if (adjBlockDirs.IsFlagSet(oppositeDirection) || blockedDirs.IsFlagSet(direction))
{
// Adjacency is blocked by some airtight entity.
tile.AdjacentBits &= ~direction;
adjacent.AdjacentBits &= ~oppositeDirection;
tile.AdjacentTiles[i] = null;
adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = null;
}
else
{
// No airtight entity in the way.
tile.AdjacentBits |= direction;
adjacent.AdjacentBits |= oppositeDirection;
tile.AdjacentTiles[i] = adjacent;
adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = tile;
} }
DebugTools.Assert(!(tile.AdjacentBits.IsFlagSet(direction) ^ DebugTools.Assert(!(tile.AdjacentBits.IsFlagSet(direction) ^
@@ -409,6 +347,16 @@ public sealed partial class AtmosphereSystem
tile.MonstermosInfo.CurrentTransferDirection = AtmosDirection.Invalid; 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) private void GridHotspotExpose(EntityUid uid, GridAtmosphereComponent component, ref HotspotExposeMethodEvent args)
{ {
if (args.Handled) if (args.Handled)
@@ -451,54 +399,50 @@ public sealed partial class AtmosphereSystem
args.Handled = true; args.Handled = true;
} }
private void GridFixTileVacuum(EntityUid uid, GridAtmosphereComponent component, ref FixTileVacuumMethodEvent args) private void GridFixTileVacuum(
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
TileAtmosphere tile,
float volume)
{ {
if (args.Handled) DebugTools.AssertNotNull(tile.Air);
return; DebugTools.Assert(tile.Air?.Immutable == false );
Array.Clear(tile.MolesArchived);
var adjEv = new GetAdjacentTileMixturesMethodEvent(uid, args.Tile, false, true);
GridGetAdjacentTileMixtures(uid, component, ref adjEv);
if (!adjEv.Handled || !component.Tiles.TryGetValue(args.Tile, out var tile))
return;
if (!TryComp<MapGridComponent>(uid, out var mapGridComp))
return;
var adjacent = adjEv.Result!.ToArray();
// Return early, let's not cause any funny NaNs or needless vacuums.
if (adjacent.Length == 0)
return;
tile.Air = new GasMixture
{
Volume = GetVolumeForTiles(mapGridComp, 1),
Temperature = Atmospherics.T20C
};
tile.MolesArchived = new float[Atmospherics.AdjustedNumberOfGases];
tile.ArchivedCycle = 0; tile.ArchivedCycle = 0;
var ratio = 1f / adjacent.Length; var count = 0;
foreach (var adj in tile.AdjacentTiles)
{
if (adj?.Air != null)
count++;
}
var ratio = 1f / count;
var totalTemperature = 0f; var totalTemperature = 0f;
foreach (var adj in adjacent) foreach (var adj in tile.AdjacentTiles)
{ {
if (adj?.Air == null)
continue;
totalTemperature += adj.Temperature; 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... // 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. // And merge it to the new tile air.
Merge(tile.Air, mix); Merge(tile.Air, mix);
// Return removed gas to its original mixture. // 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... // 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) private void GridAddPipeNet(EntityUid uid, GridAtmosphereComponent component, ref AddPipeNetMethodEvent args)
@@ -547,30 +491,21 @@ public sealed partial class AtmosphereSystem
/// <summary> /// <summary>
/// Repopulates all tiles on a grid atmosphere. /// Repopulates all tiles on a grid atmosphere.
/// </summary> /// </summary>
/// <param name="mapGrid">The grid where to get all valid tiles from.</param> public void InvalidateAllTiles(Entity<MapGridComponent?, GridAtmosphereComponent?> entity)
/// <param name="gridAtmosphere">The grid atmosphere where the tiles will be repopulated.</param>
private void GridRepopulateTiles(Entity<MapGridComponent, GridAtmosphereComponent> grid)
{ {
var (uid, mapGrid, gridAtmosphere) = grid; var (uid, grid, atmos) = entity;
var volume = GetVolumeForTiles(mapGrid, 1); 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)) atmos.InvalidatedCoords.Add(indices);
gridAtmosphere.Tiles[tile.GridIndices] = new TileAtmosphere(tile.GridUid, tile.GridIndices,
new GasMixture(volume) { Temperature = Atmospherics.T20C });
gridAtmosphere.InvalidatedCoords.Add(tile.GridIndices);
} }
TryComp(uid, out GasTileOverlayComponent? overlay); var enumerator = _map.GetAllTilesEnumerator(uid, grid);
while (enumerator.MoveNext(out var tile))
// Gotta do this afterwards so we can properly update adjacent tiles.
foreach (var (position, _) in gridAtmosphere.Tiles.ToArray())
{ {
var ev = new UpdateAdjacentMethodEvent(uid, position); atmos.InvalidatedCoords.Add(tile.Value.GridIndices);
GridUpdateAdjacent(uid, gridAtmosphere, ref ev);
InvalidateVisuals(uid, position, overlay);
} }
} }

View File

@@ -1,13 +1,11 @@
using Content.Server.Atmos.Components; using Content.Server.Atmos.Components;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Audio;
using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Components;
using Content.Shared.Physics; using Content.Shared.Physics;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Physics; using Robust.Shared.Physics;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Utility; using Robust.Shared.Utility;

View File

@@ -1,12 +1,13 @@
using Content.Server.Atmos.Components; using Content.Server.Atmos.Components;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Components; using Content.Shared.Atmos.Components;
using Robust.Shared.Utility;
namespace Content.Server.Atmos.EntitySystems namespace Content.Server.Atmos.EntitySystems
{ {
public sealed partial class AtmosphereSystem 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 // Can't process a tile without air
if (tile.Air == null) if (tile.Air == null)
@@ -116,15 +117,9 @@ namespace Content.Server.Atmos.EntitySystems
private void Archive(TileAtmosphere tile, int fireCount) private void Archive(TileAtmosphere tile, int fireCount)
{ {
if (tile.Air != null) if (tile.Air != null)
{
tile.Air.Moles.AsSpan().CopyTo(tile.MolesArchived.AsSpan()); 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; tile.ArchivedCycle = fireCount;
} }
@@ -166,6 +161,12 @@ namespace Content.Server.Atmos.EntitySystems
/// <param name="disposeExcitedGroup">Whether to dispose of the tile's <see cref="ExcitedGroup"/></param> /// <param name="disposeExcitedGroup">Whether to dispose of the tile's <see cref="ExcitedGroup"/></param>
private void RemoveActiveTile(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, bool disposeExcitedGroup = true) 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; tile.Excited = false;
gridAtmosphere.ActiveTiles.Remove(tile); gridAtmosphere.ActiveTiles.Remove(tile);
@@ -186,7 +187,6 @@ namespace Content.Server.Atmos.EntitySystems
if (tile.Air == null) if (tile.Air == null)
return tile.HeatCapacity; return tile.HeatCapacity;
// Moles archived is not null if air is not null.
return GetHeatCapacityCalculation(tile.MolesArchived!, tile.Space); return GetHeatCapacityCalculation(tile.MolesArchived!, tile.Space);
} }

View File

@@ -1,6 +1,8 @@
using Content.Server.Atmos.Components; using Content.Server.Atmos.Components;
using Content.Shared.Atmos.Components; using Content.Shared.Atmos.Components;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Map.Components;
using Robust.Shared.Utility;
namespace Content.Server.Atmos.EntitySystems; namespace Content.Server.Atmos.EntitySystems;
@@ -8,10 +10,25 @@ public partial class AtmosphereSystem
{ {
private void InitializeMap() private void InitializeMap()
{ {
SubscribeLocalEvent<MapAtmosphereComponent, ComponentInit>(OnMapStartup);
SubscribeLocalEvent<MapAtmosphereComponent, ComponentRemove>(OnMapRemove);
SubscribeLocalEvent<MapAtmosphereComponent, IsTileSpaceMethodEvent>(MapIsTileSpace); SubscribeLocalEvent<MapAtmosphereComponent, IsTileSpaceMethodEvent>(MapIsTileSpace);
SubscribeLocalEvent<MapAtmosphereComponent, GetTileMixtureMethodEvent>(MapGetTileMixture); SubscribeLocalEvent<MapAtmosphereComponent, GetTileMixtureMethodEvent>(MapGetTileMixture);
SubscribeLocalEvent<MapAtmosphereComponent, GetTileMixturesMethodEvent>(MapGetTileMixtures); SubscribeLocalEvent<MapAtmosphereComponent, GetTileMixturesMethodEvent>(MapGetTileMixtures);
SubscribeLocalEvent<MapAtmosphereComponent, ComponentGetState>(OnMapGetState); SubscribeLocalEvent<MapAtmosphereComponent, ComponentGetState>(OnMapGetState);
SubscribeLocalEvent<GridAtmosphereComponent, EntParentChangedMessage>(OnGridParentChanged);
}
private void OnMapStartup(EntityUid uid, MapAtmosphereComponent component, ComponentInit args)
{
component.Mixture.MarkImmutable();
component.Overlay = _gasTileOverlaySystem.GetOverlayData(component.Mixture);
}
private void OnMapRemove(EntityUid uid, MapAtmosphereComponent component, ComponentRemove args)
{
if (!TerminatingOrDeleted(uid))
RefreshAllGridMapAtmospheres(uid);
} }
private void MapIsTileSpace(EntityUid uid, MapAtmosphereComponent component, ref IsTileSpaceMethodEvent args) private void MapIsTileSpace(EntityUid uid, MapAtmosphereComponent component, ref IsTileSpaceMethodEvent args)
@@ -28,54 +45,115 @@ public partial class AtmosphereSystem
if (args.Handled) if (args.Handled)
return; return;
// Clone the mixture, if possible. args.Mixture = component.Mixture;
args.Mixture = component.Mixture?.Clone();
args.Handled = true; args.Handled = true;
} }
private void MapGetTileMixtures(EntityUid uid, MapAtmosphereComponent component, ref GetTileMixturesMethodEvent args) private void MapGetTileMixtures(EntityUid uid, MapAtmosphereComponent component, ref GetTileMixturesMethodEvent args)
{ {
if (args.Handled || component.Mixture == null) if (args.Handled)
return; return;
args.Handled = true; args.Handled = true;
args.Mixtures ??= new GasMixture?[args.Tiles.Count]; args.Mixtures ??= new GasMixture?[args.Tiles.Count];
for (var i = 0; i < args.Tiles.Count; i++) 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) private void OnMapGetState(EntityUid uid, MapAtmosphereComponent component, ref ComponentGetState args)
{ {
args.State = new MapAtmosphereComponentState(_gasTileOverlaySystem.GetOverlayData(component.Mixture)); args.State = new MapAtmosphereComponentState(component.Overlay);
} }
public void SetMapAtmosphere(EntityUid uid, bool space, GasMixture mixture, MapAtmosphereComponent? component = null) public void SetMapAtmosphere(EntityUid uid, bool space, GasMixture mixture)
{
DebugTools.Assert(HasComp<MapComponent>(uid));
var component = EnsureComp<MapAtmosphereComponent>(uid);
SetMapGasMixture(uid, mixture, component, false);
SetMapSpace(uid, space, component, false);
RefreshAllGridMapAtmospheres(uid);
}
public void SetMapGasMixture(EntityUid uid, GasMixture mixture, MapAtmosphereComponent? component = null, bool updateTiles = true)
{ {
if (!Resolve(uid, ref component)) if (!Resolve(uid, ref component))
return; return;
if (!mixture.Immutable)
{
mixture = mixture.Clone();
mixture.MarkImmutable();
}
component.Mixture = mixture;
component.Overlay = _gasTileOverlaySystem.GetOverlayData(component.Mixture);
Dirty(uid, component);
if (updateTiles)
RefreshAllGridMapAtmospheres(uid);
}
public void SetMapSpace(EntityUid uid, bool space, MapAtmosphereComponent? component = null, bool updateTiles = true)
{
if (!Resolve(uid, ref component))
return;
if (component.Space == space)
return;
component.Space = space; component.Space = space;
component.Mixture = mixture;
Dirty(uid, component); if (updateTiles)
RefreshAllGridMapAtmospheres(uid);
} }
public void SetMapGasMixture(EntityUid uid, GasMixture? mixture, MapAtmosphereComponent? component = null) /// <summary>
/// Forces a refresh of all MapAtmosphere tiles on every grid on a map.
/// </summary>
public void RefreshAllGridMapAtmospheres(EntityUid map)
{ {
if (!Resolve(uid, ref component)) DebugTools.Assert(HasComp<MapComponent>(map));
return; var enumerator = AllEntityQuery<GridAtmosphereComponent, TransformComponent>();
while (enumerator.MoveNext(out var grid, out var atmos, out var xform))
component.Mixture = mixture; {
Dirty(uid, component); if (xform.MapUid == map)
RefreshMapAtmosphereTiles((grid, atmos));
}
} }
public void SetMapSpace(EntityUid uid, bool space, MapAtmosphereComponent? component = null) /// <summary>
/// Forces a refresh of all MapAtmosphere tiles on a given grid.
/// </summary>
private void RefreshMapAtmosphereTiles(Entity<GridAtmosphereComponent?> grid)
{ {
if (!Resolve(uid, ref component)) if (!Resolve(grid.Owner, ref grid.Comp))
return; return;
component.Space = space; var atmos = grid.Comp;
Dirty(uid, component); foreach (var tile in atmos.MapTiles)
{
RemoveMapAtmos(atmos, tile);
atmos.InvalidatedCoords.Add(tile.GridIndices);
}
atmos.MapTiles.Clear();
}
/// <summary>
/// Handles updating map-atmospheres when grids move across maps.
/// </summary>
private void OnGridParentChanged(Entity<GridAtmosphereComponent> grid, ref EntParentChangedMessage args)
{
// Do nothing if detaching to nullspace
if (!args.Transform.ParentUid.IsValid())
return;
// Avoid doing work if moving from a space-map to another space-map.
if (args.OldParent == null
|| HasComp<MapAtmosphereComponent>(args.OldParent)
|| HasComp<MapAtmosphereComponent>(args.Transform.ParentUid))
{
RefreshMapAtmosphereTiles((grid, grid));
}
} }
} }

View File

@@ -5,7 +5,6 @@ using Content.Server.Doors.Systems;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Components; using Content.Shared.Atmos.Components;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Doors.Components;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using Robust.Shared.Random; using Robust.Shared.Random;
@@ -27,7 +26,10 @@ namespace Content.Server.Atmos.EntitySystems
private readonly TileAtmosphere[] _depressurizeSpaceTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit]; private readonly TileAtmosphere[] _depressurizeSpaceTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit];
private readonly TileAtmosphere[] _depressurizeProgressionOrder = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit * 2]; private readonly TileAtmosphere[] _depressurizeProgressionOrder = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit * 2];
private void EqualizePressureInZone(Entity<MapGridComponent, GridAtmosphereComponent> ent, TileAtmosphere tile, int cycleNum, GasTileOverlayComponent? visuals) private void EqualizePressureInZone(
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
TileAtmosphere tile,
int cycleNum)
{ {
if (tile.Air == null || (tile.MonstermosInfo.LastCycle >= cycleNum)) if (tile.Air == null || (tile.MonstermosInfo.LastCycle >= cycleNum))
return; // Already done. return; // Already done.
@@ -56,7 +58,7 @@ namespace Content.Server.Atmos.EntitySystems
return; return;
} }
var (_, mapGrid, gridAtmosphere) = ent; var gridAtmosphere = ent.Comp1;
var queueCycle = ++gridAtmosphere.EqualizationQueueCycleControl; var queueCycle = ++gridAtmosphere.EqualizationQueueCycleControl;
var totalMoles = 0f; var totalMoles = 0f;
_equalizeTiles[0] = tile; _equalizeTiles[0] = tile;
@@ -91,7 +93,7 @@ namespace Content.Server.Atmos.EntitySystems
{ {
// Looks like someone opened an airlock to space! // Looks like someone opened an airlock to space!
ExplosivelyDepressurize(ent, tile, cycleNum, visuals); ExplosivelyDepressurize(ent, tile, cycleNum);
return; return;
} }
} }
@@ -216,9 +218,13 @@ namespace Content.Server.Atmos.EntitySystems
for (var k = 0; k < Atmospherics.Directions; k++) for (var k = 0; k < Atmospherics.Directions; k++)
{ {
var direction = (AtmosDirection) (1 << k); var direction = (AtmosDirection) (1 << k);
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue; if (!otherTile.AdjacentBits.IsFlagSet(direction))
continue;
if (giver.MonstermosInfo.MoleDelta <= 0)
break; // We're done here now. Let's not do more work than needed.
var otherTile2 = otherTile.AdjacentTiles[k]; var otherTile2 = otherTile.AdjacentTiles[k];
if (giver.MonstermosInfo.MoleDelta <= 0) break; // We're done here now. Let's not do more work than needed.
if (otherTile2 == null || otherTile2.MonstermosInfo.LastQueueCycle != queueCycle) continue; if (otherTile2 == null || otherTile2.MonstermosInfo.LastQueueCycle != queueCycle) continue;
DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite())); DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
if (otherTile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) continue; if (otherTile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) continue;
@@ -332,7 +338,7 @@ namespace Content.Server.Atmos.EntitySystems
for (var i = 0; i < tileCount; i++) for (var i = 0; i < tileCount; i++)
{ {
var otherTile = _equalizeTiles[i]!; var otherTile = _equalizeTiles[i]!;
FinalizeEq(gridAtmosphere, otherTile, visuals); FinalizeEq(gridAtmosphere, otherTile, ent);
} }
for (var i = 0; i < tileCount; i++) for (var i = 0; i < tileCount; i++)
@@ -341,12 +347,17 @@ namespace Content.Server.Atmos.EntitySystems
for (var j = 0; j < Atmospherics.Directions; j++) for (var j = 0; j < Atmospherics.Directions; j++)
{ {
var direction = (AtmosDirection) (1 << j); var direction = (AtmosDirection) (1 << j);
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue; if (!otherTile.AdjacentBits.IsFlagSet(direction))
continue;
var otherTile2 = otherTile.AdjacentTiles[j]!; var otherTile2 = otherTile.AdjacentTiles[j]!;
if (otherTile2.AdjacentBits == 0) if (otherTile2.AdjacentBits == 0)
continue; continue;
DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite())); DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
if (otherTile2.Air != null && CompareExchange(otherTile2.Air, tile.Air) == GasCompareResult.NoExchange) continue; if (otherTile2.Air != null && CompareExchange(otherTile2.Air, tile.Air) == GasCompareResult.NoExchange)
continue;
AddActiveTile(gridAtmosphere, otherTile2); AddActiveTile(gridAtmosphere, otherTile2);
break; break;
} }
@@ -359,7 +370,10 @@ namespace Content.Server.Atmos.EntitySystems
Array.Clear(_equalizeQueue, 0, Atmospherics.MonstermosTileLimit); Array.Clear(_equalizeQueue, 0, Atmospherics.MonstermosTileLimit);
} }
private void ExplosivelyDepressurize(Entity<MapGridComponent, GridAtmosphereComponent> ent, TileAtmosphere tile, int cycleNum, GasTileOverlayComponent? visuals) private void ExplosivelyDepressurize(
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
TileAtmosphere tile,
int cycleNum)
{ {
// Check if explosive depressurization is enabled and if the tile is valid. // Check if explosive depressurization is enabled and if the tile is valid.
if (!MonstermosDepressurization || tile.Air == null) if (!MonstermosDepressurization || tile.Air == null)
@@ -368,7 +382,7 @@ namespace Content.Server.Atmos.EntitySystems
const int limit = Atmospherics.MonstermosHardTileLimit; const int limit = Atmospherics.MonstermosHardTileLimit;
var totalMolesRemoved = 0f; var totalMolesRemoved = 0f;
var (owner, mapGrid, gridAtmosphere) = ent; var (owner, gridAtmosphere, visuals, mapGrid, _) = ent;
var queueCycle = ++gridAtmosphere.EqualizationQueueCycleControl; var queueCycle = ++gridAtmosphere.EqualizationQueueCycleControl;
var tileCount = 0; var tileCount = 0;
@@ -388,20 +402,27 @@ namespace Content.Server.Atmos.EntitySystems
{ {
for (var j = 0; j < Atmospherics.Directions; j++) for (var j = 0; j < Atmospherics.Directions; j++)
{ {
var direction = (AtmosDirection) (1 << j);
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
var otherTile2 = otherTile.AdjacentTiles[j]; var otherTile2 = otherTile.AdjacentTiles[j];
if (otherTile2?.Air == null) continue; if (otherTile2?.Air == null)
DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite())); continue;
if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle) continue;
ConsiderFirelocks((owner, gridAtmosphere), otherTile, otherTile2, visuals, mapGrid); if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle)
continue;
var direction = (AtmosDirection) (1 << j);
DebugTools.Assert(otherTile.AdjacentBits.IsFlagSet(direction));
DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
ConsiderFirelocks(ent, otherTile, otherTile2);
// The firelocks might have closed on us. // The firelocks might have closed on us.
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue; if (!otherTile.AdjacentBits.IsFlagSet(direction))
continue;
otherTile2.MonstermosInfo = new MonstermosInfo { LastQueueCycle = queueCycle }; otherTile2.MonstermosInfo = new MonstermosInfo { LastQueueCycle = queueCycle };
_depressurizeTiles[tileCount++] = otherTile2; _depressurizeTiles[tileCount++] = otherTile2;
if (tileCount >= limit) break; if (tileCount >= limit)
break;
} }
} }
else else
@@ -437,13 +458,21 @@ namespace Content.Server.Atmos.EntitySystems
// Flood fill into this new direction // Flood fill into this new direction
var direction = (AtmosDirection) (1 << j); var direction = (AtmosDirection) (1 << j);
// Tiles in _depressurizeProgressionOrder cannot have null air. // Tiles in _depressurizeProgressionOrder cannot have null air.
if (!otherTile.AdjacentBits.IsFlagSet(direction) && !otherTile.Space) continue; if (!otherTile.AdjacentBits.IsFlagSet(direction) && !otherTile.Space)
continue;
var tile2 = otherTile.AdjacentTiles[j]; var tile2 = otherTile.AdjacentTiles[j];
if (tile2?.MonstermosInfo.LastQueueCycle != queueCycle) continue; if (tile2?.MonstermosInfo.LastQueueCycle != queueCycle)
continue;
DebugTools.Assert(tile2.AdjacentBits.IsFlagSet(direction.GetOpposite())); DebugTools.Assert(tile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
// If flood fill has already reached this tile, continue. // If flood fill has already reached this tile, continue.
if (tile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) continue; if (tile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow)
if(tile2.Space) continue; continue;
if(tile2.Space)
continue;
tile2.MonstermosInfo.CurrentTransferDirection = direction.GetOpposite(); tile2.MonstermosInfo.CurrentTransferDirection = direction.GetOpposite();
tile2.MonstermosInfo.CurrentTransferAmount = 0.0f; tile2.MonstermosInfo.CurrentTransferAmount = 0.0f;
tile2.PressureSpecificTarget = otherTile.PressureSpecificTarget; tile2.PressureSpecificTarget = otherTile.PressureSpecificTarget;
@@ -535,7 +564,7 @@ namespace Content.Server.Atmos.EntitySystems
_physics.ApplyAngularImpulse(owner, Vector2Helpers.Cross(tile.GridIndices - gridPhysics.LocalCenter, direction) * totalMolesRemoved, body: gridPhysics); _physics.ApplyAngularImpulse(owner, Vector2Helpers.Cross(tile.GridIndices - gridPhysics.LocalCenter, direction) * totalMolesRemoved, body: gridPhysics);
} }
if(tileCount > 10 && (totalMolesRemoved / tileCount) > 10) if (tileCount > 10 && (totalMolesRemoved / tileCount) > 10)
_adminLog.Add(LogType.ExplosiveDepressurization, LogImpact.High, _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}"); $"Explosive depressurization removed {totalMolesRemoved} moles from {tileCount} tiles starting from position {tile.GridIndices:position} on grid ID {tile.GridIndex:grid}");
@@ -544,36 +573,33 @@ namespace Content.Server.Atmos.EntitySystems
Array.Clear(_depressurizeProgressionOrder, 0, Atmospherics.MonstermosHardTileLimit * 2); Array.Clear(_depressurizeProgressionOrder, 0, Atmospherics.MonstermosHardTileLimit * 2);
} }
private void ConsiderFirelocks(Entity<GridAtmosphereComponent> ent, TileAtmosphere tile, TileAtmosphere other, GasTileOverlayComponent? visuals, MapGridComponent mapGrid) private void ConsiderFirelocks(
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
TileAtmosphere tile,
TileAtmosphere other)
{ {
var reconsiderAdjacent = false; 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)) if (_firelockQuery.TryGetComponent(entity, out var firelock))
continue; reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, 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)) if (_firelockQuery.TryGetComponent(entity, out var firelock))
continue; reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
} }
if (!reconsiderAdjacent) if (!reconsiderAdjacent)
return; return;
var (owner, gridAtmosphere) = ent; UpdateAdjacentTiles(ent, tile);
var tileEv = new UpdateAdjacentMethodEvent(owner, tile.GridIndices); UpdateAdjacentTiles(ent, other);
var otherEv = new UpdateAdjacentMethodEvent(owner, other.GridIndices); InvalidateVisuals(tile.GridIndex, tile.GridIndices, ent);
GridUpdateAdjacent(owner, gridAtmosphere, ref tileEv); InvalidateVisuals(other.GridIndex, other.GridIndices, ent);
GridUpdateAdjacent(owner, gridAtmosphere, ref otherEv);
InvalidateVisuals(tile.GridIndex, tile.GridIndices, visuals);
InvalidateVisuals(other.GridIndex, other.GridIndices, visuals);
} }
private void FinalizeEq(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, GasTileOverlayComponent? visuals) private void FinalizeEq(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, GasTileOverlayComponent? visuals)
@@ -642,7 +668,7 @@ namespace Content.Server.Atmos.EntitySystems
if (adj == null) if (adj == null)
{ {
var nonNull = tile.AdjacentTiles.Where(x => x != null).Count(); var nonNull = tile.AdjacentTiles.Where(x => x != null).Count();
Log.Error($"Encountered null adjacent tile in {nameof(AdjustEqMovement)}. Dir: {direction}, Tile: {tile.Tile}, non-null adj count: {nonNull}, Trace: {Environment.StackTrace}"); Log.Error($"Encountered null adjacent tile in {nameof(AdjustEqMovement)}. Dir: {direction}, Tile: ({tile.GridIndex}, {tile.GridIndices}), non-null adj count: {nonNull}, Trace: {Environment.StackTrace}");
return; return;
} }

View File

@@ -1,6 +1,5 @@
using Content.Server.Atmos.Components; using Content.Server.Atmos.Components;
using Content.Server.Atmos.Piping.Components; using Content.Server.Atmos.Piping.Components;
using Content.Server.NodeContainer.NodeGroups;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Components; using Content.Shared.Atmos.Components;
using Content.Shared.Maps; using Content.Shared.Maps;
@@ -8,6 +7,7 @@ using Robust.Shared.Map;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.Atmos.EntitySystems namespace Content.Server.Atmos.EntitySystems
{ {
@@ -30,118 +30,63 @@ namespace Content.Server.Atmos.EntitySystems
private int _currentRunAtmosphereIndex; private int _currentRunAtmosphereIndex;
private bool _simulationPaused; private bool _simulationPaused;
private readonly List<Entity<GridAtmosphereComponent>> _currentRunAtmosphere = new(); private TileAtmosphere GetOrNewTile(EntityUid owner, GridAtmosphereComponent atmosphere, Vector2i index)
{
var tile = atmosphere.Tiles.GetOrNew(index, out var existing);
if (existing)
return tile;
atmosphere.InvalidatedCoords.Add(index);
tile.GridIndex = owner;
tile.GridIndices = index;
return tile;
}
private readonly List<Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent>> _currentRunAtmosphere = new();
/// <summary> /// <summary>
/// Revalidates all invalid coordinates in a grid atmosphere. /// Revalidates all invalid coordinates in a grid atmosphere.
/// I.e., process any tiles that have had their airtight blockers modified.
/// </summary> /// </summary>
/// <param name="ent">The grid atmosphere in question.</param> /// <param name="ent">The grid atmosphere in question.</param>
/// <returns>Whether the process succeeded or got paused due to time constrains.</returns> /// <returns>Whether the process succeeded or got paused due to time constrains.</returns>
private bool ProcessRevalidate(Entity<GridAtmosphereComponent> ent, GasTileOverlayComponent? visuals) private bool ProcessRevalidate(Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent)
{ {
var (owner, atmosphere) = ent; if (ent.Comp4.MapUid == null)
if (!atmosphere.ProcessingPaused)
{ {
atmosphere.CurrentRunInvalidatedCoordinates.Clear(); Log.Error($"Attempted to process atmosphere on a map-less grid? Grid: {ToPrettyString(ent)}");
atmosphere.CurrentRunInvalidatedCoordinates.EnsureCapacity(atmosphere.InvalidatedCoords.Count); return true;
foreach (var tile in atmosphere.InvalidatedCoords)
{
atmosphere.CurrentRunInvalidatedCoordinates.Enqueue(tile);
}
atmosphere.InvalidatedCoords.Clear();
} }
if (!TryComp(owner, out MapGridComponent? mapGridComp)) var (uid, atmosphere, visuals, grid, xform) = ent;
return true; var volume = GetVolumeForTiles(grid);
TryComp(xform.MapUid, out MapAtmosphereComponent? mapAtmos);
var mapUid = _mapManager.GetMapEntityIdOrThrow(Transform(owner).MapID); if (!atmosphere.ProcessingPaused)
{
atmosphere.CurrentRunInvalidatedTiles.Clear();
atmosphere.CurrentRunInvalidatedTiles.EnsureCapacity(atmosphere.InvalidatedCoords.Count);
foreach (var indices in atmosphere.InvalidatedCoords)
{
var tile = GetOrNewTile(uid, atmosphere, indices);
atmosphere.CurrentRunInvalidatedTiles.Enqueue(tile);
var volume = GetVolumeForTiles(mapGridComp); // Update tile.IsSpace and tile.MapAtmosphere, and tile.AirtightData.
UpdateTileData(ent, mapAtmos, tile);
}
atmosphere.InvalidatedCoords.Clear();
if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime)
return false;
}
var number = 0; var number = 0;
while (atmosphere.CurrentRunInvalidatedCoordinates.TryDequeue(out var indices)) while (atmosphere.CurrentRunInvalidatedTiles.TryDequeue(out var tile))
{ {
if (!atmosphere.Tiles.TryGetValue(indices, out var tile)) DebugTools.Assert(atmosphere.Tiles.GetValueOrDefault(tile.GridIndices) == tile);
{ UpdateAdjacentTiles(ent, tile, activate: true);
tile = new TileAtmosphere(owner, indices, UpdateTileAir(ent, tile, volume);
new GasMixture(volume) { Temperature = Atmospherics.T20C }); InvalidateVisuals(uid, tile.GridIndices, visuals);
atmosphere.Tiles[indices] = tile;
}
var airBlockedEv = new IsTileAirBlockedMethodEvent(owner, indices, MapGridComponent:mapGridComp);
GridIsTileAirBlocked(owner, atmosphere, ref airBlockedEv);
var isAirBlocked = airBlockedEv.Result;
var oldBlocked = tile.BlockedAirflow;
var updateAdjacentEv = new UpdateAdjacentMethodEvent(owner, indices, mapGridComp);
GridUpdateAdjacent(owner, atmosphere, ref updateAdjacentEv);
// Blocked airflow changed, rebuild excited groups!
if (tile.Excited && tile.BlockedAirflow != oldBlocked)
{
RemoveActiveTile(atmosphere, tile);
}
// Call this instead of the grid method as the map has a say on whether the tile is space or not.
if ((!mapGridComp.TryGetTileRef(indices, out var t) || t.IsSpace(_tileDefinitionManager)) && !isAirBlocked)
{
tile.Air = GetTileMixture(null, mapUid, indices);
tile.MolesArchived = tile.Air != null ? new float[Atmospherics.AdjustedNumberOfGases] : null;
tile.Space = IsTileSpace(null, mapUid, indices, mapGridComp);
}
else if (isAirBlocked)
{
if (airBlockedEv.NoAir)
{
tile.Air = null;
tile.MolesArchived = null;
tile.ArchivedCycle = 0;
tile.LastShare = 0f;
tile.Hotspot = new Hotspot();
}
}
else
{
if (tile.Air == null && NeedsVacuumFixing(mapGridComp, indices))
{
var vacuumEv = new FixTileVacuumMethodEvent(owner, indices);
GridFixTileVacuum(owner, atmosphere, ref vacuumEv);
}
// Tile used to be space, but isn't anymore.
if (tile.Space || (tile.Air?.Immutable ?? false))
{
tile.Air = null;
tile.MolesArchived = null;
tile.ArchivedCycle = 0;
tile.LastShare = 0f;
tile.Space = false;
}
tile.Air ??= new GasMixture(volume){Temperature = Atmospherics.T20C};
tile.MolesArchived ??= new float[Atmospherics.AdjustedNumberOfGases];
}
// We activate the tile.
AddActiveTile(atmosphere, tile);
// TODO ATMOS: Query all the contents of this tile (like walls) and calculate the correct thermal conductivity and heat capacity
var tileDef = mapGridComp.TryGetTileRef(indices, out var tileRef)
? tileRef.GetContentTileDefinition(_tileDefinitionManager)
: null;
tile.ThermalConductivity = tileDef?.ThermalConductivity ?? 0.5f;
tile.HeatCapacity = tileDef?.HeatCapacity ?? float.PositiveInfinity;
InvalidateVisuals(owner, indices, visuals);
for (var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
var otherIndices = indices.Offset(direction);
if (atmosphere.Tiles.TryGetValue(otherIndices, out var otherTile))
AddActiveTile(atmosphere, otherTile);
}
if (number++ < InvalidCoordinatesLagCheckIterations) if (number++ < InvalidCoordinatesLagCheckIterations)
continue; continue;
@@ -149,12 +94,185 @@ namespace Content.Server.Atmos.EntitySystems
number = 0; number = 0;
// Process the rest next time. // Process the rest next time.
if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime) if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime)
{
return false; return false;
}
TrimDisconnectedMapTiles(ent);
return true;
}
/// <summary>
/// This method queued a tile and all of its neighbours up for processing by <see cref="TrimDisconnectedMapTiles"/>.
/// </summary>
public void QueueTileTrim(GridAtmosphereComponent atmos, TileAtmosphere tile)
{
if (!tile.TrimQueued)
{
tile.TrimQueued = true;
atmos.PossiblyDisconnectedTiles.Add(tile);
}
for (var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
var indices = tile.GridIndices.Offset(direction);
if (atmos.Tiles.TryGetValue(indices, out var adj)
&& adj.NoGridTile
&& !adj.TrimQueued)
{
adj.TrimQueued = true;
atmos.PossiblyDisconnectedTiles.Add(adj);
}
}
}
/// <summary>
/// Tiles in a <see cref="GridAtmosphereComponent"/> are either grid-tiles, or they they should be are tiles
/// adjacent to grid-tiles that represent the map's atmosphere. This method trims any map-tiles that are no longer
/// adjacent to any grid-tiles.
/// </summary>
private void TrimDisconnectedMapTiles(
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent)
{
var atmos = ent.Comp1;
foreach (var tile in atmos.PossiblyDisconnectedTiles)
{
tile.TrimQueued = false;
if (!tile.NoGridTile)
continue;
var connected = false;
for (var i = 0; i < Atmospherics.Directions; i++)
{
var indices = tile.GridIndices.Offset((AtmosDirection) (1 << i));
if (_map.TryGetTile(ent.Comp3, indices, out var gridTile) && !gridTile.IsEmpty)
{
connected = true;
break;
}
}
if (!connected)
{
RemoveActiveTile(atmos, tile);
atmos.Tiles.Remove(tile.GridIndices);
} }
} }
return true; atmos.PossiblyDisconnectedTiles.Clear();
}
/// <summary>
/// Checks whether a tile has a corresponding grid-tile, or whether it is a "map" tile. Also checks whether the
/// tile should be considered "space"
/// </summary>
private void UpdateTileData(
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
MapAtmosphereComponent? mapAtmos,
TileAtmosphere tile)
{
var idx = tile.GridIndices;
bool mapAtmosphere;
if (_map.TryGetTile(ent.Comp3, idx, out var gTile) && !gTile.IsEmpty)
{
var contentDef = (ContentTileDefinition) _tileDefinitionManager[gTile.TypeId];
mapAtmosphere = contentDef.MapAtmosphere;
tile.ThermalConductivity = contentDef.ThermalConductivity;
tile.HeatCapacity = contentDef.HeatCapacity;
tile.NoGridTile = false;
}
else
{
mapAtmosphere = true;
tile.ThermalConductivity = 0.5f;
tile.HeatCapacity = float.PositiveInfinity;
if (!tile.NoGridTile)
{
tile.NoGridTile = true;
// This tile just became a non-grid atmos tile.
// It, or one of its neighbours, might now be completely disconnected from the grid.
QueueTileTrim(ent.Comp1, tile);
}
}
UpdateAirtightData(ent.Owner, ent.Comp1, ent.Comp3, tile);
if (mapAtmosphere)
{
if (!tile.MapAtmosphere)
{
(tile.Air, tile.Space) = GetDefaultMapAtmosphere(mapAtmos);
tile.MapAtmosphere = true;
ent.Comp1.MapTiles.Add(tile);
}
DebugTools.AssertNotNull(tile.Air);
DebugTools.Assert(tile.Air?.Immutable ?? false);
return;
}
if (!tile.MapAtmosphere)
return;
// Tile used to be exposed to the map's atmosphere, but isn't anymore.
RemoveMapAtmos(ent.Comp1, tile);
}
private void RemoveMapAtmos(GridAtmosphereComponent atmos, TileAtmosphere tile)
{
DebugTools.Assert(tile.MapAtmosphere);
DebugTools.AssertNotNull(tile.Air);
DebugTools.Assert(tile.Air?.Immutable ?? false);
tile.MapAtmosphere = false;
atmos.MapTiles.Remove(tile);
tile.Air = null;
Array.Clear(tile.MolesArchived);
tile.ArchivedCycle = 0;
tile.LastShare = 0f;
tile.Space = false;
}
/// <summary>
/// Check whether a grid-tile should have an air mixture, and give it one if it doesn't already have one.
/// </summary>
private void UpdateTileAir(
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
TileAtmosphere tile,
float volume)
{
if (tile.MapAtmosphere)
{
DebugTools.AssertNotNull(tile.Air);
DebugTools.Assert(tile.Air?.Immutable ?? false);
return;
}
var data = tile.AirtightData;
var fullyBlocked = data.BlockedDirections == AtmosDirection.All;
if (fullyBlocked && data.NoAirWhenBlocked)
{
if (tile.Air == null)
return;
tile.Air = null;
Array.Clear(tile.MolesArchived);
tile.ArchivedCycle = 0;
tile.LastShare = 0f;
tile.Hotspot = new Hotspot();
return;
}
if (tile.Air != null)
return;
tile.Air = new GasMixture(volume){Temperature = Atmospherics.T20C};
if (data.FixVacuum)
GridFixTileVacuum(ent, tile, volume);
} }
private void QueueRunTiles( private void QueueRunTiles(
@@ -170,19 +288,16 @@ namespace Content.Server.Atmos.EntitySystems
} }
} }
private bool ProcessTileEqualize(Entity<GridAtmosphereComponent> ent, GasTileOverlayComponent? visuals) private bool ProcessTileEqualize(Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent)
{ {
var (uid, atmosphere) = ent; var atmosphere = ent.Comp1;
if (!atmosphere.ProcessingPaused) if (!atmosphere.ProcessingPaused)
QueueRunTiles(atmosphere.CurrentRunTiles, atmosphere.ActiveTiles); 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; var number = 0;
while (atmosphere.CurrentRunTiles.TryDequeue(out var tile)) while (atmosphere.CurrentRunTiles.TryDequeue(out var tile))
{ {
EqualizePressureInZone((uid, mapGridComp, atmosphere), tile, atmosphere.UpdateCounter, visuals); EqualizePressureInZone(ent, tile, atmosphere.UpdateCounter);
if (number++ < LagCheckIterations) if (number++ < LagCheckIterations)
continue; continue;
@@ -198,7 +313,7 @@ namespace Content.Server.Atmos.EntitySystems
return true; return true;
} }
private bool ProcessActiveTiles(GridAtmosphereComponent atmosphere, GasTileOverlayComponent? visuals) private bool ProcessActiveTiles(GridAtmosphereComponent atmosphere, GasTileOverlayComponent visuals)
{ {
if(!atmosphere.ProcessingPaused) if(!atmosphere.ProcessingPaused)
QueueRunTiles(atmosphere.CurrentRunTiles, atmosphere.ActiveTiles); QueueRunTiles(atmosphere.CurrentRunTiles, atmosphere.ActiveTiles);
@@ -240,11 +355,11 @@ namespace Content.Server.Atmos.EntitySystems
excitedGroup.BreakdownCooldown++; excitedGroup.BreakdownCooldown++;
excitedGroup.DismantleCooldown++; excitedGroup.DismantleCooldown++;
if(excitedGroup.BreakdownCooldown > Atmospherics.ExcitedGroupBreakdownCycles) if (excitedGroup.BreakdownCooldown > Atmospherics.ExcitedGroupBreakdownCycles)
ExcitedGroupSelfBreakdown(gridAtmosphere, excitedGroup); ExcitedGroupSelfBreakdown(gridAtmosphere, excitedGroup);
else if (excitedGroup.DismantleCooldown > Atmospherics.ExcitedGroupsDismantleCycles)
else if(excitedGroup.DismantleCooldown > Atmospherics.ExcitedGroupsDismantleCycles) DeactivateGroupTiles(gridAtmosphere, excitedGroup);
ExcitedGroupDismantle(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) if (number++ < LagCheckIterations)
continue; continue;
@@ -435,10 +550,10 @@ namespace Content.Server.Atmos.EntitySystems
_currentRunAtmosphereIndex = 0; _currentRunAtmosphereIndex = 0;
_currentRunAtmosphere.Clear(); _currentRunAtmosphere.Clear();
var query = EntityQueryEnumerator<GridAtmosphereComponent>(); var query = EntityQueryEnumerator<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var grid)) while (query.MoveNext(out var uid, out var atmos, out var overlay, out var grid, out var xform ))
{ {
_currentRunAtmosphere.Add((uid, grid)); _currentRunAtmosphere.Add((uid, atmos, overlay, grid, xform));
} }
} }
@@ -448,8 +563,7 @@ namespace Content.Server.Atmos.EntitySystems
for (; _currentRunAtmosphereIndex < _currentRunAtmosphere.Count; _currentRunAtmosphereIndex++) for (; _currentRunAtmosphereIndex < _currentRunAtmosphere.Count; _currentRunAtmosphereIndex++)
{ {
var ent = _currentRunAtmosphere[_currentRunAtmosphereIndex]; var ent = _currentRunAtmosphere[_currentRunAtmosphereIndex];
var (owner, atmosphere) = ent; var (owner, atmosphere, visuals, grid, xform) = ent;
TryComp(owner, out GasTileOverlayComponent? visuals);
if (!TryComp(owner, out TransformComponent? x) if (!TryComp(owner, out TransformComponent? x)
|| x.MapUid == null || x.MapUid == null
@@ -474,13 +588,14 @@ namespace Content.Server.Atmos.EntitySystems
switch (atmosphere.State) switch (atmosphere.State)
{ {
case AtmosphereProcessingState.Revalidate: case AtmosphereProcessingState.Revalidate:
if (!ProcessRevalidate(ent, visuals)) if (!ProcessRevalidate(ent))
{ {
atmosphere.ProcessingPaused = true; atmosphere.ProcessingPaused = true;
return; return;
} }
atmosphere.ProcessingPaused = false; atmosphere.ProcessingPaused = false;
// Next state depends on whether monstermos equalization is enabled or not. // 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. // 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. // Therefore, a change to this CVar might only be applied after that step is over.
@@ -489,7 +604,7 @@ namespace Content.Server.Atmos.EntitySystems
: AtmosphereProcessingState.ActiveTiles; : AtmosphereProcessingState.ActiveTiles;
continue; continue;
case AtmosphereProcessingState.TileEqualize: case AtmosphereProcessingState.TileEqualize:
if (!ProcessTileEqualize(ent, visuals)) if (!ProcessTileEqualize(ent))
{ {
atmosphere.ProcessingPaused = true; atmosphere.ProcessingPaused = true;
return; return;
@@ -499,7 +614,7 @@ namespace Content.Server.Atmos.EntitySystems
atmosphere.State = AtmosphereProcessingState.ActiveTiles; atmosphere.State = AtmosphereProcessingState.ActiveTiles;
continue; continue;
case AtmosphereProcessingState.ActiveTiles: case AtmosphereProcessingState.ActiveTiles:
if (!ProcessActiveTiles(atmosphere, visuals)) if (!ProcessActiveTiles(ent, ent))
{ {
atmosphere.ProcessingPaused = true; atmosphere.ProcessingPaused = true;
return; return;
@@ -520,7 +635,7 @@ namespace Content.Server.Atmos.EntitySystems
atmosphere.State = AtmosphereProcessingState.HighPressureDelta; atmosphere.State = AtmosphereProcessingState.HighPressureDelta;
continue; continue;
case AtmosphereProcessingState.HighPressureDelta: case AtmosphereProcessingState.HighPressureDelta:
if (!ProcessHighPressureDelta(ent)) if (!ProcessHighPressureDelta((ent, ent)))
{ {
atmosphere.ProcessingPaused = true; atmosphere.ProcessingPaused = true;
return; return;

View File

@@ -1,5 +1,6 @@
using Content.Server.Atmos.Components; using Content.Server.Atmos.Components;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Robust.Shared.Map.Components;
namespace Content.Server.Atmos.EntitySystems namespace Content.Server.Atmos.EntitySystems
{ {
@@ -12,7 +13,8 @@ namespace Content.Server.Atmos.EntitySystems
for(var i = 0; i < Atmospherics.Directions; i++) for(var i = 0; i < Atmospherics.Directions; i++)
{ {
var direction = (AtmosDirection) (1 << i); var direction = (AtmosDirection) (1 << i);
if (!directions.IsFlagSet(direction)) continue; if (!directions.IsFlagSet(direction))
continue;
var adjacent = tile.AdjacentTiles[direction.ToIndex()]; var adjacent = tile.AdjacentTiles[direction.ToIndex()];
@@ -92,7 +94,9 @@ namespace Content.Server.Atmos.EntitySystems
{ {
if (tile.Air == null) if (tile.Air == null)
{ {
if (other.Tile != null) // TODO ATMOS: why does this need to check if a tile exists if it doesn't use the tile?
if (TryComp<MapGridComponent>(other.GridIndex, out var grid)
&& _mapSystem.TryGetTileRef(other.GridIndex, grid, other.GridIndices, out var _))
{ {
TemperatureShareOpenToSolid(other, tile); TemperatureShareOpenToSolid(other, tile);
} }

View File

@@ -41,20 +41,6 @@ public partial class AtmosphereSystem
_gasTileOverlaySystem.Invalidate(gridUid, tile, comp); _gasTileOverlaySystem.Invalidate(gridUid, tile, comp);
} }
public bool NeedsVacuumFixing(MapGridComponent mapGrid, Vector2i indices)
{
var value = false;
var enumerator = GetObstructingComponentsEnumerator(mapGrid, indices);
while (enumerator.MoveNext(out var airtight))
{
value |= airtight.FixVacuum;
}
return value;
}
/// <summary> /// <summary>
/// Gets the volume in liters for a number of tiles, on a specific grid. /// Gets the volume in liters for a number of tiles, on a specific grid.
/// </summary> /// </summary>
@@ -66,34 +52,44 @@ public partial class AtmosphereSystem
return Atmospherics.CellVolume * mapGrid.TileSize * tiles; return Atmospherics.CellVolume * mapGrid.TileSize * tiles;
} }
/// <summary> public readonly record struct AirtightData(AtmosDirection BlockedDirections, bool NoAirWhenBlocked,
/// Gets all obstructing <see cref="AirtightComponent"/> instances in a specific tile. bool FixVacuum);
/// </summary>
/// <param name="mapGrid">The grid where to get the tile.</param>
/// <param name="tile">The indices of the tile.</param>
/// <returns>The enumerator for the airtight components.</returns>
public AtmosObstructionEnumerator GetObstructingComponentsEnumerator(MapGridComponent mapGrid, Vector2i tile)
{
var ancEnumerator = mapGrid.GetAnchoredEntitiesEnumerator(tile);
var airQuery = GetEntityQuery<AirtightComponent>();
var enumerator = new AtmosObstructionEnumerator(ancEnumerator, airQuery); private void UpdateAirtightData(EntityUid uid, GridAtmosphereComponent atmos, MapGridComponent grid, TileAtmosphere tile)
return enumerator; {
var oldBlocked = tile.AirtightData.BlockedDirections;
tile.AirtightData = tile.NoGridTile
? default
: GetAirtightData(uid, grid, tile.GridIndices);
if (tile.AirtightData.BlockedDirections != oldBlocked && tile.ExcitedGroup != null)
ExcitedGroupDispose(atmos, tile.ExcitedGroup);
} }
private AtmosDirection GetBlockedDirections(MapGridComponent mapGrid, Vector2i indices) private AirtightData GetAirtightData(EntityUid uid, MapGridComponent grid, Vector2i tile)
{ {
var value = AtmosDirection.Invalid; var blockedDirs = AtmosDirection.Invalid;
var noAirWhenBlocked = false;
var fixVacuum = false;
var enumerator = GetObstructingComponentsEnumerator(mapGrid, indices); foreach (var ent in _map.GetAnchoredEntities(uid, grid, tile))
while (enumerator.MoveNext(out var airtight))
{ {
if(airtight.AirBlocked) if (!_airtightQuery.TryGetComponent(ent, out var airtight))
value |= airtight.AirBlockedDirection; continue;
if(!airtight.AirBlocked)
continue;
blockedDirs |= airtight.AirBlockedDirection;
noAirWhenBlocked |= airtight.NoAirWhenFullyAirBlocked;
fixVacuum |= airtight.FixVacuum;
if (blockedDirs == AtmosDirection.All && noAirWhenBlocked && fixVacuum)
break;
} }
return value; return new AirtightData(blockedDirs, noAirWhenBlocked, fixVacuum);
} }
/// <summary> /// <summary>

View File

@@ -4,6 +4,7 @@ using Content.Server.Body.Systems;
using Content.Server.Fluids.EntitySystems; using Content.Server.Fluids.EntitySystems;
using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.EntitySystems;
using Content.Shared.Atmos.EntitySystems; using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Doors.Components;
using Content.Shared.Maps; using Content.Shared.Maps;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
@@ -40,6 +41,9 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
private const float ExposedUpdateDelay = 1f; private const float ExposedUpdateDelay = 1f;
private float _exposedTimer = 0f; private float _exposedTimer = 0f;
private EntityQuery<GridAtmosphereComponent> _atmosQuery;
private EntityQuery<AirtightComponent> _airtightQuery;
private EntityQuery<FirelockComponent> _firelockQuery;
private HashSet<EntityUid> _entSet = new(); private HashSet<EntityUid> _entSet = new();
public override void Initialize() public override void Initialize()
@@ -55,6 +59,9 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
InitializeGridAtmosphere(); InitializeGridAtmosphere();
InitializeMap(); InitializeMap();
_atmosQuery = GetEntityQuery<GridAtmosphereComponent>();
_airtightQuery = GetEntityQuery<AirtightComponent>();
_firelockQuery = GetEntityQuery<FirelockComponent>();
SubscribeLocalEvent<TileChangedEvent>(OnTileChanged); SubscribeLocalEvent<TileChangedEvent>(OnTileChanged);

View File

@@ -27,8 +27,12 @@ public sealed class AutomaticAtmosSystem : EntitySystem
// Also, these calls are surprisingly slow. // Also, these calls are surprisingly slow.
// TODO: Make tiledefmanager cache the IsSpace property, and turn this lookup-through-two-interfaces into // 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. // 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)) _atmosphereSystem.HasAtmosphere(ev.Entity))
return; return;

View File

@@ -260,13 +260,13 @@ namespace Content.Server.Atmos.EntitySystems
{ {
var gas = _atmo.GetGas(i); var gas = _atmo.GetGas(i);
if (mixture?.Moles[i] <= UIMinMoles) if (mixture?[i] <= UIMinMoles)
continue; continue;
if (mixture != null) if (mixture != null)
{ {
var gasName = Loc.GetString(gas.Name); 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));
} }
} }

View File

@@ -172,7 +172,7 @@ namespace Content.Server.Atmos.EntitySystems
{ {
var id = VisibleGasId[i]; var id = VisibleGasId[i];
var gas = _atmosphereSystem.GetGas(id); var gas = _atmosphereSystem.GetGas(id);
var moles = mixture?.Moles[id] ?? 0f; var moles = mixture?[id] ?? 0f;
ref var opacity = ref data.Opacity[i]; ref var opacity = ref data.Opacity[i];
if (moles < gas.GasMolesVisible) if (moles < gas.GasMolesVisible)
@@ -217,13 +217,13 @@ namespace Content.Server.Atmos.EntitySystems
oldData = new GasOverlayData(tile.Hotspot.State, oldData.Opacity); 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++) for (var i = 0; i < VisibleGasId.Length; i++)
{ {
var id = VisibleGasId[i]; var id = VisibleGasId[i];
var gas = _atmosphereSystem.GetGas(id); var gas = _atmosphereSystem.GetGas(id);
var moles = tile.Air.Moles[id]; var moles = tile.Air[id];
ref var oldOpacity = ref oldData.Opacity[i]; ref var oldOpacity = ref oldData.Opacity[i];
if (moles < gas.GasMolesVisible) if (moles < gas.GasMolesVisible)

View File

@@ -3,6 +3,7 @@ using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Content.Server.Atmos.Reactions; using Content.Server.Atmos.Reactions;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.EntitySystems;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -17,11 +18,13 @@ namespace Content.Server.Atmos
{ {
public static GasMixture SpaceGas => new() {Volume = Atmospherics.CellVolume, Temperature = Atmospherics.TCMB, Immutable = true}; 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. // No access, to ensure immutable mixtures are never accidentally mutated.
[DataField("moles")] [Access(typeof(SharedAtmosphereSystem), typeof(SharedAtmosDebugOverlaySystem), Other = AccessPermissions.None)]
[ViewVariables(VVAccess.ReadWrite)] [DataField]
public float[] Moles = new float[Atmospherics.AdjustedNumberOfGases]; public float[] Moles = new float[Atmospherics.AdjustedNumberOfGases];
public float this[int gas] => Moles[gas];
[DataField("temperature")] [DataField("temperature")]
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
private float _temperature = Atmospherics.TCMB; private float _temperature = Atmospherics.TCMB;
@@ -80,6 +83,19 @@ namespace Content.Server.Atmos
Volume = volume; 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MarkImmutable() public void MarkImmutable()
{ {
@@ -117,15 +133,16 @@ namespace Content.Server.Atmos
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AdjustMoles(int gasId, float quantity) public void AdjustMoles(int gasId, float quantity)
{ {
if (!Immutable) if (Immutable)
{ return;
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 if (!float.IsFinite(quantity))
// clamp here, the caller always has to call GetMoles(), clamp, then SetMoles(). throw new ArgumentException($"Invalid quantity \"{quantity}\" specified!", nameof(quantity));
Moles[gasId] = MathF.Max(Moles[gasId] + quantity, 0);
} // 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -163,7 +180,8 @@ namespace Content.Server.Atmos
{ {
var moles = Moles[i]; var moles = Moles[i];
var otherMoles = removed.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; Moles[i] = 0;
if (otherMoles < Atmospherics.GasMinMoles || float.IsNaN(otherMoles)) if (otherMoles < Atmospherics.GasMinMoles || float.IsNaN(otherMoles))
@@ -202,6 +220,9 @@ namespace Content.Server.Atmos
void ISerializationHooks.AfterDeserialization() void ISerializationHooks.AfterDeserialization()
{ {
// ISerializationHooks is obsolete.
// TODO add fixed-length-array serializer
// The arrays MUST have a specific length. // The arrays MUST have a specific length.
Array.Resize(ref Moles, Atmospherics.AdjustedNumberOfGases); Array.Resize(ref Moles, Atmospherics.AdjustedNumberOfGases);
} }
@@ -229,8 +250,12 @@ namespace Content.Server.Atmos
public bool Equals(GasMixture? other) public bool Equals(GasMixture? other)
{ {
if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other))
if (ReferenceEquals(this, other)) return true; return true;
if (ReferenceEquals(null, other))
return false;
return Moles.SequenceEqual(other.Moles) return Moles.SequenceEqual(other.Moles)
&& _temperature.Equals(other._temperature) && _temperature.Equals(other._temperature)
&& ReactionResults.SequenceEqual(other.ReactionResults) && ReactionResults.SequenceEqual(other.ReactionResults)
@@ -258,11 +283,13 @@ namespace Content.Server.Atmos
public GasMixture Clone() public GasMixture Clone()
{ {
if (Immutable)
return this;
var newMixture = new GasMixture() var newMixture = new GasMixture()
{ {
Moles = (float[])Moles.Clone(), Moles = (float[])Moles.Clone(),
_temperature = _temperature, _temperature = _temperature,
Immutable = Immutable,
Volume = Volume, Volume = Volume,
}; };
return newMixture; return newMixture;

View File

@@ -136,7 +136,7 @@ public sealed class GasCanisterSystem : EntitySystem
for (int i = 0; i < containedGasArray.Length; i++) 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)}]"); _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)}]");

View File

@@ -45,7 +45,7 @@ public sealed class GasCondenserSystem : EntitySystem
var removed = inlet.Air.Remove(molesToConvert); var removed = inlet.Air.Remove(molesToConvert);
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
{ {
var moles = removed.Moles[i]; var moles = removed[i];
if (moles <= 0) if (moles <= 0)
continue; continue;

View File

@@ -183,13 +183,7 @@ public sealed partial class TileAtmosCollectionSerializer : ITypeSerializer<Dict
target.Clear(); target.Clear();
foreach (var (key, val) in source) foreach (var (key, val) in source)
{ {
target.Add(key, target.Add(key, new TileAtmosphere(val));
new TileAtmosphere(
val.GridIndex,
val.GridIndices,
val.Air?.Clone(),
val.Air?.Immutable ?? false,
val.Space));
} }
} }
} }

View File

@@ -1,3 +1,4 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Maps; using Content.Shared.Maps;
@@ -51,6 +52,10 @@ namespace Content.Server.Atmos
[ViewVariables] [ViewVariables]
public readonly TileAtmosphere?[] AdjacentTiles = new TileAtmosphere[Atmospherics.Directions]; public readonly TileAtmosphere?[] AdjacentTiles = new TileAtmosphere[Atmospherics.Directions];
/// <summary>
/// Neighbouring tiles to which air can flow. This is a combination of this tile's unblocked direction, and the
/// unblocked directions on adjacent tiles.
/// </summary>
[ViewVariables] [ViewVariables]
public AtmosDirection AdjacentBits = AtmosDirection.Invalid; public AtmosDirection AdjacentBits = AtmosDirection.Invalid;
@@ -72,10 +77,7 @@ namespace Content.Server.Atmos
public EntityUid GridIndex { get; set; } public EntityUid GridIndex { get; set; }
[ViewVariables] [ViewVariables]
public TileRef? Tile => GridIndices.GetTileRef(GridIndex); public Vector2i GridIndices;
[ViewVariables]
public Vector2i GridIndices { get; }
[ViewVariables] [ViewVariables]
public ExcitedGroup? ExcitedGroup { get; set; } public ExcitedGroup? ExcitedGroup { get; set; }
@@ -92,7 +94,7 @@ namespace Content.Server.Atmos
public float LastShare; public float LastShare;
[ViewVariables] [ViewVariables]
public float[]? MolesArchived; public readonly float[] MolesArchived = new float[Atmospherics.AdjustedNumberOfGases];
GasMixture IGasMixtureHolder.Air GasMixture IGasMixtureHolder.Air
{ {
@@ -103,8 +105,31 @@ namespace Content.Server.Atmos
[ViewVariables] [ViewVariables]
public float MaxFireTemperatureSustained { get; set; } public float MaxFireTemperatureSustained { get; set; }
/// <summary>
/// If true, then this tile is directly exposed to the map's atmosphere, either because the grid has no tile at
/// this position, or because the tile type is not airtight.
/// </summary>
[ViewVariables] [ViewVariables]
public AtmosDirection BlockedAirflow { get; set; } = AtmosDirection.Invalid; public bool MapAtmosphere;
/// <summary>
/// If true, this tile does not actually exist on the grid, it only exists to represent the map's atmosphere for
/// adjacent grid tiles.
/// </summary>
[ViewVariables]
public bool NoGridTile;
/// <summary>
/// If true, this tile is queued for processing in <see cref="GridAtmosphereComponent.PossiblyDisconnectedTiles"/>
/// </summary>
[ViewVariables]
public bool TrimQueued;
/// <summary>
/// Cached information about airtight entities on this tile. This gets updated anytime a tile gets invalidated
/// (i.e., gets added to <see cref="GridAtmosphereComponent.InvalidatedCoords"/>).
/// </summary>
public AtmosphereSystem.AirtightData AirtightData;
public TileAtmosphere(EntityUid gridIndex, Vector2i gridIndices, GasMixture? mixture = null, bool immutable = false, bool space = false) public TileAtmosphere(EntityUid gridIndex, Vector2i gridIndices, GasMixture? mixture = null, bool immutable = false, bool space = false)
{ {
@@ -112,10 +137,24 @@ namespace Content.Server.Atmos
GridIndices = gridIndices; GridIndices = gridIndices;
Air = mixture; Air = mixture;
Space = space; Space = space;
MolesArchived = Air != null ? new float[Atmospherics.AdjustedNumberOfGases] : null;
if(immutable) if(immutable)
Air?.MarkImmutable(); 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()
{
}
} }
} }

View File

@@ -77,7 +77,7 @@ public sealed class LungSystem : EntitySystem
foreach (var gas in Enum.GetValues<Gas>()) foreach (var gas in Enum.GetValues<Gas>())
{ {
var i = (int) gas; var i = (int) gas;
var moles = lung.Air.Moles[i]; var moles = lung.Air[i];
if (moles <= 0) if (moles <= 0)
continue; continue;
var reagent = _atmosphereSystem.GasReagents[i]; var reagent = _atmosphereSystem.GasReagents[i];

View File

@@ -16,12 +16,15 @@ public sealed partial class ModifyLungGas : ReagentEffect
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
if (args.EntityManager.TryGetComponent<LungComponent>(args.OrganEntity, out var lung)) if (!args.EntityManager.TryGetComponent<LungComponent>(args.OrganEntity, out var lung))
return;
foreach (var (gas, ratio) in _ratios)
{ {
foreach (var (gas, ratio) in _ratios) var quantity = ratio * args.Quantity.Float() / Atmospherics.BreathMolesToReagentMultiplier;
{ if (quantity < 0)
lung.Air.Moles[(int) gas] += (ratio * args.Quantity.Float()) / Atmospherics.BreathMolesToReagentMultiplier; quantity = Math.Max(quantity, -lung.Air[(int)gas]);
} lung.Air.AdjustMoles(gas, quantity);
} }
} }
} }

View File

@@ -3,6 +3,7 @@ using Content.Shared.Examine;
using Content.Shared.Maps; using Content.Shared.Maps;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Server.Construction.Conditions namespace Content.Server.Construction.Conditions
@@ -49,7 +50,15 @@ namespace Content.Server.Construction.Conditions
var transformSys = entityManager.System<SharedTransformSystem>(); var transformSys = entityManager.System<SharedTransformSystem>();
var indices = transform.Coordinates.ToVector2i(entityManager, IoCManager.Resolve<IMapManager>(), transformSys); var indices = transform.Coordinates.ToVector2i(entityManager, IoCManager.Resolve<IMapManager>(), transformSys);
var lookup = entityManager.EntitySysManager.GetEntitySystem<EntityLookupSystem>(); var lookup = entityManager.EntitySysManager.GetEntitySystem<EntityLookupSystem>();
var entities = indices.GetEntitiesInTile(transform.GridUid.Value, LookupFlags.Approximate | LookupFlags.Static, lookup);
if (!entityManager.TryGetComponent<MapGridComponent>(transform.GridUid.Value, out var grid))
return !HasEntity;
if (!entityManager.System<SharedMapSystem>().TryGetTileRef(transform.GridUid.Value, grid, indices, out var tile))
return !HasEntity;
var entities = tile.GetEntitiesInTile(LookupFlags.Approximate | LookupFlags.Static, lookup);
foreach (var ent in entities) foreach (var ent in entities)
{ {

View File

@@ -493,7 +493,7 @@ public sealed partial class ExplosionSystem
if (_tileDefinitionManager[tileRef.Tile.TypeId] is not ContentTileDefinition tileDef) if (_tileDefinitionManager[tileRef.Tile.TypeId] is not ContentTileDefinition tileDef)
return; return;
if (tileDef.IsSpace) if (tileDef.MapAtmosphere)
canCreateVacuum = true; // is already a vacuum. canCreateVacuum = true; // is already a vacuum.
int tileBreakages = 0; int tileBreakages = 0;
@@ -509,7 +509,7 @@ public sealed partial class ExplosionSystem
if (_tileDefinitionManager[tileDef.BaseTurf] is not ContentTileDefinition newDef) if (_tileDefinitionManager[tileDef.BaseTurf] is not ContentTileDefinition newDef)
break; break;
if (newDef.IsSpace && !canCreateVacuum) if (newDef.MapAtmosphere && !canCreateVacuum)
break; break;
tileDef = newDef; tileDef = newDef;

View File

@@ -1001,20 +1001,13 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
light.AmbientLightColor = Color.FromHex("#D8B059"); light.AmbientLightColor = Color.FromHex("#D8B059");
Dirty(mapUid, light, metadata); Dirty(mapUid, light, metadata);
// Atmos
var atmos = EnsureComp<MapAtmosphereComponent>(mapUid);
var moles = new float[Atmospherics.AdjustedNumberOfGases]; var moles = new float[Atmospherics.AdjustedNumberOfGases];
moles[(int) Gas.Oxygen] = 21.824779f; moles[(int) Gas.Oxygen] = 21.824779f;
moles[(int) Gas.Nitrogen] = 82.10312f; moles[(int) Gas.Nitrogen] = 82.10312f;
var mixture = new GasMixture(2500) var mixture = new GasMixture(moles, Atmospherics.T20C);
{
Temperature = 293.15f,
Moles = moles,
};
_atmos.SetMapAtmosphere(mapUid, false, mixture, atmos); _atmos.SetMapAtmosphere(mapUid, false, mixture);
} }
/// <summary> /// <summary>

View File

@@ -37,7 +37,7 @@ public sealed class GasPowerReceiverSystem : EntitySystem
if (pipe.Air.Temperature <= component.MaxTemperature) if (pipe.Air.Temperature <= component.MaxTemperature)
{ {
// we have enough gas, so we consume it and are powered // 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); pipe.Air.AdjustMoles(component.TargetGas, -component.MolesConsumedSec * timeDelta);
SetPowered(uid, component, true); SetPowered(uid, component, true);

View File

@@ -125,11 +125,7 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
air.Gases.CopyTo(moles, 0); air.Gases.CopyTo(moles, 0);
var atmos = _entManager.EnsureComponent<MapAtmosphereComponent>(mapUid); var atmos = _entManager.EnsureComponent<MapAtmosphereComponent>(mapUid);
_entManager.System<AtmosphereSystem>().SetMapSpace(mapUid, air.Space, atmos); _entManager.System<AtmosphereSystem>().SetMapSpace(mapUid, air.Space, atmos);
_entManager.System<AtmosphereSystem>().SetMapGasMixture(mapUid, new GasMixture(2500) _entManager.System<AtmosphereSystem>().SetMapGasMixture(mapUid, new GasMixture(moles, mission.Temperature), atmos);
{
Temperature = mission.Temperature,
Moles = moles,
}, atmos);
if (mission.Color != null) if (mission.Color != null)
{ {

View File

@@ -18,6 +18,7 @@ namespace Content.Server.Spreader;
/// </summary> /// </summary>
public sealed class SpreaderSystem : EntitySystem public sealed class SpreaderSystem : EntitySystem
{ {
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly SharedMapSystem _map = 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 // TODO PERFORMANCE Assign each prototype to an index and convert dictionary to array
private readonly Dictionary<EntityUid, Dictionary<string, int>> _gridUpdates = []; private readonly Dictionary<EntityUid, Dictionary<string, int>> _gridUpdates = [];
private EntityQuery<EdgeSpreaderComponent> _query;
public const float SpreadCooldownSeconds = 1; public const float SpreadCooldownSeconds = 1;
[ValidatePrototypeId<TagPrototype>] [ValidatePrototypeId<TagPrototype>]
@@ -47,6 +50,8 @@ public sealed class SpreaderSystem : EntitySystem
SubscribeLocalEvent<EdgeSpreaderComponent, EntityTerminatingEvent>(OnTerminating); SubscribeLocalEvent<EdgeSpreaderComponent, EntityTerminatingEvent>(OnTerminating);
SetupPrototypes(); SetupPrototypes();
_query = GetEntityQuery<EdgeSpreaderComponent>();
} }
private void OnPrototypeReload(PrototypesReloadedEventArgs obj) private void OnPrototypeReload(PrototypesReloadedEventArgs obj)
@@ -66,13 +71,7 @@ public sealed class SpreaderSystem : EntitySystem
private void OnAirtightChanged(ref AirtightChanged ev) private void OnAirtightChanged(ref AirtightChanged ev)
{ {
var neighbors = GetSpreadableNeighbors(ev.Entity, ev.Airtight, ev.Position); ActivateSpreadableNeighbors(ev.Entity, ev.Position);
foreach (var neighbor in neighbors)
{
if (!TerminatingOrDeleted(neighbor))
EnsureComp<ActiveEdgeSpreaderComponent>(neighbor);
}
} }
private void OnGridInit(GridInitializeEvent ev) private void OnGridInit(GridInitializeEvent ev)
@@ -82,13 +81,7 @@ public sealed class SpreaderSystem : EntitySystem
private void OnTerminating(Entity<EdgeSpreaderComponent> entity, ref EntityTerminatingEvent args) private void OnTerminating(Entity<EdgeSpreaderComponent> entity, ref EntityTerminatingEvent args)
{ {
var neighbors = GetSpreadableNeighbors(entity); ActivateSpreadableNeighbors(entity);
foreach (var neighbor in neighbors)
{
if (!TerminatingOrDeleted(neighbor))
EnsureComp<ActiveEdgeSpreaderComponent>(neighbor);
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -254,8 +247,7 @@ public sealed class SpreaderSystem : EntitySystem
if (!_map.TryGetTileRef(neighborEnt, neighborGrid, neighborPos, out var tileRef) || tileRef.Tile.IsEmpty) if (!_map.TryGetTileRef(neighborEnt, neighborGrid, neighborPos, out var tileRef) || tileRef.Tile.IsEmpty)
continue; continue;
var directionEnumerator = var directionEnumerator = _map.GetAnchoredEntitiesEnumerator(neighborEnt, neighborGrid, neighborPos);
_map.GetAnchoredEntitiesEnumerator(neighborEnt, neighborGrid, neighborPos);
var occupied = false; var occupied = false;
while (directionEnumerator.MoveNext(out var ent)) while (directionEnumerator.MoveNext(out var ent))
@@ -277,8 +269,7 @@ public sealed class SpreaderSystem : EntitySystem
continue; continue;
var oldCount = occupiedTiles.Count; var oldCount = occupiedTiles.Count;
directionEnumerator = directionEnumerator = _map.GetAnchoredEntitiesEnumerator(neighborEnt, neighborGrid, neighborPos);
_map.GetAnchoredEntitiesEnumerator(neighborEnt, neighborGrid, neighborPos);
while (directionEnumerator.MoveNext(out var ent)) while (directionEnumerator.MoveNext(out var ent))
{ {
@@ -299,14 +290,11 @@ public sealed class SpreaderSystem : EntitySystem
} }
/// <summary> /// <summary>
/// Given an entity, this returns a list of all adjacent entities with a <see cref="EdgeSpreaderComponent"/>. /// This function activates all spreaders that are adjacent to a given entity. This also activates other spreaders
/// on the same tile as the current entity (for thin airtight entities like windoors).
/// </summary> /// </summary>
public List<EntityUid> GetSpreadableNeighbors(EntityUid uid, AirtightComponent? comp = null, public void ActivateSpreadableNeighbors(EntityUid uid, (EntityUid Grid, Vector2i Tile)? position = null)
(EntityUid Grid, Vector2i Tile)? position = null)
{ {
Resolve(uid, ref comp, false);
var neighbors = new List<EntityUid>();
Vector2i tile; Vector2i tile;
EntityUid ent; EntityUid ent;
MapGridComponent? grid; MapGridComponent? grid;
@@ -315,37 +303,40 @@ public sealed class SpreaderSystem : EntitySystem
{ {
var transform = Transform(uid); var transform = Transform(uid);
if (!TryComp(transform.GridUid, out grid) || TerminatingOrDeleted(transform.GridUid.Value)) if (!TryComp(transform.GridUid, out grid) || TerminatingOrDeleted(transform.GridUid.Value))
return neighbors; return;
tile = _map.TileIndicesFor(transform.GridUid.Value, grid, transform.Coordinates); tile = _map.TileIndicesFor(transform.GridUid.Value, grid, transform.Coordinates);
ent = transform.GridUid.Value; ent = transform.GridUid.Value;
} }
else else
{ {
if (!TryComp(position.Value.Grid, out grid)) if (!TryComp(position.Value.Grid, out grid))
return neighbors; return;
tile = position.Value.Tile; (ent, tile) = position.Value;
ent = position.Value.Grid;
} }
var spreaderQuery = GetEntityQuery<EdgeSpreaderComponent>(); var anchored = _map.GetAnchoredEntitiesEnumerator(ent, grid, tile);
while (anchored.MoveNext(out var entity))
{
if (entity == ent)
continue;
DebugTools.Assert(Transform(entity.Value).Anchored);
if (_query.HasComponent(ent) && !TerminatingOrDeleted(entity.Value))
EnsureComp<ActiveEdgeSpreaderComponent>(entity.Value);
}
for (var i = 0; i < Atmospherics.Directions; i++) for (var i = 0; i < Atmospherics.Directions; i++)
{ {
var direction = (AtmosDirection) (1 << i); var direction = (AtmosDirection) (1 << i);
if (comp != null && !comp.AirBlockedDirection.IsFlagSet(direction)) var adjacentTile = SharedMapSystem.GetDirection(tile, direction.ToDirection());
continue; anchored = _map.GetAnchoredEntitiesEnumerator(ent, grid, adjacentTile);
var directionEnumerator = while (anchored.MoveNext(out var entity))
_map.GetAnchoredEntitiesEnumerator(ent, grid, SharedMapSystem.GetDirection(tile, direction.ToDirection()));
while (directionEnumerator.MoveNext(out var entity))
{ {
DebugTools.Assert(Transform(entity.Value).Anchored); DebugTools.Assert(Transform(entity.Value).Anchored);
if (spreaderQuery.HasComponent(entity) && !TerminatingOrDeleted(entity.Value)) if (_query.HasComponent(ent) && !TerminatingOrDeleted(entity.Value))
neighbors.Add(entity.Value); EnsureComp<ActiveEdgeSpreaderComponent>(entity.Value);
} }
} }
return neighbors;
} }
} }

View File

@@ -8,11 +8,6 @@ namespace Content.Shared.Atmos
/// </summary> /// </summary>
public static class Atmospherics public static class Atmospherics
{ {
static Atmospherics()
{
AdjustedNumberOfGases = MathHelper.NextMultipleOf(TotalNumberOfGases, 4);
}
#region ATMOS #region ATMOS
/// <summary> /// <summary>
/// The universal gas constant, in kPa*L/(K*mol) /// The universal gas constant, in kPa*L/(K*mol)
@@ -183,7 +178,7 @@ namespace Content.Shared.Atmos
/// This is the actual length of the gases arrays in mixtures. /// This is the actual length of the gases arrays in mixtures.
/// Set to the closest multiple of 4 relative to <see cref="TotalNumberOfGases"/> for SIMD reasons. /// Set to the closest multiple of 4 relative to <see cref="TotalNumberOfGases"/> for SIMD reasons.
/// </summary> /// </summary>
public static readonly int AdjustedNumberOfGases; public const int AdjustedNumberOfGases = ((TotalNumberOfGases + 3) / 4) * 4;
/// <summary> /// <summary>
/// Amount of heat released per mole of burnt hydrogen or tritium (hydrogen isotope) /// Amount of heat released per mole of burnt hydrogen or tritium (hydrogen isotope)

View File

@@ -66,7 +66,10 @@ namespace Content.Shared.Atmos.EntitySystems
[Serializable, NetSerializable] [Serializable, NetSerializable]
public readonly struct GasOverlayData : IEquatable<GasOverlayData> public readonly struct GasOverlayData : IEquatable<GasOverlayData>
{ {
[ViewVariables]
public readonly byte FireState; public readonly byte FireState;
[ViewVariables]
public readonly byte[] Opacity; public readonly byte[] Opacity;
// TODO change fire color based on temps // TODO change fire color based on temps

View File

@@ -77,7 +77,11 @@ namespace Content.Shared.Maps
[DataField("itemDrop", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))] [DataField("itemDrop", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
public string ItemDropPrototypeName { get; private set; } = "FloorTileItemSteel"; public string ItemDropPrototypeName { get; private set; } = "FloorTileItemSteel";
[DataField("isSpace")] public bool IsSpace { get; private set; } // TODO rename data-field in yaml
/// <summary>
/// Whether or not the tile is exposed to the map's atmosphere.
/// </summary>
[DataField("isSpace")] public bool MapAtmosphere { get; private set; }
/// <summary> /// <summary>
/// Friction override for mob mover in <see cref="SharedMoverController"/> /// Friction override for mob mover in <see cref="SharedMoverController"/>

View File

@@ -12,22 +12,6 @@ namespace Content.Shared.Maps
// That, or make the interface arguments non-optional so people stop failing to pass them in. // That, or make the interface arguments non-optional so people stop failing to pass them in.
public static class TurfHelpers public static class TurfHelpers
{ {
/// <summary>
/// Attempts to get the turf at map indices with grid id or null if no such turf is found.
/// </summary>
public static TileRef GetTileRef(this Vector2i vector2i, EntityUid gridId, IEntityManager? entityManager = null)
{
entityManager ??= IoCManager.Resolve<IEntityManager>();
if (!entityManager.TryGetComponent<MapGridComponent>(gridId, out var grid))
return default;
if (!grid.TryGetTileRef(vector2i, out var tile))
return default;
return tile;
}
/// <summary> /// <summary>
/// Attempts to get the turf at a certain coordinates or null if no such turf is found. /// Attempts to get the turf at a certain coordinates or null if no such turf is found.
/// </summary> /// </summary>
@@ -68,7 +52,7 @@ namespace Content.Shared.Maps
/// </summary> /// </summary>
public static bool IsSpace(this Tile tile, ITileDefinitionManager? tileDefinitionManager = null) public static bool IsSpace(this Tile tile, ITileDefinitionManager? tileDefinitionManager = null)
{ {
return tile.GetContentTileDefinition(tileDefinitionManager).IsSpace; return tile.GetContentTileDefinition(tileDefinitionManager).MapAtmosphere;
} }
/// <summary> /// <summary>
@@ -116,15 +100,6 @@ namespace Content.Shared.Maps
return GetEntitiesInTile(turf.Value, flags, lookupSystem); return GetEntitiesInTile(turf.Value, flags, lookupSystem);
} }
/// <summary>
/// Helper that returns all entities in a turf.
/// </summary>
[Obsolete("Use the lookup system")]
public static IEnumerable<EntityUid> GetEntitiesInTile(this Vector2i indices, EntityUid gridId, LookupFlags flags = LookupFlags.Static, EntityLookupSystem? lookupSystem = null)
{
return GetEntitiesInTile(indices.GetTileRef(gridId), flags, lookupSystem);
}
/// <summary> /// <summary>
/// Checks if a turf has something dense on it. /// Checks if a turf has something dense on it.
/// </summary> /// </summary>

View File

@@ -0,0 +1,8 @@
cmd-set-map-atmos-desc = Sets a map's atmosphere
cmd-set-map-atmos-help = setmapatmos <mapid> <space> [<temperature> [moles...]]
cmd-set-map-atmos-removed = Atmosphere removed from map {$map}
cmd-set-map-atmos-updated = Atmosphere set for map {$map}
cmd-set-map-atmos-hint-map = <mapid>
cmd-set-map-atmos-hint-space = <space>
cmd-set-map-atmos-hint-temp = <temperature> (float)
cmd-set-map-atmos-hint-gas = <{$gas} moles> (float)