From ca68fbe818902318eb6666298527f1c638782728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= <6766154+Zumorica@users.noreply.github.com> Date: Thu, 13 Aug 2020 14:18:26 +0200 Subject: [PATCH] Add heat conduction (#1653) --- Content.Server/Atmos/GasMixture.cs | 29 ++- .../Atmos/IGridAtmosphereComponent.cs | 12 ++ Content.Server/Atmos/TileAtmosphere.cs | 176 +++++++++++++++++- .../Atmos/GridAtmosphereComponent.cs | 41 +++- Content.Shared/Atmos/Atmospherics.cs | 13 ++ Content.Shared/Maps/ContentTileDefinition.cs | 10 + Content.Shared/Maps/TurfHelpers.cs | 28 +++ SpaceStation14.sln.DotSettings | 1 + 8 files changed, 301 insertions(+), 9 deletions(-) diff --git a/Content.Server/Atmos/GasMixture.cs b/Content.Server/Atmos/GasMixture.cs index 1455402779..f886acad84 100644 --- a/Content.Server/Atmos/GasMixture.cs +++ b/Content.Server/Atmos/GasMixture.cs @@ -330,7 +330,7 @@ namespace Content.Server.Atmos } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void TemperatureShare(GasMixture sharer, float conductionCoefficient) + public float TemperatureShare(GasMixture sharer, float conductionCoefficient) { var temperatureDelta = TemperatureArchived - sharer.TemperatureArchived; if (MathF.Abs(temperatureDelta) > Atmospherics.MinimumTemperatureDeltaToConsider) @@ -349,6 +349,30 @@ namespace Content.Server.Atmos sharer.Temperature = MathF.Abs(MathF.Max(sharer.Temperature + heat / sharerHeatCapacity, Atmospherics.TCMB)); } } + + return sharer.Temperature; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float TemperatureShare(float conductionCoefficient, float sharerTemperature, float sharerHeatCapacity) + { + var temperatureDelta = TemperatureArchived - sharerTemperature; + if (MathF.Abs(temperatureDelta) > Atmospherics.MinimumTemperatureDeltaToConsider) + { + var heatCapacity = HeatCapacityArchived; + + if (sharerHeatCapacity > Atmospherics.MinimumHeatCapacity && heatCapacity > Atmospherics.MinimumHeatCapacity) + { + var heat = conductionCoefficient * temperatureDelta * (heatCapacity * sharerHeatCapacity / (heatCapacity + sharerHeatCapacity)); + + if (!Immutable) + Temperature = MathF.Abs(MathF.Max(Temperature - heat / heatCapacity, Atmospherics.TCMB)); + + sharerTemperature = MathF.Abs(MathF.Max(sharerTemperature + heat / sharerHeatCapacity, Atmospherics.TCMB)); + } + } + + return sharerTemperature; } public enum GasCompareResult @@ -357,6 +381,9 @@ namespace Content.Server.Atmos TemperatureExchange = -1, } + /// + /// Compares sample to self to see if within acceptable ranges that group processing may be enabled. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public GasCompareResult Compare(GasMixture sample) { diff --git a/Content.Server/Atmos/IGridAtmosphereComponent.cs b/Content.Server/Atmos/IGridAtmosphereComponent.cs index 5fdd25b2e3..430c674bce 100644 --- a/Content.Server/Atmos/IGridAtmosphereComponent.cs +++ b/Content.Server/Atmos/IGridAtmosphereComponent.cs @@ -66,6 +66,18 @@ namespace Content.Server.Atmos /// void RemoveHotspotTile(TileAtmosphere tile); + /// + /// Marks a tile as superconductive so it can be processed. + /// + /// + void AddSuperconductivityTile(TileAtmosphere tile); + + /// + /// Removes a tile from the superconductivity processing list. + /// + /// + void RemoveSuperconductivityTile(TileAtmosphere tile); + /// /// Marks a tile has having high pressure differences that need to be equalized. /// diff --git a/Content.Server/Atmos/TileAtmosphere.cs b/Content.Server/Atmos/TileAtmosphere.cs index dfb3ad675f..69d74ce995 100644 --- a/Content.Server/Atmos/TileAtmosphere.cs +++ b/Content.Server/Atmos/TileAtmosphere.cs @@ -7,6 +7,7 @@ using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces; using Content.Shared.Atmos; using Content.Shared.Audio; +using Content.Shared.Maps; using Robust.Server.GameObjects.EntitySystems; using Robust.Shared.GameObjects.Components; using Robust.Shared.GameObjects.Systems; @@ -27,9 +28,21 @@ namespace Content.Server.Atmos [Robust.Shared.IoC.Dependency] private IEntityManager _entityManager = default!; [Robust.Shared.IoC.Dependency] private IMapManager _mapManager = default!; + [ViewVariables] private int _archivedCycle = 0; + + [ViewVariables] private int _currentCycle = 0; + [ViewVariables] + private static GasTileOverlaySystem _gasTileOverlaySystem; + + [ViewVariables] + private float _temperature = 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 = 0; @@ -39,6 +52,12 @@ namespace Content.Server.Atmos [ViewVariables] public float PressureDifference { get; set; } = 0; + [ViewVariables(VVAccess.ReadWrite)] + public float HeatCapacity { get; set; } = 1f; + + [ViewVariables] + public float ThermalConductivity => Tile?.Tile.GetContentTileDefinition().ThermalConductivity ?? 0.05f; + [ViewVariables] public bool Excited { get; set; } = false; @@ -54,11 +73,15 @@ namespace Content.Server.Atmos [ViewVariables] public Hotspot Hotspot; + [ViewVariables] private Direction _pressureDirection; [ViewVariables] public GridId GridIndex { get; } + [ViewVariables] + public TileRef? Tile => GridIndices.GetTileRef(GridIndex); + [ViewVariables] public MapIndices GridIndices { get; } @@ -68,6 +91,9 @@ namespace Content.Server.Atmos [ViewVariables] public GasMixture Air { get; set; } + [ViewVariables] + public bool BlocksAir => _gridAtmosphereComponent.IsAirBlocked(GridIndices); + public TileAtmosphere(GridAtmosphereComponent atmosphereComponent, GridId gridIndex, MapIndices gridIndices, GasMixture mixture = null) { IoCManager.InjectDependencies(this); @@ -80,8 +106,9 @@ namespace Content.Server.Atmos [MethodImpl(MethodImplOptions.AggressiveInlining)] private void Archive(int fireCount) { - _archivedCycle = fireCount; Air?.Archive(); + _archivedCycle = fireCount; + _temperatureArchived = _temperature; } public void HotspotExpose(float exposedTemperature, float exposedVolume, bool soh = false) @@ -704,10 +731,150 @@ namespace Content.Server.Atmos // TODO ATMOS Let all entities in this tile know about the fire? } + private bool ConsiderSuperconductivity() + { + if (ThermalConductivity == 0f) + return false; + + _gridAtmosphereComponent.AddSuperconductivityTile(this); + return true; + } + private bool ConsiderSuperconductivity(bool starting) { - // TODO ATMOS - return false; + if (Air.Temperature < (starting + ? Atmospherics.MinimumTemperatureStartSuperConduction + : Atmospherics.MinimumTemperatureForSuperconduction)) + return false; + + return !(Air.HeatCapacity < Atmospherics.MCellWithRatio) && ConsiderSuperconductivity(); + } + + public void Superconduct() + { + var directions = ConductivityDirections(); + var adjacentTiles = _gridAtmosphereComponent.GetAdjacentTiles(GridIndices, true); + + if (directions.Length > 0) + { + foreach (var direction in directions) + { + if (!adjacentTiles.TryGetValue(direction, out var adjacent)) continue; + + if (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 (!BlocksAir) + { + _temperature = Air.TemperatureShare(ThermalConductivity, _temperature, HeatCapacity); + } + + FinishSuperconduction(BlocksAir ? _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 (BlocksAir) + { + if (!other.BlocksAir) + { + other.TemperatureShareOpenToSolid(this); + } + else + { + other.TemperatureShareMutualSolid(this, ThermalConductivity); + } + + TemperatureExpose(null, _temperature, _gridAtmosphereComponent.GetVolumeForCells(1)); + return; + } + + if (!other.BlocksAir) + { + 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 Direction[] ConductivityDirections() + { + if(BlocksAir) + { + if(_archivedCycle < _gridAtmosphereComponent.UpdateCounter) + Archive(_gridAtmosphereComponent.UpdateCounter); + return Cardinal; + } + + // TODO ATMOS check if this is correct + return Cardinal; } //[MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -820,7 +987,6 @@ namespace Content.Server.Atmos //throw new NotImplementedException(); } - private void React() { // TODO ATMOS I think this is enough? gotta make sure... @@ -880,8 +1046,6 @@ namespace Content.Server.Atmos Direction.North, Direction.East, Direction.South, Direction.West }; - private static GasTileOverlaySystem _gasTileOverlaySystem; - public void TemperatureExpose(GasMixture mixture, float temperature, float cellVolume) { // TODO ATMOS do this diff --git a/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs b/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs index ea05c19642..c7207ffa4e 100644 --- a/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs @@ -65,6 +65,9 @@ namespace Content.Server.GameObjects.Components.Atmos [ViewVariables] private readonly HashSet _hotspotTiles = new HashSet(1000); + [ViewVariables] + private readonly HashSet _superconductivityTiles = new HashSet(1000); + [ViewVariables] private readonly HashSet _invalidatedCoords = new HashSet(1000); @@ -81,6 +84,7 @@ namespace Content.Server.GameObjects.Components.Atmos ExcitedGroups, HighPressureDelta, Hotspots, + Superconductivity, } /// @@ -237,6 +241,18 @@ namespace Content.Server.GameObjects.Components.Atmos _hotspotTiles.Remove(tile); } + public void AddSuperconductivityTile(TileAtmosphere tile) + { + if (tile?.GridIndex != _grid.Index) return; + _superconductivityTiles.Add(tile); + } + + public void RemoveSuperconductivityTile(TileAtmosphere tile) + { + if (tile == null) return; + _superconductivityTiles.Remove(tile); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AddHighPressureDelta(TileAtmosphere tile) @@ -302,14 +318,14 @@ namespace Content.Server.GameObjects.Components.Atmos return _grid.GetTileRef(indices).Tile.IsEmpty; } - public Dictionary GetAdjacentTiles(MapIndices indices) + public Dictionary GetAdjacentTiles(MapIndices indices, bool includeAirBlocked = false) { var sides = new Dictionary(); foreach (var dir in Cardinal) { var side = indices.Offset(dir); var tile = GetTile(side); - if(tile?.Air != null) + if(tile?.Air != null || includeAirBlocked) sides[dir] = tile; } @@ -361,6 +377,10 @@ namespace Content.Server.GameObjects.Components.Atmos break; case ProcessState.Hotspots: ProcessHotspots(); + _state = ProcessState.Superconductivity; + break; + case ProcessState.Superconductivity: + ProcessSuperconductivity(); _state = ProcessState.TileEqualize; break; } @@ -463,6 +483,23 @@ namespace Content.Server.GameObjects.Components.Atmos } } + private void ProcessSuperconductivity() + { + _stopwatch.Restart(); + + var number = 0; + foreach (var superconductivity in _superconductivityTiles.ToArray()) + { + superconductivity.Superconduct(); + + if (number++ < LagCheckIterations) continue; + number = 0; + // Process the rest next time. + if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds) + return; + } + } + private AirtightComponent GetObstructingComponent(MapIndices indices) { foreach (var v in _grid.GetSnapGridCell(indices, SnapGridOffset.Center)) diff --git a/Content.Shared/Atmos/Atmospherics.cs b/Content.Shared/Atmos/Atmospherics.cs index 4659ab2441..2b1e2a5dc6 100644 --- a/Content.Shared/Atmos/Atmospherics.cs +++ b/Content.Shared/Atmos/Atmospherics.cs @@ -63,6 +63,11 @@ namespace Content.Shared.Atmos /// public const float MolesCellStandard = (OneAtmosphere * CellVolume / (T20C * R)); + /// + /// Compared against for superconduction. + /// + public const float MCellWithRatio = (MolesCellStandard * 0.005f); + public const float OxygenStandard = 0.21f; public const float NitrogenStandard = 0.79f; @@ -83,6 +88,11 @@ namespace Content.Shared.Atmos public const float OpenHeatTransferCoefficient = 0.4f; + /// + /// Hack to make vacuums cold, sacrificing realism for gameplay. + /// + public const float HeatCapacityVacuum = 7000f; + /// /// Ratio of air that must move to/from a tile to reset group processing /// @@ -116,6 +126,7 @@ namespace Content.Shared.Atmos /// Minimum temperature for starting superconduction. /// public const float MinimumTemperatureStartSuperConduction = (T20C + 200f); + public const float MinimumTemperatureForSuperconduction = (T20C + 10f); /// /// Minimum heat capacity. @@ -214,6 +225,8 @@ namespace Content.Shared.Atmos /// so it just applies this flat value). /// public const int LowPressureDamage = 4; + + public const float WindowHeatTransferCoefficient = 0.1f; } /// diff --git a/Content.Shared/Maps/ContentTileDefinition.cs b/Content.Shared/Maps/ContentTileDefinition.cs index 419a1bd812..c00e62fa87 100644 --- a/Content.Shared/Maps/ContentTileDefinition.cs +++ b/Content.Shared/Maps/ContentTileDefinition.cs @@ -26,6 +26,7 @@ namespace Content.Shared.Maps public bool CanCrowbar { get; private set; } public string FootstepSounds { get; private set; } public float Friction { get; set; } + public float ThermalConductivity { get; set; } public string ItemDropPrototypeName { get; private set; } public void AssignTileId(ushort id) @@ -68,6 +69,15 @@ namespace Content.Shared.Maps Friction = 0; } + if (mapping.TryGetNode("thermalConductivity", out node)) + { + ThermalConductivity = node.AsFloat(); + } + else + { + ThermalConductivity = 0.05f; + } + if (mapping.TryGetNode("item_drop", out node)) { ItemDropPrototypeName = node.ToString(); diff --git a/Content.Shared/Maps/TurfHelpers.cs b/Content.Shared/Maps/TurfHelpers.cs index 1f81bc6f8f..aa60e7168c 100644 --- a/Content.Shared/Maps/TurfHelpers.cs +++ b/Content.Shared/Maps/TurfHelpers.cs @@ -12,6 +12,34 @@ namespace Content.Shared.Maps { public static class TurfHelpers { + /// + /// Returns the content tile definition for a tile. + /// + public static ContentTileDefinition GetContentTileDefinition(this Tile tile) + { + var tileDefinitionManager = IoCManager.Resolve(); + return (ContentTileDefinition)tileDefinitionManager[tile.TypeId]; + } + + /// + /// Attempts to get the turf at map indices with grid id or null if no such turf is found. + /// + public static TileRef? GetTileRef(this MapIndices mapIndices, GridId gridId) + { + if (!gridId.IsValid()) + return null; + + var mapManager = IoCManager.Resolve(); + + if (!mapManager.TryGetGrid(gridId, out var grid)) + return null; + + if (!grid.TryGetTileRef(mapIndices, out var tile)) + return null; + + return tile; + } + /// /// Attempts to get the turf at a certain coordinates or null if no such turf is found. /// diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index bd8ed2aff7..f9a53158a2 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -72,6 +72,7 @@ True True True + True True True True