Gas overlay chunking (#1678)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
This commit is contained in:
@@ -40,7 +40,12 @@ namespace Content.Client.Atmos
|
|||||||
|
|
||||||
foreach (var mapGrid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
|
foreach (var mapGrid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
|
||||||
{
|
{
|
||||||
foreach (var tile in mapGrid.GetTilesIntersecting(worldBounds))
|
if (!_gasTileOverlaySystem.HasData(mapGrid.Index))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var gridBounds = new Box2(mapGrid.WorldToLocal(worldBounds.BottomLeft), mapGrid.WorldToLocal(worldBounds.TopRight));
|
||||||
|
|
||||||
|
foreach (var tile in mapGrid.GetTilesIntersecting(gridBounds))
|
||||||
{
|
{
|
||||||
foreach (var (texture, color) in _gasTileOverlaySystem.GetOverlays(mapGrid.Index, tile.GridIndices))
|
foreach (var (texture, color) in _gasTileOverlaySystem.GetOverlays(mapGrid.Index, tile.GridIndices))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
using Content.Client.Atmos;
|
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
using Robust.Client.Interfaces.Graphics.Overlays;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
|
|
||||||
namespace Content.Client.GameObjects.Components.Atmos
|
|
||||||
{
|
|
||||||
[RegisterComponent]
|
|
||||||
public class CanSeeGasesComponent : Component
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
|
||||||
|
|
||||||
public override string Name => "CanSeeGases";
|
|
||||||
|
|
||||||
public override void HandleMessage(ComponentMessage message, IComponent component)
|
|
||||||
{
|
|
||||||
base.HandleMessage(message, component);
|
|
||||||
|
|
||||||
switch (message)
|
|
||||||
{
|
|
||||||
case PlayerAttachedMsg _:
|
|
||||||
if(!_overlayManager.HasOverlay(nameof(GasTileOverlay)))
|
|
||||||
_overlayManager.AddOverlay(new GasTileOverlay());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PlayerDetachedMsg _:
|
|
||||||
if(!_overlayManager.HasOverlay(nameof(GasTileOverlay)))
|
|
||||||
_overlayManager.RemoveOverlay(nameof(GasTileOverlay));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,19 @@
|
|||||||
using System;
|
#nullable enable
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Content.Client.Atmos;
|
||||||
|
using Content.Client.GameObjects.Components.Atmos;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems.Atmos;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.GameObjects.EntitySystems;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.Interfaces.Graphics.Overlays;
|
||||||
using Robust.Client.Interfaces.ResourceManagement;
|
using Robust.Client.Interfaces.ResourceManagement;
|
||||||
using Robust.Client.ResourceManagement;
|
using Robust.Client.ResourceManagement;
|
||||||
using Robust.Client.Utility;
|
using Robust.Client.Utility;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.Interfaces.Map;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
@@ -16,8 +22,9 @@ using Robust.Shared.Utility;
|
|||||||
namespace Content.Client.GameObjects.EntitySystems
|
namespace Content.Client.GameObjects.EntitySystems
|
||||||
{
|
{
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
|
internal sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||||
|
|
||||||
private readonly Dictionary<float, Color> _fireCache = new Dictionary<float, Color>();
|
private readonly Dictionary<float, Color> _fireCache = new Dictionary<float, Color>();
|
||||||
@@ -36,19 +43,20 @@ namespace Content.Client.GameObjects.EntitySystems
|
|||||||
private readonly float[][] _fireFrameDelays = new float[FireStates][];
|
private readonly float[][] _fireFrameDelays = new float[FireStates][];
|
||||||
private readonly int[] _fireFrameCounter = new int[FireStates];
|
private readonly int[] _fireFrameCounter = new int[FireStates];
|
||||||
private readonly Texture[][] _fireFrames = new Texture[FireStates][];
|
private readonly Texture[][] _fireFrames = new Texture[FireStates][];
|
||||||
|
|
||||||
private Dictionary<GridId, Dictionary<MapIndices, GasOverlayData>> _overlay = new Dictionary<GridId, Dictionary<MapIndices, GasOverlayData>>();
|
private Dictionary<GridId, Dictionary<MapIndices, GasOverlayChunk>> _tileData =
|
||||||
|
new Dictionary<GridId, Dictionary<MapIndices, GasOverlayChunk>>();
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
SubscribeNetworkEvent<GasOverlayMessage>(HandleGasOverlayMessage);
|
||||||
SubscribeNetworkEvent(new EntityEventHandler<GasTileOverlayMessage>(OnTileOverlayMessage));
|
_mapManager.OnGridRemoved += OnGridRemoved;
|
||||||
|
|
||||||
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
||||||
{
|
{
|
||||||
var gas = Atmospherics.GetGas(i);
|
var overlay = Atmospherics.GetOverlay(i);
|
||||||
switch (gas.GasOverlay)
|
switch (overlay)
|
||||||
{
|
{
|
||||||
case SpriteSpecifier.Rsi animated:
|
case SpriteSpecifier.Rsi animated:
|
||||||
var rsi = _resourceCache.GetResource<RSIResource>(animated.RsiPath).RSI;
|
var rsi = _resourceCache.GetResource<RSIResource>(animated.RsiPath).RSI;
|
||||||
@@ -82,13 +90,77 @@ namespace Content.Client.GameObjects.EntitySystems
|
|||||||
_fireFrameDelays[i] = state.GetDelays();
|
_fireFrameDelays[i] = state.GetDelays();
|
||||||
_fireFrameCounter[i] = 0;
|
_fireFrameCounter[i] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||||
|
if(!overlayManager.HasOverlay(nameof(GasTileOverlay)))
|
||||||
|
overlayManager.AddOverlay(new GasTileOverlay());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleGasOverlayMessage(GasOverlayMessage message)
|
||||||
|
{
|
||||||
|
foreach (var (indices, data) in message.OverlayData)
|
||||||
|
{
|
||||||
|
var chunk = GetOrCreateChunk(message.GridId, indices);
|
||||||
|
chunk.Update(data, indices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slightly different to the server-side system version
|
||||||
|
private GasOverlayChunk GetOrCreateChunk(GridId gridId, MapIndices indices)
|
||||||
|
{
|
||||||
|
if (!_tileData.TryGetValue(gridId, out var chunks))
|
||||||
|
{
|
||||||
|
chunks = new Dictionary<MapIndices, GasOverlayChunk>();
|
||||||
|
_tileData[gridId] = chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chunkIndices = GetGasChunkIndices(indices);
|
||||||
|
|
||||||
|
if (!chunks.TryGetValue(chunkIndices, out var chunk))
|
||||||
|
{
|
||||||
|
chunk = new GasOverlayChunk(gridId, chunkIndices);
|
||||||
|
chunks[chunkIndices] = chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
_mapManager.OnGridRemoved -= OnGridRemoved;
|
||||||
|
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||||
|
if(!overlayManager.HasOverlay(nameof(GasTileOverlay)))
|
||||||
|
overlayManager.RemoveOverlay(nameof(GasTileOverlay));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGridRemoved(GridId gridId)
|
||||||
|
{
|
||||||
|
if (_tileData.ContainsKey(gridId))
|
||||||
|
{
|
||||||
|
_tileData.Remove(gridId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasData(GridId gridId)
|
||||||
|
{
|
||||||
|
return _tileData.ContainsKey(gridId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public (Texture, Color color)[] GetOverlays(GridId gridIndex, MapIndices indices)
|
public (Texture, Color color)[] GetOverlays(GridId gridIndex, MapIndices indices)
|
||||||
{
|
{
|
||||||
if (!_overlay.TryGetValue(gridIndex, out var tiles) || !tiles.TryGetValue(indices, out var overlays))
|
if (!_tileData.TryGetValue(gridIndex, out var chunks))
|
||||||
return Array.Empty<(Texture, Color)>();
|
return Array.Empty<(Texture, Color)>();
|
||||||
|
|
||||||
|
var chunkIndex = GetGasChunkIndices(indices);
|
||||||
|
if (!chunks.TryGetValue(chunkIndex, out var chunk))
|
||||||
|
return Array.Empty<(Texture, Color)>();
|
||||||
|
|
||||||
|
var overlays = chunk.GetData(indices);
|
||||||
|
|
||||||
|
if (overlays.Gas == null)
|
||||||
|
return Array.Empty<(Texture, Color)>();
|
||||||
|
|
||||||
var fire = overlays.FireState != 0;
|
var fire = overlays.FireState != 0;
|
||||||
var length = overlays.Gas.Length + (fire ? 1 : 0);
|
var length = overlays.Gas.Length + (fire ? 1 : 0);
|
||||||
|
|
||||||
@@ -112,23 +184,6 @@ namespace Content.Client.GameObjects.EntitySystems
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTileOverlayMessage(GasTileOverlayMessage ev)
|
|
||||||
{
|
|
||||||
if(ev.ClearAllOtherOverlays)
|
|
||||||
_overlay.Clear();
|
|
||||||
|
|
||||||
foreach (var data in ev.OverlayData)
|
|
||||||
{
|
|
||||||
if (!_overlay.TryGetValue(data.GridIndex, out var gridOverlays))
|
|
||||||
{
|
|
||||||
gridOverlays = new Dictionary<MapIndices, GasOverlayData>();
|
|
||||||
_overlay.Add(data.GridIndex, gridOverlays);
|
|
||||||
}
|
|
||||||
|
|
||||||
gridOverlays[data.GridIndices] = data.Data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void FrameUpdate(float frameTime)
|
public override void FrameUpdate(float frameTime)
|
||||||
{
|
{
|
||||||
base.FrameUpdate(frameTime);
|
base.FrameUpdate(frameTime);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace Content.Server.Atmos
|
|||||||
/// State for the fire sprite.
|
/// State for the fire sprite.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public int State;
|
public byte State;
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
|
|||||||
using Content.Server.Atmos.Reactions;
|
using Content.Server.Atmos.Reactions;
|
||||||
using Content.Server.GameObjects.Components.Atmos;
|
using Content.Server.GameObjects.Components.Atmos;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Server.GameObjects.EntitySystems.Atmos;
|
||||||
using Content.Server.Interfaces;
|
using Content.Server.Interfaces;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Audio;
|
using Content.Shared.Audio;
|
||||||
@@ -745,7 +746,7 @@ namespace Content.Server.Atmos
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Hotspot.State = Hotspot.Volume > Atmospherics.CellVolume * 0.4f ? 2 : 1;
|
Hotspot.State = (byte) (Hotspot.Volume > Atmospherics.CellVolume * 0.4f ? 2 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Hotspot.Temperature > MaxFireTemperatureSustained)
|
if (Hotspot.Temperature > MaxFireTemperatureSustained)
|
||||||
|
|||||||
@@ -0,0 +1,475 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using Content.Server.GameObjects.Components.Atmos;
|
||||||
|
using Content.Server.Interfaces.GameTicking;
|
||||||
|
using Content.Shared.Atmos;
|
||||||
|
using Content.Shared.GameObjects.EntitySystems.Atmos;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Server.Interfaces.Player;
|
||||||
|
using Robust.Server.Player;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
|
using Robust.Shared.Interfaces.Configuration;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Map;
|
||||||
|
using Robust.Shared.Interfaces.Timing;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.EntitySystems.Atmos
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
internal sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
|
||||||
|
{
|
||||||
|
[Robust.Shared.IoC.Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
[Robust.Shared.IoC.Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
[Robust.Shared.IoC.Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
|
[Robust.Shared.IoC.Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The tiles that have had their atmos data updated since last tick
|
||||||
|
/// </summary>
|
||||||
|
private Dictionary<GridId, HashSet<MapIndices>> _invalidTiles = new Dictionary<GridId, HashSet<MapIndices>>();
|
||||||
|
|
||||||
|
private Dictionary<IPlayerSession, PlayerGasOverlay> _knownPlayerChunks =
|
||||||
|
new Dictionary<IPlayerSession, PlayerGasOverlay>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gas data stored in chunks to make PVS / bubbling easier.
|
||||||
|
/// </summary>
|
||||||
|
private Dictionary<GridId, Dictionary<MapIndices, GasOverlayChunk>> _overlay =
|
||||||
|
new Dictionary<GridId, Dictionary<MapIndices, GasOverlayChunk>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How far away do we update gas overlays (minimum; due to chunking further away tiles may also be updated).
|
||||||
|
/// </summary>
|
||||||
|
private float _updateRange;
|
||||||
|
// Because the gas overlay updates aren't run every tick we need to avoid the pop-in that might occur with
|
||||||
|
// the regular PVS range.
|
||||||
|
private const float RangeOffset = 6.0f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overlay update ticks per second.
|
||||||
|
/// </summary>
|
||||||
|
private float _updateCooldown;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||||
|
_mapManager.OnGridRemoved += OnGridRemoved;
|
||||||
|
_configManager.RegisterCVar("net.gasoverlaytickrate", 3.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
|
||||||
|
_mapManager.OnGridRemoved -= OnGridRemoved;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Invalidate(GridId gridIndex, MapIndices indices)
|
||||||
|
{
|
||||||
|
if (!_invalidTiles.TryGetValue(gridIndex, out var existing))
|
||||||
|
{
|
||||||
|
existing = new HashSet<MapIndices>();
|
||||||
|
_invalidTiles[gridIndex] = existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
existing.Add(indices);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GasOverlayChunk GetOrCreateChunk(GridId gridIndex, MapIndices indices)
|
||||||
|
{
|
||||||
|
if (!_overlay.TryGetValue(gridIndex, out var chunks))
|
||||||
|
{
|
||||||
|
chunks = new Dictionary<MapIndices, GasOverlayChunk>();
|
||||||
|
_overlay[gridIndex] = chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chunkIndices = GetGasChunkIndices(indices);
|
||||||
|
|
||||||
|
if (!chunks.TryGetValue(chunkIndices, out var chunk))
|
||||||
|
{
|
||||||
|
chunk = new GasOverlayChunk(gridIndex, chunkIndices);
|
||||||
|
chunks[chunkIndices] = chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGridRemoved(GridId gridId)
|
||||||
|
{
|
||||||
|
if (_overlay.ContainsKey(gridId))
|
||||||
|
{
|
||||||
|
_overlay.Remove(gridId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResettingCleanup()
|
||||||
|
{
|
||||||
|
_invalidTiles.Clear();
|
||||||
|
_overlay.Clear();
|
||||||
|
|
||||||
|
foreach (var (_, data) in _knownPlayerChunks)
|
||||||
|
{
|
||||||
|
data.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.NewStatus != SessionStatus.InGame)
|
||||||
|
{
|
||||||
|
if (_knownPlayerChunks.ContainsKey(e.Session))
|
||||||
|
{
|
||||||
|
_knownPlayerChunks.Remove(e.Session);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_knownPlayerChunks.ContainsKey(e.Session))
|
||||||
|
{
|
||||||
|
_knownPlayerChunks[e.Session] = new PlayerGasOverlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether the overlay-relevant data for a gas tile has been updated.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="gam"></param>
|
||||||
|
/// <param name="oldTile"></param>
|
||||||
|
/// <param name="indices"></param>
|
||||||
|
/// <param name="overlayData"></param>
|
||||||
|
/// <returns>true if updated</returns>
|
||||||
|
private bool TryRefreshTile(GridAtmosphereComponent gam, GasOverlayData oldTile, MapIndices indices, out GasOverlayData overlayData)
|
||||||
|
{
|
||||||
|
var tile = gam.GetTile(indices);
|
||||||
|
var tileData = new List<GasData>();
|
||||||
|
|
||||||
|
for (byte i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
||||||
|
{
|
||||||
|
var gas = Atmospherics.GetGas(i);
|
||||||
|
var overlay = Atmospherics.GetOverlay(i);
|
||||||
|
if (overlay == null || tile.Air == null) continue;
|
||||||
|
|
||||||
|
var moles = tile.Air.Gases[i];
|
||||||
|
|
||||||
|
if (moles < gas.GasMolesVisible) continue;
|
||||||
|
|
||||||
|
var data = new GasData(i, (byte) (FloatMath.Clamp01(moles / gas.GasMolesVisibleMax) * 255));
|
||||||
|
tileData.Add(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
overlayData = new GasOverlayData(tile.Hotspot.State, tile.Hotspot.Temperature, tileData.Count == 0 ? null : tileData.ToArray());
|
||||||
|
|
||||||
|
if (overlayData.Equals(oldTile))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get every chunk in range of our entity that exists, including on other grids.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private List<GasOverlayChunk> GetChunksInRange(IEntity entity)
|
||||||
|
{
|
||||||
|
var inRange = new List<GasOverlayChunk>();
|
||||||
|
|
||||||
|
// This is the max in any direction that we can get a chunk (e.g. max 2 chunks away of data).
|
||||||
|
var (maxXDiff, maxYDiff) = ((int) (_updateRange / ChunkSize) + 1, (int) (_updateRange / ChunkSize) + 1);
|
||||||
|
|
||||||
|
var worldBounds = Box2.CenteredAround(entity.Transform.WorldPosition,
|
||||||
|
new Vector2(_updateRange, _updateRange));
|
||||||
|
|
||||||
|
foreach (var grid in _mapManager.FindGridsIntersecting(entity.Transform.MapID, worldBounds))
|
||||||
|
{
|
||||||
|
if (!_overlay.TryGetValue(grid.Index, out var chunks))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entityTile = grid.GetTileRef(entity.Transform.GridPosition).GridIndices;
|
||||||
|
|
||||||
|
for (var x = -maxXDiff; x <= maxXDiff; x++)
|
||||||
|
{
|
||||||
|
for (var y = -maxYDiff; y <= maxYDiff; y++)
|
||||||
|
{
|
||||||
|
var chunkIndices = GetGasChunkIndices(new MapIndices(entityTile.X + x * ChunkSize, entityTile.Y + y * ChunkSize));
|
||||||
|
|
||||||
|
if (!chunks.TryGetValue(chunkIndices, out var chunk)) continue;
|
||||||
|
|
||||||
|
// Now we'll check if it's in range and relevant for us
|
||||||
|
// (e.g. if we're on the very edge of a chunk we may need more chunks).
|
||||||
|
|
||||||
|
var (xDiff, yDiff) = (chunkIndices.X - entityTile.X, chunkIndices.Y - entityTile.Y);
|
||||||
|
if (xDiff > 0 && xDiff > _updateRange ||
|
||||||
|
yDiff > 0 && yDiff > _updateRange ||
|
||||||
|
xDiff < 0 && Math.Abs(xDiff + ChunkSize) > _updateRange ||
|
||||||
|
yDiff < 0 && Math.Abs(yDiff + ChunkSize) > _updateRange) continue;
|
||||||
|
|
||||||
|
inRange.Add(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
AccumulatedFrameTime += frameTime;
|
||||||
|
_updateCooldown = 1 / _configManager.GetCVar<float>("net.gasoverlaytickrate");
|
||||||
|
|
||||||
|
if (AccumulatedFrameTime < _updateCooldown)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateRange = _configManager.GetCVar<float>("net.maxupdaterange") + RangeOffset;
|
||||||
|
|
||||||
|
// TODO: So in the worst case scenario we still have to send a LOT of tile data per tick if there's a fire.
|
||||||
|
// If we go with say 15 tile radius then we have up to 900 tiles to update per tick.
|
||||||
|
// In a saltern fire the worst you'll normally see is around 650 at the moment.
|
||||||
|
// Need a way to fake this more because sending almost 2,000 tile updates per second to even 50 players is... yikes
|
||||||
|
// I mean that's as big as it gets so larger maps will have the same but still, that's a lot of data.
|
||||||
|
|
||||||
|
// Some ways to do this are potentially: splitting fire and gas update data so they don't update at the same time
|
||||||
|
// (gives the illusion of more updates happening), e.g. if gas updates are 3 times a second and fires are 1.6 times a second or something.
|
||||||
|
// Could also look at updating tiles close to us more frequently (e.g. within 1 chunk every tick).
|
||||||
|
// Stuff just out of our viewport we need so when we move it doesn't pop in but it doesn't mean we need to update it every tick.
|
||||||
|
|
||||||
|
AccumulatedFrameTime -= _updateCooldown;
|
||||||
|
|
||||||
|
var gridAtmosComponents = new Dictionary<GridId, GridAtmosphereComponent>();
|
||||||
|
var updatedTiles = new Dictionary<GasOverlayChunk, HashSet<MapIndices>>();
|
||||||
|
|
||||||
|
// So up to this point we've been caching the updated tiles for multiple ticks.
|
||||||
|
// Now we'll go through and check whether the update actually matters for the overlay or not,
|
||||||
|
// and if not then we won't bother sending the data.
|
||||||
|
foreach (var (gridId, indices) in _invalidTiles)
|
||||||
|
{
|
||||||
|
var gridEntityId = _mapManager.GetGrid(gridId).GridEntityId;
|
||||||
|
|
||||||
|
if (!EntityManager.GetEntity(gridEntityId).TryGetComponent(out GridAtmosphereComponent gam))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's being invalidated it should have this right?
|
||||||
|
// At any rate we'll cache it for here + the AddChunk
|
||||||
|
if (!gridAtmosComponents.ContainsKey(gridId))
|
||||||
|
{
|
||||||
|
gridAtmosComponents[gridId] = gam;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var invalid in indices)
|
||||||
|
{
|
||||||
|
var chunk = GetOrCreateChunk(gridId, invalid);
|
||||||
|
|
||||||
|
if (!TryRefreshTile(gam, chunk.GetData(invalid), invalid, out var data)) continue;
|
||||||
|
|
||||||
|
if (!updatedTiles.TryGetValue(chunk, out var tiles))
|
||||||
|
{
|
||||||
|
tiles = new HashSet<MapIndices>();
|
||||||
|
updatedTiles[chunk] = tiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedTiles[chunk].Add(invalid);
|
||||||
|
chunk.Update(data, invalid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentTick = _gameTiming.CurTick;
|
||||||
|
|
||||||
|
// Set the LastUpdate for chunks.
|
||||||
|
foreach (var (chunk, _) in updatedTiles)
|
||||||
|
{
|
||||||
|
chunk.Dirty(currentTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we'll go through each player, then through each chunk in range of that player checking if the player is still in range
|
||||||
|
// If they are, check if they need the new data to send (i.e. if there's an overlay for the gas).
|
||||||
|
// Afterwards we reset all the chunk data for the next time we tick.
|
||||||
|
foreach (var (session, overlay) in _knownPlayerChunks)
|
||||||
|
{
|
||||||
|
if (session.AttachedEntity == null) continue;
|
||||||
|
|
||||||
|
// Get chunks in range and update if we've moved around or the chunks have new overlay data
|
||||||
|
var chunksInRange = GetChunksInRange(session.AttachedEntity);
|
||||||
|
var knownChunks = overlay.GetKnownChunks();
|
||||||
|
var chunksToRemove = new List<GasOverlayChunk>();
|
||||||
|
var chunksToAdd = new List<GasOverlayChunk>();
|
||||||
|
|
||||||
|
foreach (var chunk in chunksInRange)
|
||||||
|
{
|
||||||
|
if (!knownChunks.Contains(chunk))
|
||||||
|
{
|
||||||
|
chunksToAdd.Add(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var chunk in knownChunks)
|
||||||
|
{
|
||||||
|
if (!chunksInRange.Contains(chunk))
|
||||||
|
{
|
||||||
|
chunksToRemove.Add(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var chunk in chunksToAdd)
|
||||||
|
{
|
||||||
|
var message = overlay.AddChunk(currentTick, chunk);
|
||||||
|
if (message != null)
|
||||||
|
{
|
||||||
|
RaiseNetworkEvent(message, session.ConnectedClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var chunk in chunksToRemove)
|
||||||
|
{
|
||||||
|
overlay.RemoveChunk(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
var clientInvalids = new Dictionary<GridId, List<(MapIndices, GasOverlayData)>>();
|
||||||
|
|
||||||
|
// Check for any dirty chunks in range and bundle the data to send to the client.
|
||||||
|
foreach (var chunk in chunksInRange)
|
||||||
|
{
|
||||||
|
if (!updatedTiles.TryGetValue(chunk, out var invalids)) continue;
|
||||||
|
|
||||||
|
if (!clientInvalids.TryGetValue(chunk.GridIndices, out var existingData))
|
||||||
|
{
|
||||||
|
existingData = new List<(MapIndices, GasOverlayData)>();
|
||||||
|
clientInvalids[chunk.GridIndices] = existingData;
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk.GetData(existingData, invalids);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (grid, data) in clientInvalids)
|
||||||
|
{
|
||||||
|
RaiseNetworkEvent(overlay.UpdateClient(grid, data), session.ConnectedClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
_invalidTiles.Clear();
|
||||||
|
}
|
||||||
|
private sealed class PlayerGasOverlay
|
||||||
|
{
|
||||||
|
private readonly Dictionary<GridId, Dictionary<MapIndices, GasOverlayChunk>> _data =
|
||||||
|
new Dictionary<GridId, Dictionary<MapIndices, GasOverlayChunk>>();
|
||||||
|
|
||||||
|
private readonly Dictionary<GasOverlayChunk, GameTick> _lastSent =
|
||||||
|
new Dictionary<GasOverlayChunk, GameTick>();
|
||||||
|
|
||||||
|
public GasOverlayMessage UpdateClient(GridId grid, List<(MapIndices, GasOverlayData)> data)
|
||||||
|
{
|
||||||
|
return new GasOverlayMessage(grid, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_data.Clear();
|
||||||
|
_lastSent.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<GasOverlayChunk> GetKnownChunks()
|
||||||
|
{
|
||||||
|
var known = new List<GasOverlayChunk>();
|
||||||
|
|
||||||
|
foreach (var (_, chunks) in _data)
|
||||||
|
{
|
||||||
|
foreach (var (_, chunk) in chunks)
|
||||||
|
{
|
||||||
|
known.Add(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return known;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GasOverlayMessage? AddChunk(GameTick currentTick, GasOverlayChunk chunk)
|
||||||
|
{
|
||||||
|
if (!_data.TryGetValue(chunk.GridIndices, out var chunks))
|
||||||
|
{
|
||||||
|
chunks = new Dictionary<MapIndices, GasOverlayChunk>();
|
||||||
|
_data[chunk.GridIndices] = chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lastSent.TryGetValue(chunk, out var last) && last >= chunk.LastUpdate)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastSent[chunk] = currentTick;
|
||||||
|
var message = ChunkToMessage(chunk);
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveChunk(GasOverlayChunk chunk)
|
||||||
|
{
|
||||||
|
// Don't need to sync to client as they can manage it themself.
|
||||||
|
if (!_data.TryGetValue(chunk.GridIndices, out var chunks))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunks.ContainsKey(chunk.MapIndices))
|
||||||
|
{
|
||||||
|
chunks.Remove(chunk.MapIndices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve a whole chunk as a message, only getting the relevant tiles for the gas overlay.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="chunk"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private GasOverlayMessage? ChunkToMessage(GasOverlayChunk chunk)
|
||||||
|
{
|
||||||
|
// Chunk data should already be up to date.
|
||||||
|
// Only send relevant tiles to client.
|
||||||
|
|
||||||
|
var tileData = new List<(MapIndices, GasOverlayData)>();
|
||||||
|
|
||||||
|
for (var x = 0; x < ChunkSize; x++)
|
||||||
|
{
|
||||||
|
for (var y = 0; y < ChunkSize; y++)
|
||||||
|
{
|
||||||
|
// TODO: Check could be more robust I think.
|
||||||
|
var data = chunk.TileData[x, y];
|
||||||
|
if ((data.Gas == null || data.Gas.Length == 0) && data.FireState == 0 && data.FireTemperature == 0.0f)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var indices = new MapIndices(chunk.MapIndices.X + x, chunk.MapIndices.Y + y);
|
||||||
|
tileData.Add((indices, data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tileData.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GasOverlayMessage(chunk.GridIndices, tileData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using Content.Server.GameObjects.Components.Atmos;
|
|
||||||
using Content.Shared.Atmos;
|
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Server.Interfaces.Player;
|
|
||||||
using Robust.Server.Player;
|
|
||||||
using Robust.Shared.Enums;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.Interfaces.Map;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Map;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.EntitySystems
|
|
||||||
{
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
|
|
||||||
{
|
|
||||||
private int _tickTimer = 0;
|
|
||||||
private HashSet<GasTileOverlayData> _queue = new HashSet<GasTileOverlayData>();
|
|
||||||
private Dictionary<GridId, HashSet<MapIndices>> _invalid = new Dictionary<GridId, HashSet<MapIndices>>();
|
|
||||||
|
|
||||||
private Dictionary<GridId, Dictionary<MapIndices, GasOverlayData>> _overlay =
|
|
||||||
new Dictionary<GridId, Dictionary<MapIndices, GasOverlayData>>();
|
|
||||||
|
|
||||||
[Robust.Shared.IoC.Dependency] private IPlayerManager _playerManager = default!;
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public void Invalidate(GridId gridIndex, MapIndices indices)
|
|
||||||
{
|
|
||||||
if (!_invalid.TryGetValue(gridIndex, out var set) || set == null)
|
|
||||||
{
|
|
||||||
set = new HashSet<MapIndices>();
|
|
||||||
_invalid.Add(gridIndex, set);
|
|
||||||
}
|
|
||||||
|
|
||||||
set.Add(indices);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetTileOverlay(GridId gridIndex, MapIndices indices, GasData[] gasData, int fireState = 0, float fireTemperature = 0f)
|
|
||||||
{
|
|
||||||
if(!_overlay.TryGetValue(gridIndex, out var _))
|
|
||||||
_overlay[gridIndex] = new Dictionary<MapIndices, GasOverlayData>();
|
|
||||||
|
|
||||||
_overlay[gridIndex][indices] = new GasOverlayData(fireState, fireTemperature, gasData);
|
|
||||||
_queue.Add(GetData(gridIndex, indices));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPlayerStatusChanged(object sender, SessionStatusEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.NewStatus != SessionStatus.InGame) return;
|
|
||||||
|
|
||||||
RaiseNetworkEvent(new GasTileOverlayMessage(GetData(), true), e.Session.ConnectedClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
private GasTileOverlayData[] GetData()
|
|
||||||
{
|
|
||||||
var list = new List<GasTileOverlayData>();
|
|
||||||
|
|
||||||
foreach (var (gridId, tiles) in _overlay)
|
|
||||||
{
|
|
||||||
foreach (var (indices, _) in tiles)
|
|
||||||
{
|
|
||||||
var data = GetData(gridId, indices);
|
|
||||||
if(data.Data.Gas.Length > 0)
|
|
||||||
list.Add(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return list.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private GasTileOverlayData GetData(GridId gridIndex, MapIndices indices)
|
|
||||||
{
|
|
||||||
return new GasTileOverlayData(gridIndex, indices, _overlay[gridIndex][indices]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Revalidate()
|
|
||||||
{
|
|
||||||
var mapMan = IoCManager.Resolve<IMapManager>();
|
|
||||||
var entityMan = IoCManager.Resolve<IEntityManager>();
|
|
||||||
var list = new List<GasData>();
|
|
||||||
|
|
||||||
foreach (var (gridId, indices) in _invalid)
|
|
||||||
{
|
|
||||||
if (!mapMan.GridExists(gridId))
|
|
||||||
{
|
|
||||||
_invalid.Remove(gridId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var grid = entityMan.GetEntity(mapMan.GetGrid(gridId).GridEntityId);
|
|
||||||
if (!grid.TryGetComponent(out GridAtmosphereComponent gam)) continue;
|
|
||||||
|
|
||||||
foreach (var index in indices)
|
|
||||||
{
|
|
||||||
var tile = gam.GetTile(index);
|
|
||||||
|
|
||||||
if (tile?.Air == null) continue;
|
|
||||||
|
|
||||||
list.Clear();
|
|
||||||
|
|
||||||
for(var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
|
||||||
{
|
|
||||||
var gas = Atmospherics.GetGas(i);
|
|
||||||
var overlay = gas.GasOverlay;
|
|
||||||
if (overlay == null) continue;
|
|
||||||
var moles = tile.Air.Gases[i];
|
|
||||||
if(moles == 0f || moles < gas.GasMolesVisible) continue;
|
|
||||||
list.Add(new GasData(i, MathF.Max(MathF.Min(1, moles / gas.GasMolesVisibleMax), 0f)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (list.Count == 0) continue;
|
|
||||||
|
|
||||||
SetTileOverlay(gridId, index, list.ToArray(), tile.Hotspot.State, tile.Hotspot.Temperature);
|
|
||||||
}
|
|
||||||
|
|
||||||
indices.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
|
||||||
{
|
|
||||||
_tickTimer++;
|
|
||||||
|
|
||||||
Revalidate();
|
|
||||||
|
|
||||||
if (_tickTimer < 10) return;
|
|
||||||
|
|
||||||
_tickTimer = 0;
|
|
||||||
if(_queue.Count > 0)
|
|
||||||
RaiseNetworkEvent(new GasTileOverlayMessage(_queue.ToArray()));
|
|
||||||
_queue.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,6 +13,7 @@ using Content.Server.GameObjects.Components.PDA;
|
|||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding;
|
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding;
|
||||||
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible;
|
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible;
|
||||||
|
using Content.Server.GameObjects.EntitySystems.Atmos;
|
||||||
using Content.Server.GameObjects.EntitySystems.StationEvents;
|
using Content.Server.GameObjects.EntitySystems.StationEvents;
|
||||||
using Content.Server.GameTicking.GamePresets;
|
using Content.Server.GameTicking.GamePresets;
|
||||||
using Content.Server.Interfaces;
|
using Content.Server.Interfaces;
|
||||||
@@ -633,7 +634,8 @@ namespace Content.Server.GameTicking
|
|||||||
|
|
||||||
_playerJoinLobby(player);
|
_playerJoinLobby(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EntitySystem.Get<GasTileOverlaySystem>().ResettingCleanup();
|
||||||
EntitySystem.Get<PathfindingSystem>().ResettingCleanup();
|
EntitySystem.Get<PathfindingSystem>().ResettingCleanup();
|
||||||
EntitySystem.Get<AiReachableSystem>().ResettingCleanup();
|
EntitySystem.Get<AiReachableSystem>().ResettingCleanup();
|
||||||
EntitySystem.Get<WireHackingSystem>().ResetLayouts();
|
EntitySystem.Get<WireHackingSystem>().ResetLayouts();
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
"Marker",
|
"Marker",
|
||||||
"EmergencyLight",
|
"EmergencyLight",
|
||||||
"Clickable",
|
"Clickable",
|
||||||
"CanSeeGases",
|
|
||||||
"RadiatingLight",
|
"RadiatingLight",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Shared.Atmos
|
namespace Content.Shared.Atmos
|
||||||
{
|
{
|
||||||
@@ -14,10 +15,18 @@ namespace Content.Shared.Atmos
|
|||||||
var protoMan = IoCManager.Resolve<IPrototypeManager>();
|
var protoMan = IoCManager.Resolve<IPrototypeManager>();
|
||||||
|
|
||||||
GasPrototypes = new GasPrototype[TotalNumberOfGases];
|
GasPrototypes = new GasPrototype[TotalNumberOfGases];
|
||||||
|
GasOverlays = new SpriteSpecifier[TotalNumberOfGases];
|
||||||
|
|
||||||
for (var i = 0; i < TotalNumberOfGases; i++)
|
for (var i = 0; i < TotalNumberOfGases; i++)
|
||||||
{
|
{
|
||||||
GasPrototypes[i] = protoMan.Index<GasPrototype>(i.ToString());
|
var gasPrototype = protoMan.Index<GasPrototype>(i.ToString());
|
||||||
|
GasPrototypes[i] = gasPrototype;
|
||||||
|
|
||||||
|
if(string.IsNullOrEmpty(gasPrototype.GasOverlaySprite) && !string.IsNullOrEmpty(gasPrototype.GasOverlayTexture))
|
||||||
|
GasOverlays[i] = new SpriteSpecifier.Texture(new ResourcePath(gasPrototype.GasOverlayTexture));
|
||||||
|
|
||||||
|
if(!string.IsNullOrEmpty(gasPrototype.GasOverlaySprite) && !string.IsNullOrEmpty(gasPrototype.GasOverlayState))
|
||||||
|
GasOverlays[i] = new SpriteSpecifier.Rsi(new ResourcePath(gasPrototype.GasOverlaySprite), gasPrototype.GasOverlayState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,6 +36,10 @@ namespace Content.Shared.Atmos
|
|||||||
public static GasPrototype GetGas(Gas gasId) => GasPrototypes[(int) gasId];
|
public static GasPrototype GetGas(Gas gasId) => GasPrototypes[(int) gasId];
|
||||||
public static IEnumerable<GasPrototype> Gases => GasPrototypes;
|
public static IEnumerable<GasPrototype> Gases => GasPrototypes;
|
||||||
|
|
||||||
|
private static readonly SpriteSpecifier[] GasOverlays;
|
||||||
|
|
||||||
|
public static SpriteSpecifier GetOverlay(int overlayId) => GasOverlays[overlayId];
|
||||||
|
|
||||||
#region ATMOS
|
#region ATMOS
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The universal gas constant, in kPa*L/(K*mol)
|
/// The universal gas constant, in kPa*L/(K*mol)
|
||||||
@@ -166,7 +179,7 @@ namespace Content.Shared.Atmos
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Total number of gases. Increase this if you want to add more!
|
/// Total number of gases. Increase this if you want to add more!
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int TotalNumberOfGases = 6;
|
public const byte TotalNumberOfGases = 6;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Amount of heat released per mole of burnt hydrogen or tritium (hydrogen isotope)
|
/// Amount of heat released per mole of burnt hydrogen or tritium (hydrogen isotope)
|
||||||
|
|||||||
@@ -45,23 +45,6 @@ namespace Content.Shared.Atmos
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string GasOverlaySprite { get; set; }
|
public string GasOverlaySprite { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sprite specifier for the gas overlay.
|
|
||||||
/// </summary>
|
|
||||||
public SpriteSpecifier GasOverlay
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if(string.IsNullOrEmpty(GasOverlaySprite) && !string.IsNullOrEmpty(GasOverlayTexture))
|
|
||||||
return new SpriteSpecifier.Texture(new ResourcePath(GasOverlayTexture));
|
|
||||||
|
|
||||||
if(!string.IsNullOrEmpty(GasOverlaySprite) && !string.IsNullOrEmpty(GasOverlayState))
|
|
||||||
return new SpriteSpecifier.Rsi(new ResourcePath(GasOverlaySprite), GasOverlayState);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Path to the tile overlay used when this gas appears visible.
|
/// Path to the tile overlay used when this gas appears visible.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Robust.Shared.Interfaces.Timing;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Shared.GameObjects.EntitySystems.Atmos
|
||||||
|
{
|
||||||
|
public sealed class GasOverlayChunk
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Grid for this chunk
|
||||||
|
/// </summary>
|
||||||
|
public GridId GridIndices { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Origin of this chunk
|
||||||
|
/// </summary>
|
||||||
|
public MapIndices MapIndices { get; }
|
||||||
|
|
||||||
|
public SharedGasTileOverlaySystem.GasOverlayData[,] TileData = new SharedGasTileOverlaySystem.GasOverlayData[SharedGasTileOverlaySystem.ChunkSize, SharedGasTileOverlaySystem.ChunkSize];
|
||||||
|
|
||||||
|
public GameTick LastUpdate { get; private set; }
|
||||||
|
|
||||||
|
public GasOverlayChunk(GridId gridIndices, MapIndices mapIndices)
|
||||||
|
{
|
||||||
|
GridIndices = gridIndices;
|
||||||
|
MapIndices = mapIndices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dirty(GameTick currentTick)
|
||||||
|
{
|
||||||
|
LastUpdate = currentTick;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flags Dirty if the data is different.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data"></param>
|
||||||
|
/// <param name="indices"></param>
|
||||||
|
public void Update(SharedGasTileOverlaySystem.GasOverlayData data, MapIndices indices)
|
||||||
|
{
|
||||||
|
DebugTools.Assert(InBounds(indices));
|
||||||
|
var (offsetX, offsetY) = (indices.X - MapIndices.X,
|
||||||
|
indices.Y - MapIndices.Y);
|
||||||
|
|
||||||
|
TileData[offsetX, offsetY] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(SharedGasTileOverlaySystem.GasOverlayData data, byte x, byte y)
|
||||||
|
{
|
||||||
|
DebugTools.Assert(x < SharedGasTileOverlaySystem.ChunkSize && y < SharedGasTileOverlaySystem.ChunkSize);
|
||||||
|
|
||||||
|
TileData[x, y] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<SharedGasTileOverlaySystem.GasOverlayData> GetAllData()
|
||||||
|
{
|
||||||
|
for (var x = 0; x < SharedGasTileOverlaySystem.ChunkSize; x++)
|
||||||
|
{
|
||||||
|
for (var y = 0; y < SharedGasTileOverlaySystem.ChunkSize; y++)
|
||||||
|
{
|
||||||
|
yield return TileData[x, y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetData(List<(MapIndices, SharedGasTileOverlaySystem.GasOverlayData)> existingData, HashSet<MapIndices> indices)
|
||||||
|
{
|
||||||
|
foreach (var index in indices)
|
||||||
|
{
|
||||||
|
existingData.Add((index, GetData(index)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<MapIndices> GetAllIndices()
|
||||||
|
{
|
||||||
|
for (var x = 0; x < SharedGasTileOverlaySystem.ChunkSize; x++)
|
||||||
|
{
|
||||||
|
for (var y = 0; y < SharedGasTileOverlaySystem.ChunkSize; y++)
|
||||||
|
{
|
||||||
|
yield return new MapIndices(MapIndices.X + x, MapIndices.Y + y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SharedGasTileOverlaySystem.GasOverlayData GetData(MapIndices indices)
|
||||||
|
{
|
||||||
|
DebugTools.Assert(InBounds(indices));
|
||||||
|
return TileData[indices.X - MapIndices.X, indices.Y - MapIndices.Y];
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool InBounds(MapIndices indices)
|
||||||
|
{
|
||||||
|
if (indices.X < MapIndices.X || indices.Y < MapIndices.Y) return false;
|
||||||
|
if (indices.X >= MapIndices.X + SharedGasTileOverlaySystem.ChunkSize || indices.Y >= MapIndices.Y + SharedGasTileOverlaySystem.ChunkSize) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Shared.GameObjects.EntitySystems.Atmos
|
||||||
|
{
|
||||||
|
public abstract class SharedGasTileOverlaySystem : EntitySystem
|
||||||
|
{
|
||||||
|
public const byte ChunkSize = 8;
|
||||||
|
protected float AccumulatedFrameTime;
|
||||||
|
|
||||||
|
public static MapIndices GetGasChunkIndices(MapIndices indices)
|
||||||
|
{
|
||||||
|
return new MapIndices((int) Math.Floor((float) indices.X / ChunkSize) * ChunkSize, (int) MathF.Floor((float) indices.Y / ChunkSize) * ChunkSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public struct GasData
|
||||||
|
{
|
||||||
|
public byte Index { get; set; }
|
||||||
|
public byte Opacity { get; set; }
|
||||||
|
|
||||||
|
public GasData(byte gasId, byte opacity)
|
||||||
|
{
|
||||||
|
Index = gasId;
|
||||||
|
Opacity = opacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public readonly struct GasOverlayData : IEquatable<GasOverlayData>
|
||||||
|
{
|
||||||
|
public readonly byte FireState;
|
||||||
|
public readonly float FireTemperature;
|
||||||
|
public readonly GasData[] Gas;
|
||||||
|
|
||||||
|
public GasOverlayData(byte fireState, float fireTemperature, GasData[] gas)
|
||||||
|
{
|
||||||
|
FireState = fireState;
|
||||||
|
FireTemperature = fireTemperature;
|
||||||
|
Gas = gas;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(GasOverlayData other)
|
||||||
|
{
|
||||||
|
// TODO: Moony had a suggestion on how to do this faster with the hash
|
||||||
|
// https://discordapp.com/channels/310555209753690112/310555209753690112/744080145219846204
|
||||||
|
// Aside from that I can't really see any low-hanging fruit CPU perf wise.
|
||||||
|
if (Gas?.Length != other.Gas?.Length) return false;
|
||||||
|
if (FireState != other.FireState) return false;
|
||||||
|
if (FireTemperature != other.FireTemperature) return false;
|
||||||
|
|
||||||
|
if (Gas == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugTools.Assert(other.Gas != null);
|
||||||
|
|
||||||
|
for (var i = 0; i < Gas.Length; i++)
|
||||||
|
{
|
||||||
|
var thisGas = Gas[i];
|
||||||
|
var otherGas = other.Gas[i];
|
||||||
|
|
||||||
|
if (!thisGas.Equals(otherGas))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tiles for the gas overlay.
|
||||||
|
/// No point re-sending every tile if only a subset might have been updated.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class GasOverlayMessage : EntitySystemMessage
|
||||||
|
{
|
||||||
|
public GridId GridId { get; }
|
||||||
|
|
||||||
|
public List<(MapIndices, GasOverlayData)> OverlayData { get; }
|
||||||
|
|
||||||
|
public GasOverlayMessage(GridId gridIndices, List<(MapIndices,GasOverlayData)> overlayData)
|
||||||
|
{
|
||||||
|
GridId = gridIndices;
|
||||||
|
OverlayData = overlayData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.GameObjects.Systems;
|
|
||||||
using Robust.Shared.Map;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
|
|
||||||
namespace Content.Shared.GameObjects.EntitySystems
|
|
||||||
{
|
|
||||||
public abstract class SharedGasTileOverlaySystem : EntitySystem
|
|
||||||
{
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public struct GasData
|
|
||||||
{
|
|
||||||
public int Index { get; set; }
|
|
||||||
public float Opacity { get; set; }
|
|
||||||
|
|
||||||
public GasData(int gasId, float opacity)
|
|
||||||
{
|
|
||||||
Index = gasId;
|
|
||||||
Opacity = opacity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public readonly struct GasOverlayData
|
|
||||||
{
|
|
||||||
public readonly int FireState;
|
|
||||||
public readonly float FireTemperature;
|
|
||||||
public readonly GasData[] Gas;
|
|
||||||
|
|
||||||
public GasOverlayData(int fireState, float fireTemperature, GasData[] gas)
|
|
||||||
{
|
|
||||||
FireState = fireState;
|
|
||||||
FireTemperature = fireTemperature;
|
|
||||||
Gas = gas;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public readonly struct GasTileOverlayData
|
|
||||||
{
|
|
||||||
public readonly GridId GridIndex;
|
|
||||||
public readonly MapIndices GridIndices;
|
|
||||||
public readonly GasOverlayData Data;
|
|
||||||
|
|
||||||
public GasTileOverlayData(GridId gridIndex, MapIndices gridIndices, GasOverlayData data)
|
|
||||||
{
|
|
||||||
GridIndex = gridIndex;
|
|
||||||
GridIndices = gridIndices;
|
|
||||||
Data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return GridIndex.GetHashCode() ^ GridIndices.GetHashCode() ^ Data.GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public class GasTileOverlayMessage : EntitySystemMessage
|
|
||||||
{
|
|
||||||
public GasTileOverlayData[] OverlayData { get; }
|
|
||||||
public bool ClearAllOtherOverlays { get; }
|
|
||||||
|
|
||||||
public GasTileOverlayMessage(GasTileOverlayData[] overlayData, bool clearAllOtherOverlays = false)
|
|
||||||
{
|
|
||||||
OverlayData = overlayData;
|
|
||||||
ClearAllOtherOverlays = clearAllOtherOverlays;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -22,7 +22,6 @@
|
|||||||
DoRangeCheck: false
|
DoRangeCheck: false
|
||||||
- type: IgnorePause
|
- type: IgnorePause
|
||||||
- type: Ghost
|
- type: Ghost
|
||||||
- type: CanSeeGases
|
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
netsync: false
|
netsync: false
|
||||||
drawdepth: Ghosts
|
drawdepth: Ghosts
|
||||||
|
|||||||
@@ -154,7 +154,6 @@
|
|||||||
- type: Grammar
|
- type: Grammar
|
||||||
proper: true
|
proper: true
|
||||||
- type: Pullable
|
- type: Pullable
|
||||||
- type: CanSeeGases
|
|
||||||
- type: DoAfter
|
- type: DoAfter
|
||||||
- type: Strippable
|
- type: Strippable
|
||||||
- type: UserInterface
|
- type: UserInterface
|
||||||
|
|||||||
Reference in New Issue
Block a user