using Content.Client.Decals.Overlays; using Content.Shared.Decals; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Shared.GameStates; using Robust.Shared.Utility; using static Content.Shared.Decals.DecalGridComponent; namespace Content.Client.Decals { public sealed class DecalSystem : SharedDecalSystem { [Dependency] private readonly IOverlayManager _overlayManager = default!; [Dependency] private readonly SpriteSystem _sprites = default!; private DecalOverlay _overlay = default!; private HashSet _removedUids = new(); private readonly List _removedChunks = new(); public override void Initialize() { base.Initialize(); _overlay = new DecalOverlay(_sprites, EntityManager, PrototypeManager); _overlayManager.AddOverlay(_overlay); SubscribeLocalEvent(OnHandleState); SubscribeNetworkEvent(OnChunkUpdate); } public void ToggleOverlay() { if (_overlayManager.HasOverlay()) { _overlayManager.RemoveOverlay(_overlay); } else { _overlayManager.AddOverlay(_overlay); } } public override void Shutdown() { base.Shutdown(); _overlayManager.RemoveOverlay(_overlay); } protected override void OnDecalRemoved(EntityUid gridId, uint decalId, DecalGridComponent component, Vector2i indices, DecalChunk chunk) { base.OnDecalRemoved(gridId, decalId, component, indices, chunk); if (!component.DecalZIndexIndex.Remove(decalId, out var zIndex)) return; if (!component.DecalRenderIndex.TryGetValue(zIndex, out var renderIndex)) return; renderIndex.Remove(decalId); if (renderIndex.Count == 0) component.DecalRenderIndex.Remove(zIndex); } private void OnHandleState(EntityUid gridUid, DecalGridComponent gridComp, ref ComponentHandleState args) { if (args.Current is not DecalGridState state) return; // is this a delta or full state? _removedChunks.Clear(); if (!state.FullState) { foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) { if (!state.AllChunks!.Contains(key)) _removedChunks.Add(key); } } else { foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) { if (!state.Chunks.ContainsKey(key)) _removedChunks.Add(key); } } if (_removedChunks.Count > 0) RemoveChunks(gridUid, gridComp, _removedChunks); if (state.Chunks.Count > 0) UpdateChunks(gridUid, gridComp, state.Chunks); } private void OnChunkUpdate(DecalChunkUpdateEvent ev) { foreach (var (netGrid, updatedGridChunks) in ev.Data) { if (updatedGridChunks.Count == 0) continue; var gridId = GetEntity(netGrid); if (!TryComp(gridId, out DecalGridComponent? gridComp)) { Log.Error($"Received decal information for an entity without a decal component: {ToPrettyString(gridId)}"); continue; } UpdateChunks(gridId, gridComp, updatedGridChunks); } // Now we'll cull old chunks out of range as the server will send them to us anyway. foreach (var (netGrid, chunks) in ev.RemovedChunks) { if (chunks.Count == 0) continue; var gridId = GetEntity(netGrid); if (!TryComp(gridId, out DecalGridComponent? gridComp)) { Log.Error($"Received decal information for an entity without a decal component: {ToPrettyString(gridId)}"); continue; } RemoveChunks(gridId, gridComp, chunks); } } private void UpdateChunks(EntityUid gridId, DecalGridComponent gridComp, Dictionary updatedGridChunks) { var chunkCollection = gridComp.ChunkCollection.ChunkCollection; var renderIndex = gridComp.DecalRenderIndex; var zIndexIndex = gridComp.DecalZIndexIndex; // Update any existing data / remove decals we didn't receive data for. foreach (var (indices, newChunkData) in updatedGridChunks) { if (chunkCollection.TryGetValue(indices, out var chunk)) { _removedUids.Clear(); _removedUids.UnionWith(chunk.Decals.Keys); _removedUids.ExceptWith(newChunkData.Decals.Keys); foreach (var removedUid in _removedUids) { OnDecalRemoved(gridId, removedUid, gridComp, indices, chunk); gridComp.DecalIndex.Remove(removedUid); } } chunkCollection[indices] = newChunkData; foreach (var (uid, decal) in newChunkData.Decals) { if (zIndexIndex.TryGetValue(uid, out var zIndex)) renderIndex[zIndex].Remove(uid); renderIndex.GetOrNew(decal.ZIndex)[uid] = decal; zIndexIndex[uid] = decal.ZIndex; gridComp.DecalIndex[uid] = indices; } } } private void RemoveChunks(EntityUid gridId, DecalGridComponent gridComp, IEnumerable chunks) { var chunkCollection = gridComp.ChunkCollection.ChunkCollection; foreach (var index in chunks) { if (!chunkCollection.TryGetValue(index, out var chunk)) continue; foreach (var decalId in chunk.Decals.Keys) { OnDecalRemoved(gridId, decalId, gridComp, index, chunk); gridComp.DecalIndex.Remove(decalId); } chunkCollection.Remove(index); } } } }