using System;
using System.Buffers;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Content.Server.Atmos.Reactions;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.GameObjects.EntitySystems.Atmos;
using Content.Server.Interfaces;
using Content.Server.Utility;
using Content.Shared.Atmos;
using Content.Shared.Audio;
using Content.Shared.Maps;
using JetBrains.Annotations;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.GameObjects.EntitySystems.TileLookup;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Random;
using Robust.Shared.ViewVariables;
namespace Content.Server.Atmos
{
public class TileAtmosphere : IGasMixtureHolder
{
[Robust.Shared.IoC.Dependency] private readonly IRobustRandom _robustRandom = default!;
[Robust.Shared.IoC.Dependency] private readonly IEntityManager _entityManager = default!;
[Robust.Shared.IoC.Dependency] private readonly IMapManager _mapManager = default!;
private readonly GridTileLookupSystem _gridTileLookupSystem = default!;
private static readonly TileAtmosphereComparer Comparer = new TileAtmosphereComparer();
[ViewVariables] private int _archivedCycle;
[ViewVariables] private int _currentCycle;
[ViewVariables]
private static GasTileOverlaySystem _gasTileOverlaySystem;
[ViewVariables]
public int AtmosCooldown { get; set; } = 0;
[ViewVariables]
public float Temperature {get; private set; } = Atmospherics.T20C;
[ViewVariables]
private float _temperatureArchived = Atmospherics.T20C;
// I know this being static is evil, but I seriously can't come up with a better solution to sound spam.
private static int _soundCooldown;
[ViewVariables]
public TileAtmosphere PressureSpecificTarget { get; set; }
[ViewVariables]
public float PressureDifference { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
public float HeatCapacity { get; set; } = 1f;
[ViewVariables]
public float ThermalConductivity { get; set; } = 0.05f;
[ViewVariables]
public bool Excited { get; set; }
[ViewVariables]
private readonly GridAtmosphereComponent _gridAtmosphereComponent;
///
/// Adjacent tiles in the same order as . (NSEW)
///
[ViewVariables]
private readonly TileAtmosphere[] _adjacentTiles = new TileAtmosphere[Atmospherics.Directions];
private AtmosDirection _adjacentBits = AtmosDirection.Invalid;
[ViewVariables, UsedImplicitly]
private int AdjacentBitsInt => (int)_adjacentBits;
[ViewVariables]
private TileAtmosInfo _tileAtmosInfo;
[ViewVariables]
public Hotspot Hotspot;
private AtmosDirection _pressureDirection;
// I'm assuming there's a good reason the original variable was made private, but this information is also important.
public AtmosDirection PressureDirectionForDebugOverlay => _pressureDirection;
[ViewVariables, UsedImplicitly]
private int PressureDirectionInt => (int)_pressureDirection;
[ViewVariables]
public GridId GridIndex { get; }
[ViewVariables]
public TileRef? Tile => GridIndices.GetTileRef(GridIndex);
[ViewVariables]
public MapIndices GridIndices { get; }
[ViewVariables]
public ExcitedGroup ExcitedGroup { get; set; }
[ViewVariables]
public GasMixture Air { get; set; }
[ViewVariables, UsedImplicitly]
private int _blockedAirflow => (int)BlockedAirflow;
public AtmosDirection BlockedAirflow { get; set; } = AtmosDirection.Invalid;
[ViewVariables]
public bool BlocksAllAir => BlockedAirflow == AtmosDirection.All;
public TileAtmosphere(GridAtmosphereComponent atmosphereComponent, GridId gridIndex, MapIndices gridIndices, GasMixture mixture = null, bool immutable = false)
{
IoCManager.InjectDependencies(this);
_gridAtmosphereComponent = atmosphereComponent;
_gridTileLookupSystem = _entityManager.EntitySysManager.GetEntitySystem();
GridIndex = gridIndex;
GridIndices = gridIndices;
Air = mixture;
if(immutable)
Air?.MarkImmutable();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Archive(int fireCount)
{
Air?.Archive();
_archivedCycle = fireCount;
_temperatureArchived = Temperature;
}
public void HotspotExpose(float exposedTemperature, float exposedVolume, bool soh = false)
{
if (Air == null)
return;
var oxygen = Air.GetMoles(Gas.Oxygen);
if (oxygen < 0.5f)
return;
var phoron = Air.GetMoles(Gas.Phoron);
var tritium = Air.GetMoles(Gas.Tritium);
if (Hotspot.Valid)
{
if (soh)
{
if (phoron > 0.5f || tritium > 0.5f)
{
if (Hotspot.Temperature < exposedTemperature)
Hotspot.Temperature = exposedTemperature;
if (Hotspot.Volume < exposedVolume)
Hotspot.Volume = exposedVolume;
}
}
return;
}
if ((exposedTemperature > Atmospherics.PhoronMinimumBurnTemperature) && (phoron > 0.5f || tritium > 0.5f))
{
Hotspot = new Hotspot
{
Volume = exposedVolume * 25f,
Temperature = exposedTemperature,
SkippedFirstProcess = _currentCycle > _gridAtmosphereComponent.UpdateCounter
};
Hotspot.Start();
_gridAtmosphereComponent.AddActiveTile(this);
_gridAtmosphereComponent.AddHotspotTile(this);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void HighPressureMovements()
{
// TODO ATMOS finish this
if(PressureDifference > 15)
{
if(_soundCooldown == 0)
EntitySystem.Get().PlayAtCoords("/Audio/Effects/space_wind.ogg",
GridIndices.ToEntityCoordinates(_mapManager, GridIndex), AudioHelpers.WithVariation(0.125f).WithVolume(MathHelper.Clamp(PressureDifference / 10, 10, 100)));
}
foreach (var entity in _gridTileLookupSystem.GetEntitiesIntersecting(GridIndex, GridIndices))
{
if (!entity.TryGetComponent(out ICollidableComponent physics)
|| !entity.TryGetComponent(out MovedByPressureComponent pressure)
|| ContainerHelpers.IsInContainer(entity))
continue;
physics.WakeBody();
var pressureMovements = physics.EnsureController();
if (pressure.LastHighPressureMovementAirCycle < _gridAtmosphereComponent.UpdateCounter)
{
pressureMovements.ExperiencePressureDifference(_gridAtmosphereComponent.UpdateCounter, PressureDifference, _pressureDirection, 0, PressureSpecificTarget?.GridIndices.ToEntityCoordinates(_mapManager, GridIndex) ?? EntityCoordinates.Invalid);
}
}
if (PressureDifference > 100)
{
// TODO ATMOS Do space wind graphics here!
}
_soundCooldown++;
if (_soundCooldown > 75)
_soundCooldown = 0;
}
private class TileAtmosphereComparer : IComparer
{
public int Compare(TileAtmosphere a, TileAtmosphere b)
{
if (a == null && b == null)
return 0;
if (a == null)
return -1;
if (b == null)
return 1;
return a._tileAtmosInfo.MoleDelta.CompareTo(b._tileAtmosInfo.MoleDelta);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void EqualizePressureInZone(int cycleNum)
{
if (Air == null || (_tileAtmosInfo.LastCycle >= cycleNum)) return; // Already done.
_tileAtmosInfo = new TileAtmosInfo();
var startingMoles = Air.TotalMoles;
var runAtmos = false;
// We need to figure if this is necessary
for (var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
if (!_adjacentBits.HasFlag(direction)) continue;
var other = _adjacentTiles[i];
if (other?.Air == null) continue;
var comparisonMoles = other.Air.TotalMoles;
if (!(MathF.Abs(comparisonMoles - startingMoles) > Atmospherics.MinimumMolesDeltaToMove)) continue;
runAtmos = true;
break;
}
if (!runAtmos) // There's no need so we don't bother.
{
_tileAtmosInfo.LastCycle = cycleNum;
return;
}
var queueCycle = ++_gridAtmosphereComponent.EqualizationQueueCycleControl;
var totalMoles = 0f;
var tiles = ArrayPool.Shared.Rent(Atmospherics.ZumosHardTileLimit);
tiles[0] = this;
_tileAtmosInfo.LastQueueCycle = queueCycle;
var tileCount = 1;
for (var i = 0; i < tileCount; i++)
{
if (i > Atmospherics.ZumosHardTileLimit) break;
var exploring = tiles[i];
if (i < Atmospherics.ZumosTileLimit)
{
var tileMoles = exploring.Air.TotalMoles;
exploring._tileAtmosInfo.MoleDelta = tileMoles;
totalMoles += tileMoles;
}
for (var j = 0; j < Atmospherics.Directions; j++)
{
var direction = (AtmosDirection) (1 << j);
if (!exploring._adjacentBits.HasFlag(direction)) continue;
var adj = exploring._adjacentTiles[j];
if (adj?.Air == null) continue;
if(adj._tileAtmosInfo.LastQueueCycle == queueCycle) continue;
adj._tileAtmosInfo = new TileAtmosInfo {LastQueueCycle = queueCycle};
if(tileCount < Atmospherics.ZumosHardTileLimit)
tiles[tileCount++] = adj;
if (adj.Air.Immutable)
{
// Looks like someone opened an airlock to space!
ExplosivelyDepressurize(cycleNum);
return;
}
}
}
if (tileCount > Atmospherics.ZumosTileLimit)
{
for (var i = Atmospherics.ZumosTileLimit; i < tileCount; i++)
{
//We unmark them. We shouldn't be pushing/pulling gases to/from them.
var tile = tiles[i];
if (tile == null) continue;
tiles[i]._tileAtmosInfo.LastQueueCycle = 0;
}
tileCount = Atmospherics.ZumosTileLimit;
}
//tiles = tiles.AsSpan().Slice(0, tileCount).ToArray(); // According to my benchmarks, this is much slower.
//Array.Resize(ref tiles, tileCount);
var averageMoles = totalMoles / (tileCount);
var giverTiles = ArrayPool.Shared.Rent(tileCount);
var takerTiles = ArrayPool.Shared.Rent(tileCount);
var giverTilesLength = 0;
var takerTilesLength = 0;
for (var i = 0; i < tileCount; i++)
{
var tile = tiles[i];
tile._tileAtmosInfo.LastCycle = cycleNum;
tile._tileAtmosInfo.MoleDelta -= averageMoles;
if (tile._tileAtmosInfo.MoleDelta > 0)
{
giverTiles[giverTilesLength++] = tile;
}
else
{
takerTiles[takerTilesLength++] = tile;
}
}
var logN = MathF.Log2(tileCount);
// Optimization - try to spread gases using an O(nlogn) algorithm that has a chance of not working first to avoid O(n^2)
if (giverTilesLength > logN && takerTilesLength > logN)
{
// Even if it fails, it will speed up the next part.
Array.Sort(tiles, 0, tileCount, Comparer);
for (var i = 0; i < tileCount; i++)
{
var tile = tiles[i];
tile._tileAtmosInfo.FastDone = true;
if (!(tile._tileAtmosInfo.MoleDelta > 0)) continue;
var eligibleDirections = AtmosDirection.Invalid;
var eligibleDirectionCount = 0;
for (var j = 0; j < Atmospherics.Directions; j++)
{
var direction = (AtmosDirection) (1 << j);
if (!tile._adjacentBits.HasFlag(direction)) continue;
var tile2 = tile._adjacentTiles[j];
// skip anything that isn't part of our current processing block.
if (tile2._tileAtmosInfo.FastDone || tile2._tileAtmosInfo.LastQueueCycle != queueCycle)
continue;
eligibleDirections |= direction;
eligibleDirectionCount++;
}
if (eligibleDirectionCount <= 0)
continue; // Oof we've painted ourselves into a corner. Bad luck. Next part will handle this.
var molesToMove = tile._tileAtmosInfo.MoleDelta / eligibleDirectionCount;
for (var j = 0; j < Atmospherics.Directions; j++)
{
var direction = (AtmosDirection) (1 << j);
if (!eligibleDirections.HasFlag(direction)) continue;
tile.AdjustEqMovement(direction, molesToMove);
tile._tileAtmosInfo.MoleDelta -= molesToMove;
tile._adjacentTiles[j]._tileAtmosInfo.MoleDelta += molesToMove;
}
}
giverTilesLength = 0;
takerTilesLength = 0;
for (var i = 0; i < tileCount; i++)
{
var tile = tiles[i];
if (tile._tileAtmosInfo.MoleDelta > 0)
{
giverTiles[giverTilesLength++] = tile;
}
else
{
takerTiles[takerTilesLength++] = tile;
}
}
// This is the part that can become O(n^2).
if (giverTilesLength < takerTilesLength)
{
// as an optimization, we choose one of two methods based on which list is smaller. We really want to avoid O(n^2) if we can.
var queue = ArrayPool.Shared.Rent(tileCount);
for (var j = 0; j < giverTilesLength; j++)
{
var giver = giverTiles[j];
giver._tileAtmosInfo.CurrentTransferDirection = AtmosDirection.Invalid;
giver._tileAtmosInfo.CurrentTransferAmount = 0;
var queueCycleSlow = ++_gridAtmosphereComponent.EqualizationQueueCycleControl;
var queueLength = 0;
queue[queueLength++] = giver;
giver._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow;
for (var i = 0; i < queueLength; i++)
{
if (giver._tileAtmosInfo.MoleDelta <= 0)
break; // We're done here now. Let's not do more work than needed.
var tile = queue[i];
for (var k = 0; k < Atmospherics.Directions; k++)
{
var direction = (AtmosDirection) (1 << k);
if (!tile._adjacentBits.HasFlag(direction)) continue;
var tile2 = tile._adjacentTiles[k];
if (giver._tileAtmosInfo.MoleDelta <= 0) break; // We're done here now. Let's not do more work than needed.
if (tile2._tileAtmosInfo.LastQueueCycle != queueCycle) continue;
if (tile2._tileAtmosInfo.LastSlowQueueCycle == queueCycleSlow) continue;
queue[queueLength++] = tile2;
tile2._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow;
tile2._tileAtmosInfo.CurrentTransferDirection = direction.GetOpposite();
tile2._tileAtmosInfo.CurrentTransferAmount = 0;
if (tile2._tileAtmosInfo.MoleDelta < 0)
{
// This tile needs gas. Let's give it to 'em.
if (-tile2._tileAtmosInfo.MoleDelta > giver._tileAtmosInfo.MoleDelta)
{
// We don't have enough gas!
tile2._tileAtmosInfo.CurrentTransferAmount -= giver._tileAtmosInfo.MoleDelta;
tile2._tileAtmosInfo.MoleDelta += giver._tileAtmosInfo.MoleDelta;
giver._tileAtmosInfo.MoleDelta = 0;
}
else
{
// We have enough gas.
tile2._tileAtmosInfo.CurrentTransferAmount += tile2._tileAtmosInfo.MoleDelta;
giver._tileAtmosInfo.MoleDelta += tile2._tileAtmosInfo.MoleDelta;
tile2._tileAtmosInfo.MoleDelta = 0;
}
}
}
}
// Putting this loop here helps make it O(n^2) over O(n^3)
for (var i = queueLength - 1; i >= 0; i--)
{
var tile = queue[i];
if (tile._tileAtmosInfo.CurrentTransferAmount != 0 && tile._tileAtmosInfo.CurrentTransferDirection != AtmosDirection.Invalid)
{
tile.AdjustEqMovement(tile._tileAtmosInfo.CurrentTransferDirection, tile._tileAtmosInfo.CurrentTransferAmount);
tile._adjacentTiles[tile._tileAtmosInfo.CurrentTransferDirection.ToIndex()]
._tileAtmosInfo.CurrentTransferAmount += tile._tileAtmosInfo.CurrentTransferAmount;
tile._tileAtmosInfo.CurrentTransferAmount = 0;
}
}
}
ArrayPool.Shared.Return(queue);
}
else
{
var queue = ArrayPool.Shared.Rent(tileCount);
for (var j = 0; j < takerTilesLength; j++)
{
var taker = takerTiles[j];
taker._tileAtmosInfo.CurrentTransferDirection = AtmosDirection.Invalid;
taker._tileAtmosInfo.CurrentTransferAmount = 0;
var queueCycleSlow = ++_gridAtmosphereComponent.EqualizationQueueCycleControl;
var queueLength = 0;
queue[queueLength++] = taker;
taker._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow;
for (var i = 0; i < queueLength; i++)
{
if (taker._tileAtmosInfo.MoleDelta >= 0)
break; // We're done here now. Let's not do more work than needed.
var tile = queue[i];
for (var k = 0; k < Atmospherics.Directions; k++)
{
var direction = (AtmosDirection) (1 << k);
if (!tile._adjacentBits.HasFlag(direction)) continue;
var tile2 = tile._adjacentTiles[k];
if (taker._tileAtmosInfo.MoleDelta >= 0) break; // We're done here now. Let's not do more work than needed.
if (tile2._tileAtmosInfo.LastQueueCycle != queueCycle) continue;
if (tile2._tileAtmosInfo.LastSlowQueueCycle == queueCycleSlow) continue;
queue[queueLength++] = tile2;
tile2._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow;
tile2._tileAtmosInfo.CurrentTransferDirection = direction.GetOpposite();
tile2._tileAtmosInfo.CurrentTransferAmount = 0;
if (tile2._tileAtmosInfo.MoleDelta > 0)
{
// This tile has gas we can suck, so let's
if (tile2._tileAtmosInfo.MoleDelta > -taker._tileAtmosInfo.MoleDelta)
{
// They have enough gas
tile2._tileAtmosInfo.CurrentTransferAmount -= taker._tileAtmosInfo.MoleDelta;
tile2._tileAtmosInfo.MoleDelta += taker._tileAtmosInfo.MoleDelta;
taker._tileAtmosInfo.MoleDelta = 0;
}
else
{
// They don't have enough gas!
tile2._tileAtmosInfo.CurrentTransferAmount += tile2._tileAtmosInfo.MoleDelta;
taker._tileAtmosInfo.MoleDelta += tile2._tileAtmosInfo.MoleDelta;
tile2._tileAtmosInfo.MoleDelta = 0;
}
}
}
}
for (var i = queueLength - 1; i >= 0; i--)
{
var tile = queue[i];
if (tile._tileAtmosInfo.CurrentTransferAmount == 0 || tile._tileAtmosInfo.CurrentTransferDirection == AtmosDirection.Invalid)
continue;
tile.AdjustEqMovement(tile._tileAtmosInfo.CurrentTransferDirection, tile._tileAtmosInfo.CurrentTransferAmount);
tile._adjacentTiles[tile._tileAtmosInfo.CurrentTransferDirection.ToIndex()]
._tileAtmosInfo.CurrentTransferAmount += tile._tileAtmosInfo.CurrentTransferAmount;
tile._tileAtmosInfo.CurrentTransferAmount = 0;
}
}
ArrayPool.Shared.Return(queue);
}
for (var i = 0; i < tileCount; i++)
{
var tile = tiles[i];
tile.FinalizeEq();
}
for (var i = 0; i < tileCount; i++)
{
var tile = tiles[i];
for (var j = 0; j < Atmospherics.Directions; j++)
{
var direction = (AtmosDirection) (1 << j);
if (!tile._adjacentBits.HasFlag(direction)) continue;
var tile2 = tile._adjacentTiles[j];
if (tile2?.Air?.Compare(Air) == GasMixture.GasCompareResult.NoExchange) continue;
_gridAtmosphereComponent.AddActiveTile(tile2);
break;
}
}
ArrayPool.Shared.Return(tiles);
ArrayPool.Shared.Return(giverTiles);
ArrayPool.Shared.Return(takerTiles);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void FinalizeEq()
{
var transferDirections = new float[Atmospherics.Directions];
var hasTransferDirs = false;
for (var i = 0; i < Atmospherics.Directions; i++)
{
var amount = _tileAtmosInfo[i];
if (amount == 0) continue;
transferDirections[i] = amount;
_tileAtmosInfo[i] = 0; // Set them to 0 to prevent infinite recursion.
hasTransferDirs = true;
}
if (!hasTransferDirs) return;
for(var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
if (!_adjacentBits.HasFlag(direction)) continue;
var amount = transferDirections[i];
var tile = _adjacentTiles[i];
if (tile?.Air == null) continue;
if (amount > 0)
{
if (Air.TotalMoles < amount)
FinalizeEqNeighbors(transferDirections);
tile._tileAtmosInfo[direction.GetOpposite()] = 0;
tile.Air.Merge(Air.Remove(amount));
UpdateVisuals();
tile.UpdateVisuals();
ConsiderPressureDifference(tile, amount);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void FinalizeEqNeighbors(in float[] transferDirs)
{
for (var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
var amount = transferDirs[i];
if(amount < 0 && _adjacentBits.HasFlag(direction))
_adjacentTiles[i].FinalizeEq(); // A bit of recursion if needed.
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ConsiderPressureDifference(TileAtmosphere other, float difference)
{
_gridAtmosphereComponent.AddHighPressureDelta(this);
if (difference > PressureDifference)
{
PressureDifference = difference;
_pressureDirection = ((Vector2i)(GridIndices - other.GridIndices)).GetDir().ToAtmosDirection();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void AdjustEqMovement(AtmosDirection direction, float amount)
{
_tileAtmosInfo[direction] += amount;
_adjacentTiles[direction.ToIndex()]._tileAtmosInfo[direction.GetOpposite()] -= amount;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ProcessCell(int fireCount)
{
// Can't process a tile without air
if (Air == null)
{
_gridAtmosphereComponent.RemoveActiveTile(this);
return;
}
if (_archivedCycle < fireCount)
Archive(fireCount);
_currentCycle = fireCount;
var adjacentTileLength = 0;
AtmosCooldown++;
for (var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
if(_adjacentBits.HasFlag(direction))
adjacentTileLength++;
}
for(var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
if (!_adjacentBits.HasFlag(direction)) continue;
var enemyTile = _adjacentTiles[i];
// If the tile is null or has no air, we don't do anything for it.
if(enemyTile?.Air == null) continue;
if (fireCount <= enemyTile._currentCycle) continue;
enemyTile.Archive(fireCount);
var shouldShareAir = false;
if (ExcitedGroup != null && enemyTile.ExcitedGroup != null)
{
if (ExcitedGroup != enemyTile.ExcitedGroup)
{
ExcitedGroup.MergeGroups(enemyTile.ExcitedGroup);
}
shouldShareAir = true;
} else if (Air.Compare(enemyTile.Air) != GasMixture.GasCompareResult.NoExchange)
{
if (!enemyTile.Excited)
{
_gridAtmosphereComponent.AddActiveTile(enemyTile);
}
var excitedGroup = ExcitedGroup;
excitedGroup ??= enemyTile.ExcitedGroup;
if (excitedGroup == null)
{
excitedGroup = new ExcitedGroup();
excitedGroup.Initialize(_gridAtmosphereComponent);
}
if (ExcitedGroup == null)
excitedGroup.AddTile(this);
if(enemyTile.ExcitedGroup == null)
excitedGroup.AddTile(enemyTile);
shouldShareAir = true;
}
if (shouldShareAir)
{
var difference = Air.Share(enemyTile.Air, adjacentTileLength);
// Space wind!
if (difference > 0)
{
ConsiderPressureDifference(enemyTile, difference);
}
else
{
enemyTile.ConsiderPressureDifference(this, -difference);
}
LastShareCheck();
}
}
React();
UpdateVisuals();
var remove = true;
if(Air.Temperature > Atmospherics.MinimumTemperatureStartSuperConduction)
if (ConsiderSuperconductivity(true))
remove = false;
if((ExcitedGroup == null && remove) || (AtmosCooldown > (Atmospherics.ExcitedGroupsDismantleCycles * 2)))
_gridAtmosphereComponent.RemoveActiveTile(this);
}
public void ProcessHotspot()
{
if (!Hotspot.Valid)
{
_gridAtmosphereComponent.RemoveHotspotTile(this);
return;
}
if (!Hotspot.SkippedFirstProcess)
{
Hotspot.SkippedFirstProcess = true;
return;
}
ExcitedGroup?.ResetCooldowns();
if ((Hotspot.Temperature < Atmospherics.FireMinimumTemperatureToExist) || (Hotspot.Volume <= 1f)
|| Air == null || Air.Gases[(int)Gas.Oxygen] < 0.5f || (Air.Gases[(int)Gas.Phoron] < 0.5f && Air.GetMoles(Gas.Tritium) < 0.5f))
{
Hotspot = new Hotspot();
UpdateVisuals();
return;
}
PerformHotspotExposure();
if (Hotspot.Bypassing)
{
Hotspot.State = 3;
_gridAtmosphereComponent.BurnTile(GridIndices);
if (Air.Temperature > Atmospherics.FireMinimumTemperatureToSpread)
{
var radiatedTemperature = Air.Temperature * Atmospherics.FireSpreadRadiosityScale;
foreach (var tile in _adjacentTiles)
{
if(!tile.Hotspot.Valid)
tile.HotspotExpose(radiatedTemperature, Atmospherics.CellVolume/4);
}
}
}
else
{
Hotspot.State = (byte) (Hotspot.Volume > Atmospherics.CellVolume * 0.4f ? 2 : 1);
}
if (Hotspot.Temperature > MaxFireTemperatureSustained)
MaxFireTemperatureSustained = Hotspot.Temperature;
// TODO ATMOS Maybe destroy location here?
}
public float MaxFireTemperatureSustained { get; private set; }
private void PerformHotspotExposure()
{
if (Air == null || !Hotspot.Valid) return;
Hotspot.Bypassing = Hotspot.SkippedFirstProcess && (Hotspot.Volume > Atmospherics.CellVolume*0.95);
if (Hotspot.Bypassing)
{
Hotspot.Volume = Air.ReactionResults[GasReaction.Fire] * Atmospherics.FireGrowthRate;
Hotspot.Temperature = Air.Temperature;
}
else
{
var affected = Air.RemoveRatio(Hotspot.Volume / Air.Volume);
affected.Temperature = Hotspot.Temperature;
affected.React(this);
Hotspot.Temperature = affected.Temperature;
Hotspot.Volume = affected.ReactionResults[GasReaction.Fire] * Atmospherics.FireGrowthRate;
AssumeAir(affected);
}
var tileRef = GridIndices.GetTileRef(GridIndex);
if (tileRef == null) return;
foreach (var entity in tileRef?.GetEntitiesInTileFast(_gridTileLookupSystem))
{
foreach (var fireAct in entity.GetAllComponents())
{
fireAct.FireAct(Hotspot.Temperature, Hotspot.Volume);
}
}
}
private bool ConsiderSuperconductivity()
{
if (ThermalConductivity == 0f)
return false;
_gridAtmosphereComponent.AddSuperconductivityTile(this);
return true;
}
private bool ConsiderSuperconductivity(bool starting)
{
if (Air.Temperature < (starting
? Atmospherics.MinimumTemperatureStartSuperConduction
: Atmospherics.MinimumTemperatureForSuperconduction))
return false;
return !(Air.HeatCapacity < Atmospherics.MCellWithRatio) && ConsiderSuperconductivity();
}
public void Superconduct()
{
var directions = ConductivityDirections();
for(var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
if (!directions.HasFlag(direction)) continue;
var adjacent = _adjacentTiles[direction.ToIndex()];
// TODO ATMOS handle adjacent being null.
if (adjacent == null || adjacent.ThermalConductivity == 0f)
continue;
if(adjacent._archivedCycle < _gridAtmosphereComponent.UpdateCounter)
adjacent.Archive(_gridAtmosphereComponent.UpdateCounter);
adjacent.NeighborConductWithSource(this);
adjacent.ConsiderSuperconductivity();
}
RadiateToSpace();
FinishSuperconduction();
}
private void FinishSuperconduction()
{
// Conduct with air on my tile if I have it
if (!BlocksAllAir)
{
Temperature = Air.TemperatureShare(ThermalConductivity, Temperature, HeatCapacity);
}
FinishSuperconduction(BlocksAllAir ? Temperature : Air.Temperature);
}
private void FinishSuperconduction(float temperature)
{
// Make sure it's still hot enough to continue conducting.
if (temperature < Atmospherics.MinimumTemperatureForSuperconduction)
{
_gridAtmosphereComponent.RemoveSuperconductivityTile(this);
}
}
private void NeighborConductWithSource(TileAtmosphere other)
{
if (BlocksAllAir)
{
if (!other.BlocksAllAir)
{
other.TemperatureShareOpenToSolid(this);
}
else
{
other.TemperatureShareMutualSolid(this, ThermalConductivity);
}
TemperatureExpose(null, Temperature, _gridAtmosphereComponent.GetVolumeForCells(1));
return;
}
if (!other.BlocksAllAir)
{
other.Air.TemperatureShare(Air, Atmospherics.WindowHeatTransferCoefficient);
}
else
{
TemperatureShareOpenToSolid(other);
}
_gridAtmosphereComponent.AddActiveTile(this);
}
private void TemperatureShareOpenToSolid(TileAtmosphere other)
{
other.Temperature =
Air.TemperatureShare(other.ThermalConductivity, other.Temperature, other.HeatCapacity);
}
private void TemperatureShareMutualSolid(TileAtmosphere other, float conductionCoefficient)
{
var deltaTemperature = (_temperatureArchived - other._temperatureArchived);
if (MathF.Abs(deltaTemperature) > Atmospherics.MinimumTemperatureDeltaToConsider
&& HeatCapacity != 0f && other.HeatCapacity != 0f)
{
var heat = conductionCoefficient * deltaTemperature *
(HeatCapacity * other.HeatCapacity / (HeatCapacity + other.HeatCapacity));
Temperature -= heat / HeatCapacity;
other.Temperature += heat / other.HeatCapacity;
}
}
public void RadiateToSpace()
{
// Considering 0ºC as the break even point for radiation in and out.
if (Temperature > Atmospherics.T0C)
{
// Hardcoded space temperature.
var deltaTemperature = (_temperatureArchived - Atmospherics.TCMB);
if ((HeatCapacity > 0) && (MathF.Abs(deltaTemperature) > Atmospherics.MinimumTemperatureDeltaToConsider))
{
var heat = ThermalConductivity * deltaTemperature * (HeatCapacity *
Atmospherics.HeatCapacityVacuum / (HeatCapacity + Atmospherics.HeatCapacityVacuum));
Temperature -= heat;
}
}
}
public AtmosDirection ConductivityDirections()
{
if(BlocksAllAir)
{
if(_archivedCycle < _gridAtmosphereComponent.UpdateCounter)
Archive(_gridAtmosphereComponent.UpdateCounter);
return AtmosDirection.All;
}
// TODO ATMOS check if this is correct
return AtmosDirection.All;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ExplosivelyDepressurize(int cycleNum)
{
if (Air == null) return;
const int limit = Atmospherics.ZumosTileLimit;
var totalGasesRemoved = 0f;
var queueCycle = ++_gridAtmosphereComponent.EqualizationQueueCycleControl;
var tiles = ArrayPool.Shared.Rent(limit);
var spaceTiles = ArrayPool.Shared.Rent(limit);
var tileCount = 0;
var spaceTileCount = 0;
tiles[tileCount++] = this;
_tileAtmosInfo = new TileAtmosInfo {LastQueueCycle = queueCycle};
for (var i = 0; i < tileCount; i++)
{
var tile = tiles[i];
tile._tileAtmosInfo.LastCycle = cycleNum;
tile._tileAtmosInfo.CurrentTransferDirection = AtmosDirection.Invalid;
if (tile.Air.Immutable)
{
spaceTiles[spaceTileCount++] = tile;
tile.PressureSpecificTarget = tile;
}
else
{
for (var j = 0; j < Atmospherics.Directions; j++)
{
var direction = (AtmosDirection) (1 << j);
if (!tile._adjacentBits.HasFlag(direction)) continue;
var tile2 = tile._adjacentTiles[j];
if (tile2.Air == null) continue;
if (tile2._tileAtmosInfo.LastQueueCycle == queueCycle) continue;
tile.ConsiderFirelocks(tile2);
// The firelocks might have closed on us.
if (!tile._adjacentBits.HasFlag(direction)) continue;
tile2._tileAtmosInfo = new TileAtmosInfo {LastQueueCycle = queueCycle};
tiles[tileCount++] = tile2;
}
}
if (tileCount >= limit || spaceTileCount >= limit)
break;
}
var queueCycleSlow = ++_gridAtmosphereComponent.EqualizationQueueCycleControl;
var progressionOrder = ArrayPool.Shared.Rent(limit * 2);
var progressionCount = 0;
for (var i = 0; i < spaceTileCount; i++)
{
var tile = spaceTiles[i];
progressionOrder[progressionCount++] = tile;
tile._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow;
tile._tileAtmosInfo.CurrentTransferDirection = AtmosDirection.Invalid;
}
for (var i = 0; i < progressionCount; i++)
{
var tile = progressionOrder[i];
for (var j = 0; j < Atmospherics.Directions; j++)
{
var direction = (AtmosDirection) (1 << j);
// TODO ATMOS This is a terrible hack that accounts for the mess that are space TileAtmospheres.
if (!tile._adjacentBits.HasFlag(direction) && !tile.Air.Immutable) continue;
var tile2 = tile._adjacentTiles[j];
if (tile2?._tileAtmosInfo.LastQueueCycle != queueCycle) continue;
if (tile2._tileAtmosInfo.LastSlowQueueCycle == queueCycleSlow) continue;
if(tile2.Air?.Immutable ?? false) continue;
tile2._tileAtmosInfo.CurrentTransferDirection = direction.GetOpposite();
tile2._tileAtmosInfo.CurrentTransferAmount = 0;
tile2.PressureSpecificTarget = tile.PressureSpecificTarget;
tile2._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow;
progressionOrder[progressionCount++] = tile2;
}
}
for (var i = progressionCount - 1; i >= 0; i--)
{
var tile = progressionOrder[i];
if (tile._tileAtmosInfo.CurrentTransferDirection == AtmosDirection.Invalid) continue;
_gridAtmosphereComponent.AddHighPressureDelta(tile);
_gridAtmosphereComponent.AddActiveTile(tile);
var tile2 = tile._adjacentTiles[tile._tileAtmosInfo.CurrentTransferDirection.ToIndex()];
if (tile2?.Air == null) continue;
var sum = tile2.Air.TotalMoles;
totalGasesRemoved += sum;
tile._tileAtmosInfo.CurrentTransferAmount += sum;
tile2._tileAtmosInfo.CurrentTransferAmount += tile._tileAtmosInfo.CurrentTransferAmount;
tile.PressureDifference = tile._tileAtmosInfo.CurrentTransferAmount;
tile._pressureDirection = tile._tileAtmosInfo.CurrentTransferDirection;
if (tile2._tileAtmosInfo.CurrentTransferDirection == AtmosDirection.Invalid)
{
tile2.PressureDifference = tile2._tileAtmosInfo.CurrentTransferAmount;
tile2._pressureDirection = tile._tileAtmosInfo.CurrentTransferDirection;
}
tile.Air.Clear();
tile.UpdateVisuals();
tile.HandleDecompressionFloorRip(sum);
}
ArrayPool.Shared.Return(tiles);
ArrayPool.Shared.Return(spaceTiles);
ArrayPool.Shared.Return(progressionOrder);
}
private void HandleDecompressionFloorRip(float sum)
{
var chance = MathHelper.Clamp(sum / 500, 0.005f, 0.5f);
if (sum > 20 && _robustRandom.Prob(chance))
_gridAtmosphereComponent.PryTile(GridIndices);
}
private void ConsiderFirelocks(TileAtmosphere other)
{
var reconsiderAdjacent = false;
foreach (var entity in GridIndices.GetEntitiesInTileFast(GridIndex, _gridAtmosphereComponent.GridTileLookupSystem))
{
if (!entity.TryGetComponent(out FirelockComponent firelock)) continue;
reconsiderAdjacent |= firelock.EmergencyPressureStop();
}
foreach (var entity in other.GridIndices.GetEntitiesInTileFast(other.GridIndex, _gridAtmosphereComponent.GridTileLookupSystem))
{
if (!entity.TryGetComponent(out FirelockComponent firelock)) continue;
reconsiderAdjacent |= firelock.EmergencyPressureStop();
}
if (reconsiderAdjacent)
{
UpdateAdjacent();
other.UpdateAdjacent();
}
}
private void React()
{
// TODO ATMOS I think this is enough? gotta make sure...
Air?.React(this);
}
public bool AssumeAir(GasMixture giver)
{
if (giver == null || Air == null) return false;
Air.Merge(giver);
UpdateVisuals();
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UpdateVisuals()
{
if (Air == null) return;
_gasTileOverlaySystem ??= EntitySystem.Get();
_gasTileOverlaySystem.Invalidate(GridIndex, GridIndices);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UpdateAdjacent()
{
for (var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
var otherIndices = GridIndices.Offset(direction.ToDirection());
var isSpace = _gridAtmosphereComponent.IsSpace(GridIndices);
var adjacent = _gridAtmosphereComponent.GetTile(otherIndices, !isSpace);
_adjacentTiles[direction.ToIndex()] = adjacent;
adjacent?.UpdateAdjacent(direction.GetOpposite());
if (adjacent != null && !BlockedAirflow.HasFlag(direction) && !_gridAtmosphereComponent.IsAirBlocked(adjacent.GridIndices, direction.GetOpposite()))
{
_adjacentBits |= direction;
}
}
}
public void UpdateAdjacent(AtmosDirection direction)
{
_adjacentTiles[direction.ToIndex()] = _gridAtmosphereComponent.GetTile(GridIndices.Offset(direction.ToDirection()));
if (!BlockedAirflow.HasFlag(direction) && !_gridAtmosphereComponent.IsAirBlocked(GridIndices.Offset(direction.ToDirection()), direction.GetOpposite()))
{
_adjacentBits |= direction;
}
else
{
_adjacentBits &= ~direction;
}
}
private void LastShareCheck()
{
var lastShare = Air.LastShare;
if (lastShare > Atmospherics.MinimumAirToSuspend)
{
ExcitedGroup.ResetCooldowns();
AtmosCooldown = 0;
} else if (lastShare > Atmospherics.MinimumMolesDeltaToMove)
{
ExcitedGroup.DismantleCooldown = 0;
AtmosCooldown = 0;
}
}
public void TemperatureExpose(GasMixture air, float temperature, float volume)
{
// TODO ATMOS do this
}
}
}