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); DebugTools.Assert(chunk.Decals.ContainsKey(decalId)); chunk.Decals.Remove(decalId); } private void OnHandleState(EntityUid gridUid, DecalGridComponent gridComp, ref ComponentHandleState args) { // is this a delta or full state? _removedChunks.Clear(); Dictionary modifiedChunks; switch (args.Current) { case DecalGridDeltaState delta: { modifiedChunks = delta.ModifiedChunks; foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) { if (!delta.AllChunks.Contains(key)) _removedChunks.Add(key); } break; } case DecalGridState state: { modifiedChunks = state.Chunks; foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) { if (!state.Chunks.ContainsKey(key)) _removedChunks.Add(key); } break; } default: return; } if (_removedChunks.Count > 0) RemoveChunks(gridUid, gridComp, _removedChunks); if (modifiedChunks.Count > 0) UpdateChunks(gridUid, gridComp, modifiedChunks); } 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; // 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) { 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); } } } }