#nullable enable using System; using System.Collections.Generic; using Content.Client.Atmos.Overlays; using Content.Shared.Atmos; using Content.Shared.Atmos.EntitySystems; using JetBrains.Annotations; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Client.Utility; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Utility; namespace Content.Client.Atmos.EntitySystems { [UsedImplicitly] internal sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem { [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IResourceCache _resourceCache = default!; // Gas overlays private readonly float[] _timer = new float[Atmospherics.TotalNumberOfGases]; private readonly float[][] _frameDelays = new float[Atmospherics.TotalNumberOfGases][]; private readonly int[] _frameCounter = new int[Atmospherics.TotalNumberOfGases]; private readonly Texture[][] _frames = new Texture[Atmospherics.TotalNumberOfGases][]; // Fire overlays private const int FireStates = 3; private const string FireRsiPath = "/Textures/Effects/fire.rsi"; private readonly float[] _fireTimer = new float[FireStates]; private readonly float[][] _fireFrameDelays = new float[FireStates][]; private readonly int[] _fireFrameCounter = new int[FireStates]; private readonly Texture[][] _fireFrames = new Texture[FireStates][]; private readonly Dictionary> _tileData = new(); private AtmosphereSystem _atmosphereSystem = default!; public override void Initialize() { base.Initialize(); SubscribeNetworkEvent(HandleGasOverlayMessage); _mapManager.OnGridRemoved += OnGridRemoved; _atmosphereSystem = Get(); for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) { var overlay = _atmosphereSystem.GetOverlay(i); switch (overlay) { case SpriteSpecifier.Rsi animated: var rsi = _resourceCache.GetResource(animated.RsiPath).RSI; var stateId = animated.RsiState; if(!rsi.TryGetState(stateId, out var state)) continue; _frames[i] = state.GetFrames(RSI.State.Direction.South); _frameDelays[i] = state.GetDelays(); _frameCounter[i] = 0; break; case SpriteSpecifier.Texture texture: _frames[i] = new[] {texture.Frame0()}; _frameDelays[i] = Array.Empty(); break; case null: _frames[i] = Array.Empty(); _frameDelays[i] = Array.Empty(); break; } } var fire = _resourceCache.GetResource(FireRsiPath).RSI; for (var i = 0; i < FireStates; i++) { if (!fire.TryGetState((i+1).ToString(), out var state)) throw new ArgumentOutOfRangeException($"Fire RSI doesn't have state \"{i}\"!"); _fireFrames[i] = state.GetFrames(RSI.State.Direction.South); _fireFrameDelays[i] = state.GetDelays(); _fireFrameCounter[i] = 0; } var overlayManager = IoCManager.Resolve(); if(!overlayManager.HasOverlay()) 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, Vector2i indices) { if (!_tileData.TryGetValue(gridId, out var chunks)) { chunks = new Dictionary(); _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(); if(!overlayManager.HasOverlay()) overlayManager.RemoveOverlay(); } private void OnGridRemoved(MapId mapId, GridId gridId) { if (_tileData.ContainsKey(gridId)) { _tileData.Remove(gridId); } } public bool HasData(GridId gridId) { return _tileData.ContainsKey(gridId); } public IEnumerable<(Texture, Color)> GetOverlays(GridId gridIndex, Vector2i indices) { if (!_tileData.TryGetValue(gridIndex, out var chunks)) yield break; var chunkIndex = GetGasChunkIndices(indices); if (!chunks.TryGetValue(chunkIndex, out var chunk)) yield break; var overlays = chunk.GetData(indices); if (overlays.Gas == null) yield break; var fire = overlays.FireState != 0; foreach (var gasData in overlays.Gas) { var frames = _frames[gasData.Index]; yield return (frames[_frameCounter[gasData.Index]], Color.White.WithAlpha(gasData.Opacity)); } if (fire) { var state = overlays.FireState - 1; var frames = _fireFrames[state]; // TODO ATMOS Set color depending on temperature yield return (frames[_fireFrameCounter[state]], Color.White); } } public override void FrameUpdate(float frameTime) { base.FrameUpdate(frameTime); for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) { var delays = _frameDelays[i]; if (delays.Length == 0) continue; var frameCount = _frameCounter[i]; _timer[i] += frameTime; if (!(_timer[i] >= delays[frameCount])) continue; _timer[i] = 0f; _frameCounter[i] = (frameCount + 1) % _frames[i].Length; } for (var i = 0; i < FireStates; i++) { var delays = _fireFrameDelays[i]; if (delays.Length == 0) continue; var frameCount = _fireFrameCounter[i]; _fireTimer[i] += frameTime; if (!(_fireTimer[i] >= delays[frameCount])) continue; _fireTimer[i] = 0f; _fireFrameCounter[i] = (frameCount + 1) % _fireFrames[i].Length; } } } }