diff --git a/Content.Shared/Decals/DecalGridChunkCollectionTypeSerializer.cs b/Content.Shared/Decals/DecalGridChunkCollectionTypeSerializer.cs index 2fdb92d8c7..ad6b803e76 100644 --- a/Content.Shared/Decals/DecalGridChunkCollectionTypeSerializer.cs +++ b/Content.Shared/Decals/DecalGridChunkCollectionTypeSerializer.cs @@ -1,9 +1,14 @@ +using System.Globalization; +using Robust.Shared.Map; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Markdown; using Robust.Shared.Serialization.Markdown.Mapping; +using Robust.Shared.Serialization.Markdown.Sequence; using Robust.Shared.Serialization.Markdown.Validation; +using Robust.Shared.Serialization.Markdown.Value; using Robust.Shared.Serialization.TypeSerializers.Interfaces; +using Robust.Shared.Utility; using static Content.Shared.Decals.DecalGridComponent; namespace Content.Shared.Decals @@ -22,7 +27,38 @@ namespace Content.Shared.Decals IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null, ISerializationManager.InstantiationDelegate? _ = default) { - var dictionary = serializationManager.Read>(node, hookCtx, context, notNullableOverride: true); + node.TryGetValue(new ValueDataNode("version"), out var versionNode); + var version = ((ValueDataNode?) versionNode)?.AsInt() ?? 1; + Dictionary dictionary; + + // TODO: Dump this when we don't need support anymore. + if (version > 1) + { + var nodes = (SequenceDataNode) node["nodes"]; + dictionary = new Dictionary(); + + foreach (var dNode in nodes) + { + var aNode = (MappingDataNode) dNode; + var data = serializationManager.Read(aNode["node"], hookCtx, context); + var deckNodes = (MappingDataNode) aNode["decals"]; + + foreach (var (decalUidNode, decalData) in deckNodes) + { + var dUid = serializationManager.Read(decalUidNode, hookCtx, context); + var coords = serializationManager.Read(decalData, hookCtx, context); + + var chunkOrigin = SharedMapSystem.GetChunkIndices(coords, SharedDecalSystem.ChunkSize); + var chunk = dictionary.GetOrNew(chunkOrigin); + var decal = new Decal(coords, data.Id, data.Color, data.Angle, data.ZIndex, data.Cleanable); + chunk.Decals.Add(dUid, decal); + } + } + } + else + { + dictionary = serializationManager.Read>(node, hookCtx, context, notNullableOverride: true); + } var uids = new SortedSet(); var uidChunkMap = new Dictionary(); @@ -59,7 +95,116 @@ namespace Content.Shared.Decals bool alwaysWrite = false, ISerializationContext? context = null) { - return serializationManager.WriteValue(value.ChunkCollection, alwaysWrite, context, notNullableOverride: true); + var lookup = new Dictionary>(); + var decalLookup = new Dictionary(); + + var allData = new MappingDataNode(); + // Want consistent chunk + decal ordering so diffs aren't mangled + var chunks = new List(value.ChunkCollection.Keys); + chunks.Sort((x, y) => x.X == y.X ? x.Y.CompareTo(y.Y) : x.X.CompareTo(y.X)); + var nodes = new SequenceDataNode(); + + // Assuming decal indices stay consistent: + // We'll write decals by + // - decaldata + // - decal uid + // - additional decal data + + // Build all of the decal lookups first. + foreach (var chunk in value.ChunkCollection.Values) + { + var sortedDecals = new List(chunk.Decals.Keys); + sortedDecals.Sort(); + + foreach (var (uid, decal) in chunk.Decals) + { + var data = new DecalData(decal); + var existing = lookup.GetOrNew(data); + existing.Add(uid); + decalLookup[uid] = decal; + } + } + + var lookupIndex = 0; + + foreach (var (data, uids) in lookup) + { + var lookupNode = new MappingDataNode { { "node", serializationManager.WriteValue(data, alwaysWrite, context) } }; + var decks = new MappingDataNode(); + + uids.Sort(); + + foreach (var uid in uids) + { + var decal = decalLookup[uid]; + // Inline coordinates + decks.Add(serializationManager.WriteValue(uid, alwaysWrite, context), serializationManager.WriteValue(decal.Coordinates, alwaysWrite, context)); + } + + lookupNode.Add("decals", decks); + nodes.Add(lookupNode); + } + + allData.Add("version", 2.ToString(CultureInfo.InvariantCulture)); + allData.Add("nodes", nodes); + + return allData; + } + + [DataDefinition] + private readonly struct DecalData : IEquatable + { + [DataField("id")] + public readonly string Id = string.Empty; + + [DataField("color")] + public readonly Color? Color; + + [DataField("angle")] + public readonly Angle Angle = Angle.Zero; + + [DataField("zIndex")] + public readonly int ZIndex; + + [DataField("cleanable")] + public readonly bool Cleanable; + + public DecalData(string id, Color? color, Angle angle, int zIndex, bool cleanable) + { + Id = id; + Color = color; + Angle = angle; + ZIndex = zIndex; + Cleanable = cleanable; + } + + public DecalData(Decal decal) + { + Id = decal.Id; + Color = decal.Color; + Angle = decal.Angle; + ZIndex = decal.ZIndex; + Cleanable = decal.Cleanable; + } + + public bool Equals(DecalData other) + { + return Id == other.Id && + Nullable.Equals(Color, other.Color) && + Angle.Equals(other.Angle) && + ZIndex == other.ZIndex && + Cleanable == other.Cleanable; + } + + public override bool Equals(object? obj) + { + return obj is DecalData other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(Id, Color, Angle, ZIndex, Cleanable); + } } } }