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;
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
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();
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;
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;
var mapUid = _mapManager.GetMapEntityId(args.MapId);
if (_entManager.TryGetComponent<MapAtmosphereComponent>(mapUid, out var atmos))
- {
- var bottomLeft = args.WorldAABB.BottomLeft.Floored();
- var topRight = args.WorldAABB.TopRight.Ceiled();
-
- for (var x = bottomLeft.X; x <= topRight.X; x++)
- {
- for (var y = bottomLeft.Y; y <= topRight.Y; y++)
- {
- var tilePosition = new Vector2(x, y);
-
- for (var i = 0; i < atmos.OverlayData.Opacity.Length; i++)
- {
- var opacity = atmos.OverlayData.Opacity[i];
+ DrawMapOverlay(drawHandle, args, mapUid, atmos);
- if (opacity > 0)
- args.WorldHandle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity));
- }
- }
- }
- }
+ if (args.Space != OverlaySpace.WorldSpaceEntities)
+ return;
// TODO: WorldBounds callback.
_mapManager.FindGridsIntersecting(args.MapId, args.WorldAABB, ref gridState,
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));
+ }
+ }
+ }
+ }
}
}
if (tileDef is not ContentTileDefinition contentTileDef)
return;
- var tileIcon = contentTileDef.IsSpace
+ var tileIcon = contentTileDef.MapAtmosphere
? _spaceIcon
: new Texture(contentTileDef.Sprite!.Value);
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));
});
}
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));
});
}
--- /dev/null
+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())));
+ }
+}
+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; }
[DataField("noAirWhenFullyAirBlocked")]
public bool NoAirWhenFullyAirBlocked { get; set; } = true;
+ [Access(Other = AccessPermissions.ReadWriteExecute)]
public AtmosDirection AirBlockedDirection => (AtmosDirection)CurrentAirBlockedDirection;
}
}
[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);
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;
/// <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;
}
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
[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!;
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)
}
}
- 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)
+++ /dev/null
-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;
- }
-}
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)
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)
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)
{
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);
[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);
[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);
(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);
[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);
var tileSize = excitedGroup.Tiles.Count;
- if (excitedGroup.Disposed) return;
+ if (excitedGroup.Disposed)
+ return;
if (tileSize == 0)
{
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);
}
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)
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();
}
}
}
private void InitializeGridAtmosphere()
{
SubscribeLocalEvent<GridAtmosphereComponent, ComponentInit>(OnGridAtmosphereInit);
+ SubscribeLocalEvent<GridAtmosphereComponent, ComponentStartup>(OnGridAtmosphereStartup);
SubscribeLocalEvent<GridAtmosphereComponent, ComponentRemove>(OnAtmosphereRemove);
SubscribeLocalEvent<GridAtmosphereComponent, GridSplitEvent>(OnGridSplit);
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);
}
}
- private void OnGridAtmosphereInit(EntityUid uid, GridAtmosphereComponent gridAtmosphere, ComponentInit args)
+ private void OnGridAtmosphereInit(EntityUid uid, GridAtmosphereComponent component, ComponentInit args)
{
base.Initialize();
- if (!TryComp(uid, out MapGridComponent? mapGrid))
- return;
-
EnsureComp<GasTileOverlayComponent>(uid);
-
- foreach (var (indices, tile) in gridAtmosphere.Tiles)
+ foreach (var tile in component.Tiles.Values)
{
- gridAtmosphere.InvalidatedCoords.Add(indices);
tile.GridIndex = uid;
}
+ }
+
+ private void OnGridAtmosphereStartup(EntityUid uid, GridAtmosphereComponent component, ComponentStartup args)
+ {
+ if (!TryComp(uid, out MapGridComponent? mapGrid))
+ return;
- GridRepopulateTiles((uid, mapGrid, gridAtmosphere));
+ InvalidateAllTiles((uid, mapGrid, component));
}
private void OnGridSplit(EntityUid uid, GridAtmosphereComponent originalGridAtmos, ref GridSplitEvent args)
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;
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)
{
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)
args.Handled = true;
}
- private void GridUpdateAdjacent(EntityUid uid, GridAtmosphereComponent component,
- ref UpdateAdjacentMethodEvent args)
+ /// <summary>
+ /// Update array of adjacent tiles and the adjacency flags. Optionally activates all tiles with modified adjacencies.
+ /// </summary>
+ private void UpdateAdjacentTiles(
+ Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
+ TileAtmosphere tile,
+ bool activate = false)
{
- if (args.Handled)
- return;
-
- var mapGridComp = args.MapGridComponent;
-
- if (!Resolve(uid, ref mapGridComp))
- return;
-
- var xform = Transform(uid);
- EntityUid? mapUid = _mapManager.MapExists(xform.MapID) ? _mapManager.GetMapEntityId(xform.MapID) : null;
-
- if (!component.Tiles.TryGetValue(args.Tile, out var tile))
- return;
+ var uid = ent.Owner;
+ var atmos = ent.Comp1;
+ var blockedDirs = tile.AirtightData.BlockedDirections;
+ if (activate)
+ AddActiveTile(atmos, tile);
tile.AdjacentBits = AtmosDirection.Invalid;
- tile.BlockedAirflow = GetBlockedDirections(mapGridComp, tile.GridIndices);
-
for (var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
+ var adjacentIndices = tile.GridIndices.Offset(direction);
- var otherIndices = tile.GridIndices.Offset(direction);
-
- if (!component.Tiles.TryGetValue(otherIndices, out var adjacent))
+ TileAtmosphere? adjacent;
+ if (!tile.NoGridTile)
{
- adjacent = new TileAtmosphere(tile.GridIndex, otherIndices,
- GetTileMixture(null, mapUid, otherIndices),
- space: IsTileSpace(null, mapUid, otherIndices, mapGridComp));
+ adjacent = GetOrNewTile(uid, atmos, adjacentIndices);
}
-
- var oppositeDirection = direction.GetOpposite();
-
- adjacent.BlockedAirflow = GetBlockedDirections(mapGridComp, adjacent.GridIndices);
-
- // Pass in MapGridComponent so we don't have to resolve it for every adjacent direction.
- var tileBlockedEv = new IsTileAirBlockedMethodEvent(uid, tile.GridIndices, direction, mapGridComp);
- GridIsTileAirBlocked(uid, component, ref tileBlockedEv);
-
- var adjacentBlockedEv =
- new IsTileAirBlockedMethodEvent(uid, adjacent.GridIndices, oppositeDirection, mapGridComp);
- GridIsTileAirBlocked(uid, component, ref adjacentBlockedEv);
-
- if (!adjacent.BlockedAirflow.IsFlagSet(oppositeDirection) && !tileBlockedEv.Result)
+ else if (!atmos.Tiles.TryGetValue(adjacentIndices, out adjacent))
{
- adjacent.AdjacentBits |= oppositeDirection;
- adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = tile;
+ tile.AdjacentBits &= ~direction;
+ tile.AdjacentTiles[i] = null;
+ continue;
}
- else
+
+ var adjBlockDirs = adjacent.AirtightData.BlockedDirections;
+ if (activate)
+ AddActiveTile(atmos, adjacent);
+
+ var oppositeDirection = direction.GetOpposite();
+ if (adjBlockDirs.IsFlagSet(oppositeDirection) || blockedDirs.IsFlagSet(direction))
{
+ // Adjacency is blocked by some airtight entity.
+ tile.AdjacentBits &= ~direction;
adjacent.AdjacentBits &= ~oppositeDirection;
+ tile.AdjacentTiles[i] = null;
adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = null;
}
-
- if (!tile.BlockedAirflow.IsFlagSet(direction) && !adjacentBlockedEv.Result)
- {
- tile.AdjacentBits |= direction;
- tile.AdjacentTiles[direction.ToIndex()] = adjacent;
- }
else
{
- tile.AdjacentBits &= ~direction;
- tile.AdjacentTiles[direction.ToIndex()] = null;
+ // No airtight entity in the way.
+ tile.AdjacentBits |= direction;
+ adjacent.AdjacentBits |= oppositeDirection;
+ tile.AdjacentTiles[i] = adjacent;
+ adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = tile;
}
DebugTools.Assert(!(tile.AdjacentBits.IsFlagSet(direction) ^
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)
args.Handled = true;
}
- private void GridFixTileVacuum(EntityUid uid, GridAtmosphereComponent component, ref FixTileVacuumMethodEvent args)
+ private void GridFixTileVacuum(
+ Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
+ TileAtmosphere tile,
+ float volume)
{
- if (args.Handled)
- return;
-
- var adjEv = new GetAdjacentTileMixturesMethodEvent(uid, args.Tile, false, true);
- GridGetAdjacentTileMixtures(uid, component, ref adjEv);
-
- if (!adjEv.Handled || !component.Tiles.TryGetValue(args.Tile, out var tile))
- return;
-
- if (!TryComp<MapGridComponent>(uid, out var mapGridComp))
- return;
-
- var adjacent = adjEv.Result!.ToArray();
-
- // Return early, let's not cause any funny NaNs or needless vacuums.
- if (adjacent.Length == 0)
- return;
+ DebugTools.AssertNotNull(tile.Air);
+ DebugTools.Assert(tile.Air?.Immutable == false );
+ Array.Clear(tile.MolesArchived);
+ tile.ArchivedCycle = 0;
- tile.Air = new GasMixture
+ var count = 0;
+ foreach (var adj in tile.AdjacentTiles)
{
- Volume = GetVolumeForTiles(mapGridComp, 1),
- Temperature = Atmospherics.T20C
- };
-
- tile.MolesArchived = new float[Atmospherics.AdjustedNumberOfGases];
- tile.ArchivedCycle = 0;
+ if (adj?.Air != null)
+ count++;
+ }
- var ratio = 1f / adjacent.Length;
+ var ratio = 1f / count;
var totalTemperature = 0f;
- foreach (var adj in adjacent)
+ foreach (var adj in tile.AdjacentTiles)
{
+ if (adj?.Air == null)
+ continue;
+
totalTemperature += adj.Temperature;
+ // TODO ATMOS. Why is this removing and then re-adding air to the neighbouring tiles?
+ // Is it some rounding issue to do with Atmospherics.GasMinMoles? because otherwise this is just unnecessary.
+ // if we get rid of this, then this could also just add moles and then multiply by ratio at the end, rather
+ // than having to iterate over adjacent tiles twice.
+
// Remove a bit of gas from the adjacent ratio...
- var mix = adj.RemoveRatio(ratio);
+ var mix = adj.Air.RemoveRatio(ratio);
// And merge it to the new tile air.
Merge(tile.Air, mix);
// Return removed gas to its original mixture.
- Merge(adj, mix);
+ Merge(adj.Air, mix);
}
// New temperature is the arithmetic mean of the sum of the adjacent temperatures...
- tile.Air.Temperature = totalTemperature / adjacent.Length;
+ tile.Air.Temperature = totalTemperature / count;
}
private void GridAddPipeNet(EntityUid uid, GridAtmosphereComponent component, ref AddPipeNetMethodEvent args)
/// <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);
}
}
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;
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)
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;
}
/// <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);
if (tile.Air == null)
return tile.HeatCapacity;
- // Moles archived is not null if air is not null.
return GetHeatCapacityCalculation(tile.MolesArchived!, tile.Space);
}
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;
{
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)
if (args.Handled)
return;
- // Clone the mixture, if possible.
- args.Mixture = component.Mixture?.Clone();
+ args.Mixture = component.Mixture;
args.Handled = true;
}
private void MapGetTileMixtures(EntityUid uid, MapAtmosphereComponent component, ref GetTileMixturesMethodEvent args)
{
- if (args.Handled || component.Mixture == null)
+ if (args.Handled)
return;
args.Handled = true;
args.Mixtures ??= new GasMixture?[args.Tiles.Count];
for (var i = 0; i < args.Tiles.Count; i++)
{
- args.Mixtures[i] ??= component.Mixture.Clone();
+ args.Mixtures[i] ??= component.Mixture;
}
}
private void OnMapGetState(EntityUid uid, MapAtmosphereComponent component, ref ComponentGetState args)
{
- args.State = new MapAtmosphereComponentState(_gasTileOverlaySystem.GetOverlayData(component.Mixture));
+ args.State = new MapAtmosphereComponentState(component.Overlay);
+ }
+
+ public void SetMapAtmosphere(EntityUid uid, bool space, GasMixture mixture)
+ {
+ DebugTools.Assert(HasComp<MapComponent>(uid));
+ var component = EnsureComp<MapAtmosphereComponent>(uid);
+ SetMapGasMixture(uid, mixture, component, false);
+ SetMapSpace(uid, space, component, false);
+ RefreshAllGridMapAtmospheres(uid);
}
- public void SetMapAtmosphere(EntityUid uid, bool space, GasMixture mixture, MapAtmosphereComponent? component = null)
+ public void SetMapGasMixture(EntityUid uid, GasMixture mixture, MapAtmosphereComponent? component = null, bool updateTiles = true)
{
if (!Resolve(uid, ref component))
return;
- component.Space = space;
+ if (!mixture.Immutable)
+ {
+ mixture = mixture.Clone();
+ mixture.MarkImmutable();
+ }
+
component.Mixture = mixture;
+ component.Overlay = _gasTileOverlaySystem.GetOverlayData(component.Mixture);
Dirty(uid, component);
+ if (updateTiles)
+ RefreshAllGridMapAtmospheres(uid);
}
- public void SetMapGasMixture(EntityUid uid, GasMixture? mixture, MapAtmosphereComponent? component = null)
+ public void SetMapSpace(EntityUid uid, bool space, MapAtmosphereComponent? component = null, bool updateTiles = true)
{
if (!Resolve(uid, ref component))
return;
- component.Mixture = mixture;
- Dirty(uid, component);
+ if (component.Space == space)
+ return;
+
+ component.Space = space;
+
+ if (updateTiles)
+ RefreshAllGridMapAtmospheres(uid);
}
- public void SetMapSpace(EntityUid uid, bool space, MapAtmosphereComponent? component = null)
+ /// <summary>
+ /// Forces a refresh of all MapAtmosphere tiles on every grid on a map.
+ /// </summary>
+ public void RefreshAllGridMapAtmospheres(EntityUid map)
{
- if (!Resolve(uid, ref component))
+ DebugTools.Assert(HasComp<MapComponent>(map));
+ var enumerator = AllEntityQuery<GridAtmosphereComponent, TransformComponent>();
+ while (enumerator.MoveNext(out var grid, out var atmos, out var xform))
+ {
+ if (xform.MapUid == map)
+ RefreshMapAtmosphereTiles((grid, atmos));
+ }
+ }
+
+ /// <summary>
+ /// Forces a refresh of all MapAtmosphere tiles on a given grid.
+ /// </summary>
+ private void RefreshMapAtmosphereTiles(Entity<GridAtmosphereComponent?> grid)
+ {
+ if (!Resolve(grid.Owner, ref grid.Comp))
return;
- component.Space = space;
- Dirty(uid, component);
+ var atmos = grid.Comp;
+ foreach (var tile in atmos.MapTiles)
+ {
+ RemoveMapAtmos(atmos, tile);
+ atmos.InvalidatedCoords.Add(tile.GridIndices);
+ }
+ atmos.MapTiles.Clear();
+ }
+
+ /// <summary>
+ /// Handles updating map-atmospheres when grids move across maps.
+ /// </summary>
+ private void OnGridParentChanged(Entity<GridAtmosphereComponent> grid, ref EntParentChangedMessage args)
+ {
+ // Do nothing if detaching to nullspace
+ if (!args.Transform.ParentUid.IsValid())
+ return;
+
+ // Avoid doing work if moving from a space-map to another space-map.
+ if (args.OldParent == null
+ || HasComp<MapAtmosphereComponent>(args.OldParent)
+ || HasComp<MapAtmosphereComponent>(args.Transform.ParentUid))
+ {
+ RefreshMapAtmosphereTiles((grid, grid));
+ }
}
}
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;
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.
return;
}
- var (_, mapGrid, gridAtmosphere) = ent;
+ var gridAtmosphere = ent.Comp1;
var queueCycle = ++gridAtmosphere.EqualizationQueueCycleControl;
var totalMoles = 0f;
_equalizeTiles[0] = tile;
{
// Looks like someone opened an airlock to space!
- ExplosivelyDepressurize(ent, tile, cycleNum, visuals);
+ ExplosivelyDepressurize(ent, tile, cycleNum);
return;
}
}
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;
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++)
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;
}
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)
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;
{
for (var j = 0; j < Atmospherics.Directions; j++)
{
- var direction = (AtmosDirection) (1 << j);
- if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
var otherTile2 = otherTile.AdjacentTiles[j];
- if (otherTile2?.Air == null) continue;
+ if (otherTile2?.Air == null)
+ continue;
+
+ if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle)
+ continue;
+
+ var direction = (AtmosDirection) (1 << j);
+ DebugTools.Assert(otherTile.AdjacentBits.IsFlagSet(direction));
DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
- if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle) continue;
- ConsiderFirelocks((owner, gridAtmosphere), otherTile, otherTile2, visuals, mapGrid);
+ ConsiderFirelocks(ent, otherTile, otherTile2);
// The firelocks might have closed on us.
- if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
+ if (!otherTile.AdjacentBits.IsFlagSet(direction))
+ continue;
+
otherTile2.MonstermosInfo = new MonstermosInfo { LastQueueCycle = queueCycle };
_depressurizeTiles[tileCount++] = otherTile2;
- if (tileCount >= limit) break;
+ if (tileCount >= limit)
+ break;
}
}
else
// 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;
_physics.ApplyAngularImpulse(owner, Vector2Helpers.Cross(tile.GridIndices - gridPhysics.LocalCenter, direction) * totalMolesRemoved, body: gridPhysics);
}
- if(tileCount > 10 && (totalMolesRemoved / tileCount) > 10)
+ if (tileCount > 10 && (totalMolesRemoved / tileCount) > 10)
_adminLog.Add(LogType.ExplosiveDepressurization, LogImpact.High,
$"Explosive depressurization removed {totalMolesRemoved} moles from {tileCount} tiles starting from position {tile.GridIndices:position} on grid ID {tile.GridIndex:grid}");
Array.Clear(_depressurizeProgressionOrder, 0, Atmospherics.MonstermosHardTileLimit * 2);
}
- private void ConsiderFirelocks(Entity<GridAtmosphereComponent> ent, TileAtmosphere tile, TileAtmosphere other, GasTileOverlayComponent? visuals, MapGridComponent mapGrid)
+ private void ConsiderFirelocks(
+ Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
+ TileAtmosphere tile,
+ TileAtmosphere other)
{
var reconsiderAdjacent = false;
- foreach (var entity in mapGrid.GetAnchoredEntities(tile.GridIndices))
+ var mapGrid = ent.Comp3;
+ foreach (var entity in _map.GetAnchoredEntities(ent.Owner, mapGrid, tile.GridIndices))
{
- if (!TryComp(entity, out FirelockComponent? firelock))
- continue;
-
- reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
+ if (_firelockQuery.TryGetComponent(entity, out var firelock))
+ reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
}
- foreach (var entity in mapGrid.GetAnchoredEntities(other.GridIndices))
+ foreach (var entity in _map.GetAnchoredEntities(ent.Owner, mapGrid, other.GridIndices))
{
- if (!TryComp(entity, out FirelockComponent? firelock))
- continue;
-
- reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
+ if (_firelockQuery.TryGetComponent(entity, out var firelock))
+ reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
}
if (!reconsiderAdjacent)
return;
- var (owner, gridAtmosphere) = ent;
- var tileEv = new UpdateAdjacentMethodEvent(owner, tile.GridIndices);
- var otherEv = new UpdateAdjacentMethodEvent(owner, other.GridIndices);
- GridUpdateAdjacent(owner, gridAtmosphere, ref tileEv);
- GridUpdateAdjacent(owner, gridAtmosphere, ref otherEv);
- InvalidateVisuals(tile.GridIndex, tile.GridIndices, visuals);
- InvalidateVisuals(other.GridIndex, other.GridIndices, visuals);
+ UpdateAdjacentTiles(ent, tile);
+ UpdateAdjacentTiles(ent, other);
+ InvalidateVisuals(tile.GridIndex, tile.GridIndices, ent);
+ InvalidateVisuals(other.GridIndex, other.GridIndices, ent);
}
private void FinalizeEq(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, GasTileOverlayComponent? visuals)
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;
}
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;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Timing;
+using Robust.Shared.Utility;
namespace Content.Server.Atmos.EntitySystems
{
private int _currentRunAtmosphereIndex;
private bool _simulationPaused;
- private readonly List<Entity<GridAtmosphereComponent>> _currentRunAtmosphere = new();
+ private TileAtmosphere GetOrNewTile(EntityUid owner, GridAtmosphereComponent atmosphere, Vector2i index)
+ {
+ var tile = atmosphere.Tiles.GetOrNew(index, out var existing);
+ if (existing)
+ return tile;
+
+ atmosphere.InvalidatedCoords.Add(index);
+ tile.GridIndex = owner;
+ tile.GridIndices = index;
+ return tile;
+ }
+
+ private readonly List<Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent>> _currentRunAtmosphere = new();
/// <summary>
/// Revalidates all invalid coordinates in a grid atmosphere.
+ /// I.e., process any tiles that have had their airtight blockers modified.
/// </summary>
/// <param name="ent">The grid atmosphere in question.</param>
/// <returns>Whether the process succeeded or got paused due to time constrains.</returns>
- private bool ProcessRevalidate(Entity<GridAtmosphereComponent> ent, GasTileOverlayComponent? visuals)
+ private bool ProcessRevalidate(Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent)
{
- var (owner, atmosphere) = ent;
+ if (ent.Comp4.MapUid == null)
+ {
+ Log.Error($"Attempted to process atmosphere on a map-less grid? Grid: {ToPrettyString(ent)}");
+ return true;
+ }
+
+ var (uid, atmosphere, visuals, grid, xform) = ent;
+ var volume = GetVolumeForTiles(grid);
+ TryComp(xform.MapUid, out MapAtmosphereComponent? mapAtmos);
+
if (!atmosphere.ProcessingPaused)
{
- atmosphere.CurrentRunInvalidatedCoordinates.Clear();
- atmosphere.CurrentRunInvalidatedCoordinates.EnsureCapacity(atmosphere.InvalidatedCoords.Count);
- foreach (var tile in atmosphere.InvalidatedCoords)
+ atmosphere.CurrentRunInvalidatedTiles.Clear();
+ atmosphere.CurrentRunInvalidatedTiles.EnsureCapacity(atmosphere.InvalidatedCoords.Count);
+ foreach (var indices in atmosphere.InvalidatedCoords)
{
- atmosphere.CurrentRunInvalidatedCoordinates.Enqueue(tile);
+ var tile = GetOrNewTile(uid, atmosphere, indices);
+ atmosphere.CurrentRunInvalidatedTiles.Enqueue(tile);
+
+ // Update tile.IsSpace and tile.MapAtmosphere, and tile.AirtightData.
+ UpdateTileData(ent, mapAtmos, tile);
}
atmosphere.InvalidatedCoords.Clear();
+
+ if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime)
+ return false;
}
- if (!TryComp(owner, out MapGridComponent? mapGridComp))
- return true;
+ var number = 0;
+ while (atmosphere.CurrentRunInvalidatedTiles.TryDequeue(out var tile))
+ {
+ DebugTools.Assert(atmosphere.Tiles.GetValueOrDefault(tile.GridIndices) == tile);
+ UpdateAdjacentTiles(ent, tile, activate: true);
+ UpdateTileAir(ent, tile, volume);
+ InvalidateVisuals(uid, tile.GridIndices, visuals);
- var mapUid = _mapManager.GetMapEntityIdOrThrow(Transform(owner).MapID);
+ if (number++ < InvalidCoordinatesLagCheckIterations)
+ continue;
- var volume = GetVolumeForTiles(mapGridComp);
+ number = 0;
+ // Process the rest next time.
+ if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime)
+ return false;
+ }
- var number = 0;
- while (atmosphere.CurrentRunInvalidatedCoordinates.TryDequeue(out var indices))
+ TrimDisconnectedMapTiles(ent);
+ return true;
+ }
+
+ /// <summary>
+ /// This method queued a tile and all of its neighbours up for processing by <see cref="TrimDisconnectedMapTiles"/>.
+ /// </summary>
+ public void QueueTileTrim(GridAtmosphereComponent atmos, TileAtmosphere tile)
+ {
+ if (!tile.TrimQueued)
{
- if (!atmosphere.Tiles.TryGetValue(indices, out var tile))
+ tile.TrimQueued = true;
+ atmos.PossiblyDisconnectedTiles.Add(tile);
+ }
+
+ for (var i = 0; i < Atmospherics.Directions; i++)
+ {
+ var direction = (AtmosDirection) (1 << i);
+ var indices = tile.GridIndices.Offset(direction);
+ if (atmos.Tiles.TryGetValue(indices, out var adj)
+ && adj.NoGridTile
+ && !adj.TrimQueued)
{
- tile = new TileAtmosphere(owner, indices,
- new GasMixture(volume) { Temperature = Atmospherics.T20C });
- atmosphere.Tiles[indices] = tile;
+ adj.TrimQueued = true;
+ atmos.PossiblyDisconnectedTiles.Add(adj);
}
+ }
+ }
- var airBlockedEv = new IsTileAirBlockedMethodEvent(owner, indices, MapGridComponent:mapGridComp);
- GridIsTileAirBlocked(owner, atmosphere, ref airBlockedEv);
- var isAirBlocked = airBlockedEv.Result;
+ /// <summary>
+ /// Tiles in a <see cref="GridAtmosphereComponent"/> are either grid-tiles, or they they should be are tiles
+ /// adjacent to grid-tiles that represent the map's atmosphere. This method trims any map-tiles that are no longer
+ /// adjacent to any grid-tiles.
+ /// </summary>
+ private void TrimDisconnectedMapTiles(
+ Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent)
+ {
+ var atmos = ent.Comp1;
- var oldBlocked = tile.BlockedAirflow;
- var updateAdjacentEv = new UpdateAdjacentMethodEvent(owner, indices, mapGridComp);
- GridUpdateAdjacent(owner, atmosphere, ref updateAdjacentEv);
+ foreach (var tile in atmos.PossiblyDisconnectedTiles)
+ {
+ tile.TrimQueued = false;
+ if (!tile.NoGridTile)
+ continue;
- // Blocked airflow changed, rebuild excited groups!
- if (tile.Excited && tile.BlockedAirflow != oldBlocked)
+ var connected = false;
+ for (var i = 0; i < Atmospherics.Directions; i++)
{
- RemoveActiveTile(atmosphere, tile);
+ var indices = tile.GridIndices.Offset((AtmosDirection) (1 << i));
+ if (_map.TryGetTile(ent.Comp3, indices, out var gridTile) && !gridTile.IsEmpty)
+ {
+ connected = true;
+ break;
+ }
}
- // Call this instead of the grid method as the map has a say on whether the tile is space or not.
- if ((!mapGridComp.TryGetTileRef(indices, out var t) || t.IsSpace(_tileDefinitionManager)) && !isAirBlocked)
+ if (!connected)
{
- tile.Air = GetTileMixture(null, mapUid, indices);
- tile.MolesArchived = tile.Air != null ? new float[Atmospherics.AdjustedNumberOfGases] : null;
- tile.Space = IsTileSpace(null, mapUid, indices, mapGridComp);
+ RemoveActiveTile(atmos, tile);
+ atmos.Tiles.Remove(tile.GridIndices);
}
- else if (isAirBlocked)
+ }
+
+ atmos.PossiblyDisconnectedTiles.Clear();
+ }
+
+ /// <summary>
+ /// Checks whether a tile has a corresponding grid-tile, or whether it is a "map" tile. Also checks whether the
+ /// tile should be considered "space"
+ /// </summary>
+ private void UpdateTileData(
+ Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
+ MapAtmosphereComponent? mapAtmos,
+ TileAtmosphere tile)
+ {
+ var idx = tile.GridIndices;
+ bool mapAtmosphere;
+ if (_map.TryGetTile(ent.Comp3, idx, out var gTile) && !gTile.IsEmpty)
+ {
+ var contentDef = (ContentTileDefinition) _tileDefinitionManager[gTile.TypeId];
+ mapAtmosphere = contentDef.MapAtmosphere;
+ tile.ThermalConductivity = contentDef.ThermalConductivity;
+ tile.HeatCapacity = contentDef.HeatCapacity;
+ tile.NoGridTile = false;
+ }
+ else
+ {
+ mapAtmosphere = true;
+ tile.ThermalConductivity = 0.5f;
+ tile.HeatCapacity = float.PositiveInfinity;
+
+ if (!tile.NoGridTile)
{
- if (airBlockedEv.NoAir)
- {
- tile.Air = null;
- tile.MolesArchived = null;
- tile.ArchivedCycle = 0;
- tile.LastShare = 0f;
- tile.Hotspot = new Hotspot();
- }
+ tile.NoGridTile = true;
+
+ // This tile just became a non-grid atmos tile.
+ // It, or one of its neighbours, might now be completely disconnected from the grid.
+ QueueTileTrim(ent.Comp1, tile);
}
- else
- {
- if (tile.Air == null && NeedsVacuumFixing(mapGridComp, indices))
- {
- var vacuumEv = new FixTileVacuumMethodEvent(owner, indices);
- GridFixTileVacuum(owner, atmosphere, ref vacuumEv);
- }
+ }
- // Tile used to be space, but isn't anymore.
- if (tile.Space || (tile.Air?.Immutable ?? false))
- {
- tile.Air = null;
- tile.MolesArchived = null;
- tile.ArchivedCycle = 0;
- tile.LastShare = 0f;
- tile.Space = false;
- }
+ UpdateAirtightData(ent.Owner, ent.Comp1, ent.Comp3, tile);
- tile.Air ??= new GasMixture(volume){Temperature = Atmospherics.T20C};
- tile.MolesArchived ??= new float[Atmospherics.AdjustedNumberOfGases];
+ if (mapAtmosphere)
+ {
+ if (!tile.MapAtmosphere)
+ {
+ (tile.Air, tile.Space) = GetDefaultMapAtmosphere(mapAtmos);
+ tile.MapAtmosphere = true;
+ ent.Comp1.MapTiles.Add(tile);
}
- // We activate the tile.
- AddActiveTile(atmosphere, tile);
+ DebugTools.AssertNotNull(tile.Air);
+ DebugTools.Assert(tile.Air?.Immutable ?? false);
+ return;
+ }
- // TODO ATMOS: Query all the contents of this tile (like walls) and calculate the correct thermal conductivity and heat capacity
- var tileDef = mapGridComp.TryGetTileRef(indices, out var tileRef)
- ? tileRef.GetContentTileDefinition(_tileDefinitionManager)
- : null;
+ if (!tile.MapAtmosphere)
+ return;
- tile.ThermalConductivity = tileDef?.ThermalConductivity ?? 0.5f;
- tile.HeatCapacity = tileDef?.HeatCapacity ?? float.PositiveInfinity;
- InvalidateVisuals(owner, indices, visuals);
+ // Tile used to be exposed to the map's atmosphere, but isn't anymore.
+ RemoveMapAtmos(ent.Comp1, tile);
+ }
- for (var i = 0; i < Atmospherics.Directions; i++)
- {
- var direction = (AtmosDirection) (1 << i);
- var otherIndices = indices.Offset(direction);
+ private void RemoveMapAtmos(GridAtmosphereComponent atmos, TileAtmosphere tile)
+ {
+ DebugTools.Assert(tile.MapAtmosphere);
+ DebugTools.AssertNotNull(tile.Air);
+ DebugTools.Assert(tile.Air?.Immutable ?? false);
+ tile.MapAtmosphere = false;
+ atmos.MapTiles.Remove(tile);
+ tile.Air = null;
+ Array.Clear(tile.MolesArchived);
+ tile.ArchivedCycle = 0;
+ tile.LastShare = 0f;
+ tile.Space = false;
+ }
- if (atmosphere.Tiles.TryGetValue(otherIndices, out var otherTile))
- AddActiveTile(atmosphere, otherTile);
- }
+ /// <summary>
+ /// Check whether a grid-tile should have an air mixture, and give it one if it doesn't already have one.
+ /// </summary>
+ private void UpdateTileAir(
+ Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
+ TileAtmosphere tile,
+ float volume)
+ {
+ if (tile.MapAtmosphere)
+ {
+ DebugTools.AssertNotNull(tile.Air);
+ DebugTools.Assert(tile.Air?.Immutable ?? false);
+ return;
+ }
- if (number++ < InvalidCoordinatesLagCheckIterations)
- continue;
+ var data = tile.AirtightData;
+ var fullyBlocked = data.BlockedDirections == AtmosDirection.All;
- number = 0;
- // Process the rest next time.
- if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime)
- {
- return false;
- }
+ if (fullyBlocked && data.NoAirWhenBlocked)
+ {
+ if (tile.Air == null)
+ return;
+
+ tile.Air = null;
+ Array.Clear(tile.MolesArchived);
+ tile.ArchivedCycle = 0;
+ tile.LastShare = 0f;
+ tile.Hotspot = new Hotspot();
+ return;
}
- return true;
+ if (tile.Air != null)
+ return;
+
+ tile.Air = new GasMixture(volume){Temperature = Atmospherics.T20C};
+
+ if (data.FixVacuum)
+ GridFixTileVacuum(ent, tile, volume);
}
private void QueueRunTiles(
}
}
- 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;
return true;
}
- private bool ProcessActiveTiles(GridAtmosphereComponent atmosphere, GasTileOverlayComponent? visuals)
+ private bool ProcessActiveTiles(GridAtmosphereComponent atmosphere, GasTileOverlayComponent visuals)
{
if(!atmosphere.ProcessingPaused)
QueueRunTiles(atmosphere.CurrentRunTiles, atmosphere.ActiveTiles);
excitedGroup.BreakdownCooldown++;
excitedGroup.DismantleCooldown++;
- if(excitedGroup.BreakdownCooldown > Atmospherics.ExcitedGroupBreakdownCycles)
+ if (excitedGroup.BreakdownCooldown > Atmospherics.ExcitedGroupBreakdownCycles)
ExcitedGroupSelfBreakdown(gridAtmosphere, excitedGroup);
-
- else if(excitedGroup.DismantleCooldown > Atmospherics.ExcitedGroupsDismantleCycles)
- ExcitedGroupDismantle(gridAtmosphere, excitedGroup);
+ else if (excitedGroup.DismantleCooldown > Atmospherics.ExcitedGroupsDismantleCycles)
+ DeactivateGroupTiles(gridAtmosphere, excitedGroup);
+ // TODO ATMOS. What is the point of this? why is this only de-exciting the group? Shouldn't it also dismantle it?
if (number++ < LagCheckIterations)
continue;
_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));
}
}
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
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.
: AtmosphereProcessingState.ActiveTiles;
continue;
case AtmosphereProcessingState.TileEqualize:
- if (!ProcessTileEqualize(ent, visuals))
+ if (!ProcessTileEqualize(ent))
{
atmosphere.ProcessingPaused = true;
return;
atmosphere.State = AtmosphereProcessingState.ActiveTiles;
continue;
case AtmosphereProcessingState.ActiveTiles:
- if (!ProcessActiveTiles(atmosphere, visuals))
+ if (!ProcessActiveTiles(ent, ent))
{
atmosphere.ProcessingPaused = true;
return;
atmosphere.State = AtmosphereProcessingState.HighPressureDelta;
continue;
case AtmosphereProcessingState.HighPressureDelta:
- if (!ProcessHighPressureDelta(ent))
+ if (!ProcessHighPressureDelta((ent, ent)))
{
atmosphere.ProcessingPaused = true;
return;
using Content.Server.Atmos.Components;
using Content.Shared.Atmos;
+using Robust.Shared.Map.Components;
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()];
{
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);
}
_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>
return Atmospherics.CellVolume * mapGrid.TileSize * tiles;
}
- /// <summary>
- /// Gets all obstructing <see cref="AirtightComponent"/> instances in a specific tile.
- /// </summary>
- /// <param name="mapGrid">The grid where to get the tile.</param>
- /// <param name="tile">The indices of the tile.</param>
- /// <returns>The enumerator for the airtight components.</returns>
- public AtmosObstructionEnumerator GetObstructingComponentsEnumerator(MapGridComponent mapGrid, Vector2i tile)
+ public readonly record struct AirtightData(AtmosDirection BlockedDirections, bool NoAirWhenBlocked,
+ bool FixVacuum);
+
+ private void UpdateAirtightData(EntityUid uid, GridAtmosphereComponent atmos, MapGridComponent grid, TileAtmosphere tile)
{
- var ancEnumerator = mapGrid.GetAnchoredEntitiesEnumerator(tile);
- var airQuery = GetEntityQuery<AirtightComponent>();
+ var oldBlocked = tile.AirtightData.BlockedDirections;
+
+ tile.AirtightData = tile.NoGridTile
+ ? default
+ : GetAirtightData(uid, grid, tile.GridIndices);
- var enumerator = new AtmosObstructionEnumerator(ancEnumerator, airQuery);
- return enumerator;
+ if (tile.AirtightData.BlockedDirections != oldBlocked && tile.ExcitedGroup != null)
+ ExcitedGroupDispose(atmos, tile.ExcitedGroup);
}
- private AtmosDirection GetBlockedDirections(MapGridComponent mapGrid, Vector2i indices)
+ private AirtightData GetAirtightData(EntityUid uid, MapGridComponent grid, Vector2i tile)
{
- var value = AtmosDirection.Invalid;
+ var blockedDirs = AtmosDirection.Invalid;
+ var noAirWhenBlocked = false;
+ var fixVacuum = false;
- var enumerator = GetObstructingComponentsEnumerator(mapGrid, indices);
-
- while (enumerator.MoveNext(out var airtight))
+ foreach (var ent in _map.GetAnchoredEntities(uid, grid, tile))
{
- if(airtight.AirBlocked)
- value |= airtight.AirBlockedDirection;
+ if (!_airtightQuery.TryGetComponent(ent, out var airtight))
+ continue;
+
+ if(!airtight.AirBlocked)
+ continue;
+
+ blockedDirs |= airtight.AirBlockedDirection;
+ noAirWhenBlocked |= airtight.NoAirWhenFullyAirBlocked;
+ fixVacuum |= airtight.FixVacuum;
+
+ if (blockedDirs == AtmosDirection.All && noAirWhenBlocked && fixVacuum)
+ break;
}
- return value;
+ return new AirtightData(blockedDirs, noAirWhenBlocked, fixVacuum);
}
/// <summary>
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;
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()
InitializeGridAtmosphere();
InitializeMap();
+ _atmosQuery = GetEntityQuery<GridAtmosphereComponent>();
+ _airtightQuery = GetEntityQuery<AirtightComponent>();
+ _firelockQuery = GetEntityQuery<FirelockComponent>();
SubscribeLocalEvent<TileChangedEvent>(OnTileChanged);
// 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;
{
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));
}
}
{
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)
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)
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;
{
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;
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()
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AdjustMoles(int gasId, float quantity)
{
- if (!Immutable)
- {
- if (!float.IsFinite(quantity))
- throw new ArgumentException($"Invalid quantity \"{quantity}\" specified!", nameof(quantity));
+ if (Immutable)
+ return;
- // Clamping is needed because x - x can be negative with floating point numbers. If we don't
- // clamp here, the caller always has to call GetMoles(), clamp, then SetMoles().
- Moles[gasId] = MathF.Max(Moles[gasId] + quantity, 0);
- }
+ if (!float.IsFinite(quantity))
+ throw new ArgumentException($"Invalid quantity \"{quantity}\" specified!", nameof(quantity));
+
+ // Clamping is needed because x - x can be negative with floating point numbers. If we don't
+ // clamp here, the caller always has to call GetMoles(), clamp, then SetMoles().
+ ref var moles = ref Moles[gasId];
+ moles = MathF.Max(moles + quantity, 0);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
{
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))
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);
}
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)
public GasMixture Clone()
{
+ if (Immutable)
+ return this;
+
var newMixture = new GasMixture()
{
Moles = (float[])Moles.Clone(),
_temperature = _temperature,
- Immutable = Immutable,
Volume = Volume,
};
return newMixture;
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)}]");
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;
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));
}
}
}
+using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos;
using Content.Shared.Maps;
[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;
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; }
public float LastShare;
[ViewVariables]
- public float[]? MolesArchived;
+ public readonly float[] MolesArchived = new float[Atmospherics.AdjustedNumberOfGases];
GasMixture IGasMixtureHolder.Air
{
[ViewVariables]
public float MaxFireTemperatureSustained { get; set; }
+ /// <summary>
+ /// If true, then this tile is directly exposed to the map's atmosphere, either because the grid has no tile at
+ /// this position, or because the tile type is not airtight.
+ /// </summary>
+ [ViewVariables]
+ public bool MapAtmosphere;
+
+ /// <summary>
+ /// If true, this tile does not actually exist on the grid, it only exists to represent the map's atmosphere for
+ /// adjacent grid tiles.
+ /// </summary>
[ViewVariables]
- public AtmosDirection BlockedAirflow { get; set; } = AtmosDirection.Invalid;
+ public bool NoGridTile;
+
+ /// <summary>
+ /// If true, this tile is queued for processing in <see cref="GridAtmosphereComponent.PossiblyDisconnectedTiles"/>
+ /// </summary>
+ [ViewVariables]
+ public bool TrimQueued;
+
+ /// <summary>
+ /// Cached information about airtight entities on this tile. This gets updated anytime a tile gets invalidated
+ /// (i.e., gets added to <see cref="GridAtmosphereComponent.InvalidatedCoords"/>).
+ /// </summary>
+ public AtmosphereSystem.AirtightData AirtightData;
public TileAtmosphere(EntityUid gridIndex, Vector2i gridIndices, GasMixture? mixture = null, bool immutable = false, bool space = false)
{
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()
+ {
+ }
}
}
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];
public override void Effect(ReagentEffectArgs args)
{
- if (args.EntityManager.TryGetComponent<LungComponent>(args.OrganEntity, out var lung))
+ if (!args.EntityManager.TryGetComponent<LungComponent>(args.OrganEntity, out var lung))
+ return;
+
+ foreach (var (gas, ratio) in _ratios)
{
- foreach (var (gas, ratio) in _ratios)
- {
- lung.Air.Moles[(int) gas] += (ratio * args.Quantity.Float()) / Atmospherics.BreathMolesToReagentMultiplier;
- }
+ var quantity = ratio * args.Quantity.Float() / Atmospherics.BreathMolesToReagentMultiplier;
+ if (quantity < 0)
+ quantity = Math.Max(quantity, -lung.Air[(int)gas]);
+ lung.Air.AdjustMoles(gas, quantity);
}
}
}
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
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)
{
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;
if (_tileDefinitionManager[tileDef.BaseTurf] is not ContentTileDefinition newDef)
break;
- if (newDef.IsSpace && !canCreateVacuum)
+ if (newDef.MapAtmosphere && !canCreateVacuum)
break;
tileDef = newDef;
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>
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);
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)
{
/// </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!;
// 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>]
SubscribeLocalEvent<EdgeSpreaderComponent, EntityTerminatingEvent>(OnTerminating);
SetupPrototypes();
+
+ _query = GetEntityQuery<EdgeSpreaderComponent>();
}
private void OnPrototypeReload(PrototypesReloadedEventArgs obj)
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)
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/>
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))
continue;
var oldCount = occupiedTiles.Count;
- directionEnumerator =
- _map.GetAnchoredEntitiesEnumerator(neighborEnt, neighborGrid, neighborPos);
+ directionEnumerator = _map.GetAnchoredEntitiesEnumerator(neighborEnt, neighborGrid, neighborPos);
while (directionEnumerator.MoveNext(out var ent))
{
}
/// <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;
{
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;
}
}
/// </summary>
public static class Atmospherics
{
- static Atmospherics()
- {
- AdjustedNumberOfGases = MathHelper.NextMultipleOf(TotalNumberOfGases, 4);
- }
-
#region ATMOS
/// <summary>
/// The universal gas constant, in kPa*L/(K*mol)
/// 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)
[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
[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"/>
// 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>
/// </summary>
public static bool IsSpace(this Tile tile, ITileDefinitionManager? tileDefinitionManager = null)
{
- return tile.GetContentTileDefinition(tileDefinitionManager).IsSpace;
+ return tile.GetContentTileDefinition(tileDefinitionManager).MapAtmosphere;
}
/// <summary>
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>
--- /dev/null
+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)