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

View File

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

View File

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

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

View File

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

View File

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

View File

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

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(
tile.GridIndices,
tile.Air?.Temperature ?? default,
tile.Air?.Moles,
tile.PressureDirection,
tile.LastPressureDirection,
tile.BlockedAirflow,
tile.AirtightData.BlockedDirections,
tile.ExcitedGroup?.GetHashCode(),
tile.Space,
false,
false);
tile.MapAtmosphere,
tile.NoGridTile);
}
public override void Update(float frameTime)

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,8 @@
using Content.Server.Atmos.Components;
using Content.Shared.Atmos.Components;
using Robust.Shared.GameStates;
using Robust.Shared.Map.Components;
using Robust.Shared.Utility;
namespace Content.Server.Atmos.EntitySystems;
@@ -8,10 +10,25 @@ public partial class AtmosphereSystem
{
private void InitializeMap()
{
SubscribeLocalEvent<MapAtmosphereComponent, ComponentInit>(OnMapStartup);
SubscribeLocalEvent<MapAtmosphereComponent, ComponentRemove>(OnMapRemove);
SubscribeLocalEvent<MapAtmosphereComponent, IsTileSpaceMethodEvent>(MapIsTileSpace);
SubscribeLocalEvent<MapAtmosphereComponent, GetTileMixtureMethodEvent>(MapGetTileMixture);
SubscribeLocalEvent<MapAtmosphereComponent, GetTileMixturesMethodEvent>(MapGetTileMixtures);
SubscribeLocalEvent<MapAtmosphereComponent, ComponentGetState>(OnMapGetState);
SubscribeLocalEvent<GridAtmosphereComponent, EntParentChangedMessage>(OnGridParentChanged);
}
private void OnMapStartup(EntityUid uid, MapAtmosphereComponent component, ComponentInit args)
{
component.Mixture.MarkImmutable();
component.Overlay = _gasTileOverlaySystem.GetOverlayData(component.Mixture);
}
private void OnMapRemove(EntityUid uid, MapAtmosphereComponent component, ComponentRemove args)
{
if (!TerminatingOrDeleted(uid))
RefreshAllGridMapAtmospheres(uid);
}
private void MapIsTileSpace(EntityUid uid, MapAtmosphereComponent component, ref IsTileSpaceMethodEvent args)
@@ -28,54 +45,115 @@ public partial class AtmosphereSystem
if (args.Handled)
return;
// Clone the mixture, if possible.
args.Mixture = component.Mixture?.Clone();
args.Mixture = component.Mixture;
args.Handled = true;
}
private void MapGetTileMixtures(EntityUid uid, MapAtmosphereComponent component, ref GetTileMixturesMethodEvent args)
{
if (args.Handled || component.Mixture == null)
if (args.Handled)
return;
args.Handled = true;
args.Mixtures ??= new GasMixture?[args.Tiles.Count];
for (var i = 0; i < args.Tiles.Count; i++)
{
args.Mixtures[i] ??= component.Mixture.Clone();
args.Mixtures[i] ??= component.Mixture;
}
}
private void OnMapGetState(EntityUid uid, MapAtmosphereComponent component, ref ComponentGetState args)
{
args.State = new MapAtmosphereComponentState(_gasTileOverlaySystem.GetOverlayData(component.Mixture));
args.State = new MapAtmosphereComponentState(component.Overlay);
}
public void SetMapAtmosphere(EntityUid uid, bool space, GasMixture mixture, 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))
return;
if (!mixture.Immutable)
{
mixture = mixture.Clone();
mixture.MarkImmutable();
}
component.Mixture = mixture;
component.Overlay = _gasTileOverlaySystem.GetOverlayData(component.Mixture);
Dirty(uid, component);
if (updateTiles)
RefreshAllGridMapAtmospheres(uid);
}
public void SetMapSpace(EntityUid uid, bool space, MapAtmosphereComponent? component = null, bool updateTiles = true)
{
if (!Resolve(uid, ref component))
return;
if (component.Space == space)
return;
component.Space = space;
component.Mixture = mixture;
Dirty(uid, component);
if (updateTiles)
RefreshAllGridMapAtmospheres(uid);
}
public void SetMapGasMixture(EntityUid uid, GasMixture? mixture, MapAtmosphereComponent? component = null)
/// <summary>
/// Forces a refresh of all MapAtmosphere tiles on every grid on a map.
/// </summary>
public void RefreshAllGridMapAtmospheres(EntityUid map)
{
if (!Resolve(uid, ref component))
DebugTools.Assert(HasComp<MapComponent>(map));
var enumerator = AllEntityQuery<GridAtmosphereComponent, TransformComponent>();
while (enumerator.MoveNext(out var grid, out var atmos, out var xform))
{
if (xform.MapUid == map)
RefreshMapAtmosphereTiles((grid, atmos));
}
}
/// <summary>
/// Forces a refresh of all MapAtmosphere tiles on a given grid.
/// </summary>
private void RefreshMapAtmosphereTiles(Entity<GridAtmosphereComponent?> grid)
{
if (!Resolve(grid.Owner, ref grid.Comp))
return;
component.Mixture = mixture;
Dirty(uid, component);
var atmos = grid.Comp;
foreach (var tile in atmos.MapTiles)
{
RemoveMapAtmos(atmos, tile);
atmos.InvalidatedCoords.Add(tile.GridIndices);
}
atmos.MapTiles.Clear();
}
public void SetMapSpace(EntityUid uid, bool space, MapAtmosphereComponent? component = null)
/// <summary>
/// Handles updating map-atmospheres when grids move across maps.
/// </summary>
private void OnGridParentChanged(Entity<GridAtmosphereComponent> grid, ref EntParentChangedMessage args)
{
if (!Resolve(uid, ref component))
// Do nothing if detaching to nullspace
if (!args.Transform.ParentUid.IsValid())
return;
component.Space = space;
Dirty(uid, component);
// 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.Components;
using Content.Shared.Database;
using Content.Shared.Doors.Components;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Random;
@@ -27,7 +26,10 @@ namespace Content.Server.Atmos.EntitySystems
private readonly TileAtmosphere[] _depressurizeSpaceTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit];
private readonly TileAtmosphere[] _depressurizeProgressionOrder = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit * 2];
private void EqualizePressureInZone(Entity<MapGridComponent, GridAtmosphereComponent> ent, TileAtmosphere tile, int cycleNum, GasTileOverlayComponent? visuals)
private void EqualizePressureInZone(
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
TileAtmosphere tile,
int cycleNum)
{
if (tile.Air == null || (tile.MonstermosInfo.LastCycle >= cycleNum))
return; // Already done.
@@ -56,7 +58,7 @@ namespace Content.Server.Atmos.EntitySystems
return;
}
var (_, mapGrid, gridAtmosphere) = ent;
var gridAtmosphere = ent.Comp1;
var queueCycle = ++gridAtmosphere.EqualizationQueueCycleControl;
var totalMoles = 0f;
_equalizeTiles[0] = tile;
@@ -91,7 +93,7 @@ namespace Content.Server.Atmos.EntitySystems
{
// Looks like someone opened an airlock to space!
ExplosivelyDepressurize(ent, tile, cycleNum, visuals);
ExplosivelyDepressurize(ent, tile, cycleNum);
return;
}
}
@@ -216,9 +218,13 @@ namespace Content.Server.Atmos.EntitySystems
for (var k = 0; k < Atmospherics.Directions; k++)
{
var direction = (AtmosDirection) (1 << k);
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
if (!otherTile.AdjacentBits.IsFlagSet(direction))
continue;
if (giver.MonstermosInfo.MoleDelta <= 0)
break; // We're done here now. Let's not do more work than needed.
var otherTile2 = otherTile.AdjacentTiles[k];
if (giver.MonstermosInfo.MoleDelta <= 0) break; // We're done here now. Let's not do more work than needed.
if (otherTile2 == null || otherTile2.MonstermosInfo.LastQueueCycle != queueCycle) continue;
DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
if (otherTile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) continue;
@@ -332,7 +338,7 @@ namespace Content.Server.Atmos.EntitySystems
for (var i = 0; i < tileCount; i++)
{
var otherTile = _equalizeTiles[i]!;
FinalizeEq(gridAtmosphere, otherTile, visuals);
FinalizeEq(gridAtmosphere, otherTile, ent);
}
for (var i = 0; i < tileCount; i++)
@@ -341,12 +347,17 @@ namespace Content.Server.Atmos.EntitySystems
for (var j = 0; j < Atmospherics.Directions; j++)
{
var direction = (AtmosDirection) (1 << j);
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
if (!otherTile.AdjacentBits.IsFlagSet(direction))
continue;
var otherTile2 = otherTile.AdjacentTiles[j]!;
if (otherTile2.AdjacentBits == 0)
continue;
DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
if (otherTile2.Air != null && CompareExchange(otherTile2.Air, tile.Air) == GasCompareResult.NoExchange) continue;
if (otherTile2.Air != null && CompareExchange(otherTile2.Air, tile.Air) == GasCompareResult.NoExchange)
continue;
AddActiveTile(gridAtmosphere, otherTile2);
break;
}
@@ -359,7 +370,10 @@ namespace Content.Server.Atmos.EntitySystems
Array.Clear(_equalizeQueue, 0, Atmospherics.MonstermosTileLimit);
}
private void ExplosivelyDepressurize(Entity<MapGridComponent, GridAtmosphereComponent> ent, TileAtmosphere tile, int cycleNum, GasTileOverlayComponent? visuals)
private void ExplosivelyDepressurize(
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
TileAtmosphere tile,
int cycleNum)
{
// Check if explosive depressurization is enabled and if the tile is valid.
if (!MonstermosDepressurization || tile.Air == null)
@@ -368,7 +382,7 @@ namespace Content.Server.Atmos.EntitySystems
const int limit = Atmospherics.MonstermosHardTileLimit;
var totalMolesRemoved = 0f;
var (owner, mapGrid, gridAtmosphere) = ent;
var (owner, gridAtmosphere, visuals, mapGrid, _) = ent;
var queueCycle = ++gridAtmosphere.EqualizationQueueCycleControl;
var tileCount = 0;
@@ -388,20 +402,27 @@ namespace Content.Server.Atmos.EntitySystems
{
for (var j = 0; j < Atmospherics.Directions; j++)
{
var direction = (AtmosDirection) (1 << j);
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
var otherTile2 = otherTile.AdjacentTiles[j];
if (otherTile2?.Air == null) continue;
DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle) continue;
if (otherTile2?.Air == null)
continue;
ConsiderFirelocks((owner, gridAtmosphere), otherTile, otherTile2, visuals, mapGrid);
if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle)
continue;
var direction = (AtmosDirection) (1 << j);
DebugTools.Assert(otherTile.AdjacentBits.IsFlagSet(direction));
DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
ConsiderFirelocks(ent, otherTile, otherTile2);
// The firelocks might have closed on us.
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
if (!otherTile.AdjacentBits.IsFlagSet(direction))
continue;
otherTile2.MonstermosInfo = new MonstermosInfo { LastQueueCycle = queueCycle };
_depressurizeTiles[tileCount++] = otherTile2;
if (tileCount >= limit) break;
if (tileCount >= limit)
break;
}
}
else
@@ -437,13 +458,21 @@ namespace Content.Server.Atmos.EntitySystems
// Flood fill into this new direction
var direction = (AtmosDirection) (1 << j);
// Tiles in _depressurizeProgressionOrder cannot have null air.
if (!otherTile.AdjacentBits.IsFlagSet(direction) && !otherTile.Space) continue;
if (!otherTile.AdjacentBits.IsFlagSet(direction) && !otherTile.Space)
continue;
var tile2 = otherTile.AdjacentTiles[j];
if (tile2?.MonstermosInfo.LastQueueCycle != queueCycle) continue;
if (tile2?.MonstermosInfo.LastQueueCycle != queueCycle)
continue;
DebugTools.Assert(tile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
// If flood fill has already reached this tile, continue.
if (tile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) continue;
if(tile2.Space) continue;
if (tile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow)
continue;
if(tile2.Space)
continue;
tile2.MonstermosInfo.CurrentTransferDirection = direction.GetOpposite();
tile2.MonstermosInfo.CurrentTransferAmount = 0.0f;
tile2.PressureSpecificTarget = otherTile.PressureSpecificTarget;
@@ -544,36 +573,33 @@ namespace Content.Server.Atmos.EntitySystems
Array.Clear(_depressurizeProgressionOrder, 0, Atmospherics.MonstermosHardTileLimit * 2);
}
private void ConsiderFirelocks(Entity<GridAtmosphereComponent> ent, TileAtmosphere tile, TileAtmosphere other, GasTileOverlayComponent? visuals, MapGridComponent mapGrid)
private void ConsiderFirelocks(
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
TileAtmosphere tile,
TileAtmosphere other)
{
var reconsiderAdjacent = false;
foreach (var entity in mapGrid.GetAnchoredEntities(tile.GridIndices))
var mapGrid = ent.Comp3;
foreach (var entity in _map.GetAnchoredEntities(ent.Owner, mapGrid, tile.GridIndices))
{
if (!TryComp(entity, out FirelockComponent? firelock))
continue;
if (_firelockQuery.TryGetComponent(entity, out var firelock))
reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
}
foreach (var entity in mapGrid.GetAnchoredEntities(other.GridIndices))
foreach (var entity in _map.GetAnchoredEntities(ent.Owner, mapGrid, other.GridIndices))
{
if (!TryComp(entity, out FirelockComponent? firelock))
continue;
if (_firelockQuery.TryGetComponent(entity, out var firelock))
reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
}
if (!reconsiderAdjacent)
return;
var (owner, gridAtmosphere) = ent;
var tileEv = new UpdateAdjacentMethodEvent(owner, tile.GridIndices);
var otherEv = new UpdateAdjacentMethodEvent(owner, other.GridIndices);
GridUpdateAdjacent(owner, gridAtmosphere, ref tileEv);
GridUpdateAdjacent(owner, gridAtmosphere, ref otherEv);
InvalidateVisuals(tile.GridIndex, tile.GridIndices, visuals);
InvalidateVisuals(other.GridIndex, other.GridIndices, visuals);
UpdateAdjacentTiles(ent, tile);
UpdateAdjacentTiles(ent, other);
InvalidateVisuals(tile.GridIndex, tile.GridIndices, ent);
InvalidateVisuals(other.GridIndex, other.GridIndices, ent);
}
private void FinalizeEq(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, GasTileOverlayComponent? visuals)
@@ -642,7 +668,7 @@ namespace Content.Server.Atmos.EntitySystems
if (adj == null)
{
var nonNull = tile.AdjacentTiles.Where(x => x != null).Count();
Log.Error($"Encountered null adjacent tile in {nameof(AdjustEqMovement)}. Dir: {direction}, Tile: {tile.Tile}, non-null adj count: {nonNull}, Trace: {Environment.StackTrace}");
Log.Error($"Encountered null adjacent tile in {nameof(AdjustEqMovement)}. Dir: {direction}, Tile: ({tile.GridIndex}, {tile.GridIndices}), non-null adj count: {nonNull}, Trace: {Environment.StackTrace}");
return;
}

View File

@@ -1,6 +1,5 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.Piping.Components;
using Content.Server.NodeContainer.NodeGroups;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Maps;
@@ -8,6 +7,7 @@ using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.Atmos.EntitySystems
{
@@ -30,118 +30,63 @@ namespace Content.Server.Atmos.EntitySystems
private int _currentRunAtmosphereIndex;
private bool _simulationPaused;
private readonly List<Entity<GridAtmosphereComponent>> _currentRunAtmosphere = new();
private TileAtmosphere GetOrNewTile(EntityUid owner, GridAtmosphereComponent atmosphere, Vector2i index)
{
var tile = atmosphere.Tiles.GetOrNew(index, out var existing);
if (existing)
return tile;
atmosphere.InvalidatedCoords.Add(index);
tile.GridIndex = owner;
tile.GridIndices = index;
return tile;
}
private readonly List<Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent>> _currentRunAtmosphere = new();
/// <summary>
/// Revalidates all invalid coordinates in a grid atmosphere.
/// I.e., process any tiles that have had their airtight blockers modified.
/// </summary>
/// <param name="ent">The grid atmosphere in question.</param>
/// <returns>Whether the process succeeded or got paused due to time constrains.</returns>
private bool ProcessRevalidate(Entity<GridAtmosphereComponent> ent, GasTileOverlayComponent? visuals)
private bool ProcessRevalidate(Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent)
{
var (owner, atmosphere) = ent;
if (ent.Comp4.MapUid == null)
{
Log.Error($"Attempted to process atmosphere on a map-less grid? Grid: {ToPrettyString(ent)}");
return true;
}
var (uid, atmosphere, visuals, grid, xform) = ent;
var volume = GetVolumeForTiles(grid);
TryComp(xform.MapUid, out MapAtmosphereComponent? mapAtmos);
if (!atmosphere.ProcessingPaused)
{
atmosphere.CurrentRunInvalidatedCoordinates.Clear();
atmosphere.CurrentRunInvalidatedCoordinates.EnsureCapacity(atmosphere.InvalidatedCoords.Count);
foreach (var tile in atmosphere.InvalidatedCoords)
atmosphere.CurrentRunInvalidatedTiles.Clear();
atmosphere.CurrentRunInvalidatedTiles.EnsureCapacity(atmosphere.InvalidatedCoords.Count);
foreach (var indices in atmosphere.InvalidatedCoords)
{
atmosphere.CurrentRunInvalidatedCoordinates.Enqueue(tile);
var tile = GetOrNewTile(uid, atmosphere, indices);
atmosphere.CurrentRunInvalidatedTiles.Enqueue(tile);
// Update tile.IsSpace and tile.MapAtmosphere, and tile.AirtightData.
UpdateTileData(ent, mapAtmos, tile);
}
atmosphere.InvalidatedCoords.Clear();
if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime)
return false;
}
if (!TryComp(owner, out MapGridComponent? mapGridComp))
return true;
var mapUid = _mapManager.GetMapEntityIdOrThrow(Transform(owner).MapID);
var volume = GetVolumeForTiles(mapGridComp);
var number = 0;
while (atmosphere.CurrentRunInvalidatedCoordinates.TryDequeue(out var indices))
while (atmosphere.CurrentRunInvalidatedTiles.TryDequeue(out var tile))
{
if (!atmosphere.Tiles.TryGetValue(indices, out var tile))
{
tile = new TileAtmosphere(owner, indices,
new GasMixture(volume) { Temperature = Atmospherics.T20C });
atmosphere.Tiles[indices] = tile;
}
var airBlockedEv = new IsTileAirBlockedMethodEvent(owner, indices, MapGridComponent:mapGridComp);
GridIsTileAirBlocked(owner, atmosphere, ref airBlockedEv);
var isAirBlocked = airBlockedEv.Result;
var oldBlocked = tile.BlockedAirflow;
var updateAdjacentEv = new UpdateAdjacentMethodEvent(owner, indices, mapGridComp);
GridUpdateAdjacent(owner, atmosphere, ref updateAdjacentEv);
// Blocked airflow changed, rebuild excited groups!
if (tile.Excited && tile.BlockedAirflow != oldBlocked)
{
RemoveActiveTile(atmosphere, tile);
}
// Call this instead of the grid method as the map has a say on whether the tile is space or not.
if ((!mapGridComp.TryGetTileRef(indices, out var t) || t.IsSpace(_tileDefinitionManager)) && !isAirBlocked)
{
tile.Air = GetTileMixture(null, mapUid, indices);
tile.MolesArchived = tile.Air != null ? new float[Atmospherics.AdjustedNumberOfGases] : null;
tile.Space = IsTileSpace(null, mapUid, indices, mapGridComp);
}
else if (isAirBlocked)
{
if (airBlockedEv.NoAir)
{
tile.Air = null;
tile.MolesArchived = null;
tile.ArchivedCycle = 0;
tile.LastShare = 0f;
tile.Hotspot = new Hotspot();
}
}
else
{
if (tile.Air == null && NeedsVacuumFixing(mapGridComp, indices))
{
var vacuumEv = new FixTileVacuumMethodEvent(owner, indices);
GridFixTileVacuum(owner, atmosphere, ref vacuumEv);
}
// Tile used to be space, but isn't anymore.
if (tile.Space || (tile.Air?.Immutable ?? false))
{
tile.Air = null;
tile.MolesArchived = null;
tile.ArchivedCycle = 0;
tile.LastShare = 0f;
tile.Space = false;
}
tile.Air ??= new GasMixture(volume){Temperature = Atmospherics.T20C};
tile.MolesArchived ??= new float[Atmospherics.AdjustedNumberOfGases];
}
// We activate the tile.
AddActiveTile(atmosphere, tile);
// TODO ATMOS: Query all the contents of this tile (like walls) and calculate the correct thermal conductivity and heat capacity
var tileDef = mapGridComp.TryGetTileRef(indices, out var tileRef)
? tileRef.GetContentTileDefinition(_tileDefinitionManager)
: null;
tile.ThermalConductivity = tileDef?.ThermalConductivity ?? 0.5f;
tile.HeatCapacity = tileDef?.HeatCapacity ?? float.PositiveInfinity;
InvalidateVisuals(owner, indices, visuals);
for (var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
var otherIndices = indices.Offset(direction);
if (atmosphere.Tiles.TryGetValue(otherIndices, out var otherTile))
AddActiveTile(atmosphere, otherTile);
}
DebugTools.Assert(atmosphere.Tiles.GetValueOrDefault(tile.GridIndices) == tile);
UpdateAdjacentTiles(ent, tile, activate: true);
UpdateTileAir(ent, tile, volume);
InvalidateVisuals(uid, tile.GridIndices, visuals);
if (number++ < InvalidCoordinatesLagCheckIterations)
continue;
@@ -149,12 +94,185 @@ namespace Content.Server.Atmos.EntitySystems
number = 0;
// Process the rest next time.
if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime)
{
return false;
}
TrimDisconnectedMapTiles(ent);
return true;
}
/// <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);
}
}
}
return true;
/// <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);
}
}
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(
@@ -170,19 +288,16 @@ namespace Content.Server.Atmos.EntitySystems
}
}
private bool ProcessTileEqualize(Entity<GridAtmosphereComponent> ent, GasTileOverlayComponent? visuals)
private bool ProcessTileEqualize(Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent)
{
var (uid, atmosphere) = ent;
var atmosphere = ent.Comp1;
if (!atmosphere.ProcessingPaused)
QueueRunTiles(atmosphere.CurrentRunTiles, atmosphere.ActiveTiles);
if (!TryComp(uid, out MapGridComponent? mapGridComp))
throw new Exception("Tried to process a grid atmosphere on an entity that isn't a grid!");
var number = 0;
while (atmosphere.CurrentRunTiles.TryDequeue(out var tile))
{
EqualizePressureInZone((uid, mapGridComp, atmosphere), tile, atmosphere.UpdateCounter, visuals);
EqualizePressureInZone(ent, tile, atmosphere.UpdateCounter);
if (number++ < LagCheckIterations)
continue;
@@ -198,7 +313,7 @@ namespace Content.Server.Atmos.EntitySystems
return true;
}
private bool ProcessActiveTiles(GridAtmosphereComponent atmosphere, GasTileOverlayComponent? visuals)
private bool ProcessActiveTiles(GridAtmosphereComponent atmosphere, GasTileOverlayComponent visuals)
{
if(!atmosphere.ProcessingPaused)
QueueRunTiles(atmosphere.CurrentRunTiles, atmosphere.ActiveTiles);
@@ -242,9 +357,9 @@ namespace Content.Server.Atmos.EntitySystems
if (excitedGroup.BreakdownCooldown > Atmospherics.ExcitedGroupBreakdownCycles)
ExcitedGroupSelfBreakdown(gridAtmosphere, excitedGroup);
else if (excitedGroup.DismantleCooldown > Atmospherics.ExcitedGroupsDismantleCycles)
ExcitedGroupDismantle(gridAtmosphere, excitedGroup);
DeactivateGroupTiles(gridAtmosphere, excitedGroup);
// TODO ATMOS. What is the point of this? why is this only de-exciting the group? Shouldn't it also dismantle it?
if (number++ < LagCheckIterations)
continue;
@@ -435,10 +550,10 @@ namespace Content.Server.Atmos.EntitySystems
_currentRunAtmosphereIndex = 0;
_currentRunAtmosphere.Clear();
var query = EntityQueryEnumerator<GridAtmosphereComponent>();
while (query.MoveNext(out var uid, out var grid))
var query = EntityQueryEnumerator<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var atmos, out var overlay, out var grid, out var xform ))
{
_currentRunAtmosphere.Add((uid, grid));
_currentRunAtmosphere.Add((uid, atmos, overlay, grid, xform));
}
}
@@ -448,8 +563,7 @@ namespace Content.Server.Atmos.EntitySystems
for (; _currentRunAtmosphereIndex < _currentRunAtmosphere.Count; _currentRunAtmosphereIndex++)
{
var ent = _currentRunAtmosphere[_currentRunAtmosphereIndex];
var (owner, atmosphere) = ent;
TryComp(owner, out GasTileOverlayComponent? visuals);
var (owner, atmosphere, visuals, grid, xform) = ent;
if (!TryComp(owner, out TransformComponent? x)
|| x.MapUid == null
@@ -474,13 +588,14 @@ namespace Content.Server.Atmos.EntitySystems
switch (atmosphere.State)
{
case AtmosphereProcessingState.Revalidate:
if (!ProcessRevalidate(ent, visuals))
if (!ProcessRevalidate(ent))
{
atmosphere.ProcessingPaused = true;
return;
}
atmosphere.ProcessingPaused = false;
// Next state depends on whether monstermos equalization is enabled or not.
// Note: We do this here instead of on the tile equalization step to prevent ending it early.
// Therefore, a change to this CVar might only be applied after that step is over.
@@ -489,7 +604,7 @@ namespace Content.Server.Atmos.EntitySystems
: AtmosphereProcessingState.ActiveTiles;
continue;
case AtmosphereProcessingState.TileEqualize:
if (!ProcessTileEqualize(ent, visuals))
if (!ProcessTileEqualize(ent))
{
atmosphere.ProcessingPaused = true;
return;
@@ -499,7 +614,7 @@ namespace Content.Server.Atmos.EntitySystems
atmosphere.State = AtmosphereProcessingState.ActiveTiles;
continue;
case AtmosphereProcessingState.ActiveTiles:
if (!ProcessActiveTiles(atmosphere, visuals))
if (!ProcessActiveTiles(ent, ent))
{
atmosphere.ProcessingPaused = true;
return;
@@ -520,7 +635,7 @@ namespace Content.Server.Atmos.EntitySystems
atmosphere.State = AtmosphereProcessingState.HighPressureDelta;
continue;
case AtmosphereProcessingState.HighPressureDelta:
if (!ProcessHighPressureDelta(ent))
if (!ProcessHighPressureDelta((ent, ent)))
{
atmosphere.ProcessingPaused = true;
return;

View File

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

View File

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

View File

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

View File

@@ -27,8 +27,12 @@ public sealed class AutomaticAtmosSystem : EntitySystem
// Also, these calls are surprisingly slow.
// TODO: Make tiledefmanager cache the IsSpace property, and turn this lookup-through-two-interfaces into
// TODO: a simple array lookup, as tile IDs are likely contiguous, and there's at most 2^16 possibilities anyway.
if (!((ev.OldTile.IsSpace(_tileDefinitionManager) && !ev.NewTile.IsSpace(_tileDefinitionManager)) ||
(!ev.OldTile.IsSpace(_tileDefinitionManager) && ev.NewTile.IsSpace(_tileDefinitionManager))) ||
var oldSpace = ev.OldTile.IsSpace(_tileDefinitionManager);
var newSpace = ev.NewTile.IsSpace(_tileDefinitionManager);
if (!(oldSpace && !newSpace ||
!oldSpace && newSpace) ||
_atmosphereSystem.HasAtmosphere(ev.Entity))
return;

View File

@@ -260,13 +260,13 @@ namespace Content.Server.Atmos.EntitySystems
{
var gas = _atmo.GetGas(i);
if (mixture?.Moles[i] <= UIMinMoles)
if (mixture?[i] <= UIMinMoles)
continue;
if (mixture != null)
{
var gasName = Loc.GetString(gas.Name);
gases.Add(new GasEntry(gasName, mixture.Moles[i], gas.Color));
gases.Add(new GasEntry(gasName, mixture[i], gas.Color));
}
}

View File

@@ -172,7 +172,7 @@ namespace Content.Server.Atmos.EntitySystems
{
var id = VisibleGasId[i];
var gas = _atmosphereSystem.GetGas(id);
var moles = mixture?.Moles[id] ?? 0f;
var moles = mixture?[id] ?? 0f;
ref var opacity = ref data.Opacity[i];
if (moles < gas.GasMolesVisible)
@@ -217,13 +217,13 @@ namespace Content.Server.Atmos.EntitySystems
oldData = new GasOverlayData(tile.Hotspot.State, oldData.Opacity);
}
if (tile.Air != null)
if (tile is {Air: not null, NoGridTile: false})
{
for (var i = 0; i < VisibleGasId.Length; i++)
{
var id = VisibleGasId[i];
var gas = _atmosphereSystem.GetGas(id);
var moles = tile.Air.Moles[id];
var moles = tile.Air[id];
ref var oldOpacity = ref oldData.Opacity[i];
if (moles < gas.GasMolesVisible)

View File

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

View File

@@ -136,7 +136,7 @@ public sealed class GasCanisterSystem : EntitySystem
for (int i = 0; i < containedGasArray.Length; i++)
{
containedGasDict.Add((Gas)i, canister.Air.Moles[i]);
containedGasDict.Add((Gas)i, canister.Air[i]);
}
_adminLogger.Add(LogType.CanisterValve, impact, $"{ToPrettyString(args.Session.AttachedEntity.GetValueOrDefault()):player} set the valve on {ToPrettyString(uid):canister} to {args.Valve:valveState} while it contained [{string.Join(", ", containedGasDict)}]");

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos;
using Content.Shared.Maps;
@@ -51,6 +52,10 @@ namespace Content.Server.Atmos
[ViewVariables]
public readonly TileAtmosphere?[] AdjacentTiles = new TileAtmosphere[Atmospherics.Directions];
/// <summary>
/// Neighbouring tiles to which air can flow. This is a combination of this tile's unblocked direction, and the
/// unblocked directions on adjacent tiles.
/// </summary>
[ViewVariables]
public AtmosDirection AdjacentBits = AtmosDirection.Invalid;
@@ -72,10 +77,7 @@ namespace Content.Server.Atmos
public EntityUid GridIndex { get; set; }
[ViewVariables]
public TileRef? Tile => GridIndices.GetTileRef(GridIndex);
[ViewVariables]
public Vector2i GridIndices { get; }
public Vector2i GridIndices;
[ViewVariables]
public ExcitedGroup? ExcitedGroup { get; set; }
@@ -92,7 +94,7 @@ namespace Content.Server.Atmos
public float LastShare;
[ViewVariables]
public float[]? MolesArchived;
public readonly float[] MolesArchived = new float[Atmospherics.AdjustedNumberOfGases];
GasMixture IGasMixtureHolder.Air
{
@@ -103,8 +105,31 @@ namespace Content.Server.Atmos
[ViewVariables]
public float MaxFireTemperatureSustained { get; set; }
/// <summary>
/// If true, then this tile is directly exposed to the map's atmosphere, either because the grid has no tile at
/// this position, or because the tile type is not airtight.
/// </summary>
[ViewVariables]
public 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)
{
@@ -112,10 +137,24 @@ namespace Content.Server.Atmos
GridIndices = gridIndices;
Air = mixture;
Space = space;
MolesArchived = Air != null ? new float[Atmospherics.AdjustedNumberOfGases] : null;
if(immutable)
Air?.MarkImmutable();
}
public TileAtmosphere(TileAtmosphere other)
{
GridIndex = other.GridIndex;
GridIndices = other.GridIndices;
Space = other.Space;
NoGridTile = other.NoGridTile;
MapAtmosphere = other.MapAtmosphere;
Air = other.Air?.Clone();
Array.Copy(other.MolesArchived, MolesArchived, MolesArchived.Length);
}
public TileAtmosphere()
{
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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)