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 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))
|
||||
{
|
||||
|
||||
@@ -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 Content.Client.Atmos;
|
||||
using Content.Client.GameObjects.Components.Atmos;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.EntitySystems.Atmos;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.GameObjects.EntitySystems;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Interfaces.Graphics.Overlays;
|
||||
using Robust.Client.Interfaces.ResourceManagement;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -16,8 +22,9 @@ using Robust.Shared.Utility;
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
|
||||
internal sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
private readonly Dictionary<float, Color> _fireCache = new Dictionary<float, Color>();
|
||||
@@ -37,18 +44,19 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
private readonly int[] _fireFrameCounter = new int[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()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent(new EntityEventHandler<GasTileOverlayMessage>(OnTileOverlayMessage));
|
||||
SubscribeNetworkEvent<GasOverlayMessage>(HandleGasOverlayMessage);
|
||||
_mapManager.OnGridRemoved += OnGridRemoved;
|
||||
|
||||
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
||||
{
|
||||
var gas = Atmospherics.GetGas(i);
|
||||
switch (gas.GasOverlay)
|
||||
var overlay = Atmospherics.GetOverlay(i);
|
||||
switch (overlay)
|
||||
{
|
||||
case SpriteSpecifier.Rsi animated:
|
||||
var rsi = _resourceCache.GetResource<RSIResource>(animated.RsiPath).RSI;
|
||||
@@ -82,11 +90,75 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
_fireFrameDelays[i] = state.GetDelays();
|
||||
_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)
|
||||
{
|
||||
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)>();
|
||||
|
||||
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;
|
||||
@@ -112,23 +184,6 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
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)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Content.Server.Atmos
|
||||
/// State for the fire sprite.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int State;
|
||||
public byte State;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
|
||||
using Content.Server.Atmos.Reactions;
|
||||
using Content.Server.GameObjects.Components.Atmos;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.GameObjects.EntitySystems.Atmos;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Audio;
|
||||
@@ -745,7 +746,7 @@ namespace Content.Server.Atmos
|
||||
}
|
||||
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)
|
||||
|
||||
@@ -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.AI.Pathfinding;
|
||||
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible;
|
||||
using Content.Server.GameObjects.EntitySystems.Atmos;
|
||||
using Content.Server.GameObjects.EntitySystems.StationEvents;
|
||||
using Content.Server.GameTicking.GamePresets;
|
||||
using Content.Server.Interfaces;
|
||||
@@ -634,6 +635,7 @@ namespace Content.Server.GameTicking
|
||||
_playerJoinLobby(player);
|
||||
}
|
||||
|
||||
EntitySystem.Get<GasTileOverlaySystem>().ResettingCleanup();
|
||||
EntitySystem.Get<PathfindingSystem>().ResettingCleanup();
|
||||
EntitySystem.Get<AiReachableSystem>().ResettingCleanup();
|
||||
EntitySystem.Get<WireHackingSystem>().ResetLayouts();
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
"Marker",
|
||||
"EmergencyLight",
|
||||
"Clickable",
|
||||
"CanSeeGases",
|
||||
"RadiatingLight",
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Atmos
|
||||
{
|
||||
@@ -14,10 +15,18 @@ namespace Content.Shared.Atmos
|
||||
var protoMan = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
GasPrototypes = new GasPrototype[TotalNumberOfGases];
|
||||
GasOverlays = new SpriteSpecifier[TotalNumberOfGases];
|
||||
|
||||
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 IEnumerable<GasPrototype> Gases => GasPrototypes;
|
||||
|
||||
private static readonly SpriteSpecifier[] GasOverlays;
|
||||
|
||||
public static SpriteSpecifier GetOverlay(int overlayId) => GasOverlays[overlayId];
|
||||
|
||||
#region ATMOS
|
||||
/// <summary>
|
||||
/// The universal gas constant, in kPa*L/(K*mol)
|
||||
@@ -166,7 +179,7 @@ namespace Content.Shared.Atmos
|
||||
/// <summary>
|
||||
/// Total number of gases. Increase this if you want to add more!
|
||||
/// </summary>
|
||||
public const int TotalNumberOfGases = 6;
|
||||
public const byte TotalNumberOfGases = 6;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of heat released per mole of burnt hydrogen or tritium (hydrogen isotope)
|
||||
|
||||
@@ -45,23 +45,6 @@ namespace Content.Shared.Atmos
|
||||
/// </summary>
|
||||
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>
|
||||
/// Path to the tile overlay used when this gas appears visible.
|
||||
/// </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
|
||||
- type: IgnorePause
|
||||
- type: Ghost
|
||||
- type: CanSeeGases
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
drawdepth: Ghosts
|
||||
|
||||
@@ -154,7 +154,6 @@
|
||||
- type: Grammar
|
||||
proper: true
|
||||
- type: Pullable
|
||||
- type: CanSeeGases
|
||||
- type: DoAfter
|
||||
- type: Strippable
|
||||
- type: UserInterface
|
||||
|
||||
Reference in New Issue
Block a user