diff --git a/DotTiled/Serialization/Helpers.Data.cs b/DotTiled/Serialization/Helpers.Data.cs deleted file mode 100644 index ab1fadc..0000000 --- a/DotTiled/Serialization/Helpers.Data.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; - -namespace DotTiled; - -internal static partial class Helpers -{ - internal static class Data - { - internal static uint[] ReadMemoryStreamAsInt32Array(Stream stream) - { - var finalValues = new List(); - var int32Bytes = new byte[4]; - while (stream.Read(int32Bytes, 0, 4) == 4) - { - var value = BitConverter.ToUInt32(int32Bytes, 0); - finalValues.Add(value); - } - return [.. finalValues]; - } - - internal static uint[] DecompressGZip(MemoryStream stream) - { - using var decompressedStream = new GZipStream(stream, CompressionMode.Decompress); - return ReadMemoryStreamAsInt32Array(decompressedStream); - } - - internal static uint[] DecompressZLib(MemoryStream stream) - { - using var decompressedStream = new ZLibStream(stream, CompressionMode.Decompress); - return ReadMemoryStreamAsInt32Array(decompressedStream); - } - - internal static uint[] ReadBytesAsInt32Array(byte[] bytes) - { - var intArray = new uint[bytes.Length / 4]; - for (var i = 0; i < intArray.Length; i++) - { - intArray[i] = BitConverter.ToUInt32(bytes, i * 4); - } - - return intArray; - } - - internal static (uint[] GlobalTileIDs, FlippingFlags[] FlippingFlags) ReadAndClearFlippingFlagsFromGIDs(uint[] globalTileIDs) - { - var clearedGlobalTileIDs = new uint[globalTileIDs.Length]; - var flippingFlags = new FlippingFlags[globalTileIDs.Length]; - for (var i = 0; i < globalTileIDs.Length; i++) - { - var gid = globalTileIDs[i]; - var flags = gid & 0xF0000000u; - flippingFlags[i] = (FlippingFlags)flags; - clearedGlobalTileIDs[i] = gid & 0x0FFFFFFFu; - } - - return (clearedGlobalTileIDs, flippingFlags); - } - } -} diff --git a/DotTiled/Serialization/Helpers.cs b/DotTiled/Serialization/Helpers.cs new file mode 100644 index 0000000..905cb9f --- /dev/null +++ b/DotTiled/Serialization/Helpers.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; + +namespace DotTiled; + +internal static partial class Helpers +{ + internal static uint[] ReadMemoryStreamAsInt32Array(Stream stream) + { + var finalValues = new List(); + var int32Bytes = new byte[4]; + while (stream.Read(int32Bytes, 0, 4) == 4) + { + var value = BitConverter.ToUInt32(int32Bytes, 0); + finalValues.Add(value); + } + return [.. finalValues]; + } + + internal static uint[] DecompressGZip(MemoryStream stream) + { + using var decompressedStream = new GZipStream(stream, CompressionMode.Decompress); + return ReadMemoryStreamAsInt32Array(decompressedStream); + } + + internal static uint[] DecompressZLib(MemoryStream stream) + { + using var decompressedStream = new ZLibStream(stream, CompressionMode.Decompress); + return ReadMemoryStreamAsInt32Array(decompressedStream); + } + + internal static uint[] ReadBytesAsInt32Array(byte[] bytes) + { + var intArray = new uint[bytes.Length / 4]; + for (var i = 0; i < intArray.Length; i++) + { + intArray[i] = BitConverter.ToUInt32(bytes, i * 4); + } + + return intArray; + } + + internal static (uint[] GlobalTileIDs, FlippingFlags[] FlippingFlags) ReadAndClearFlippingFlagsFromGIDs(uint[] globalTileIDs) + { + var clearedGlobalTileIDs = new uint[globalTileIDs.Length]; + var flippingFlags = new FlippingFlags[globalTileIDs.Length]; + for (var i = 0; i < globalTileIDs.Length; i++) + { + var gid = globalTileIDs[i]; + var flags = gid & 0xF0000000u; + flippingFlags[i] = (FlippingFlags)flags; + clearedGlobalTileIDs[i] = gid & 0x0FFFFFFFu; + } + + return (clearedGlobalTileIDs, flippingFlags); + } + + internal static ImageFormat ParseImageFormatFromSource(string source) + { + var extension = Path.GetExtension(source).ToLowerInvariant(); + return extension switch + { + ".png" => ImageFormat.Png, + ".gif" => ImageFormat.Gif, + ".jpg" => ImageFormat.Jpg, + ".jpeg" => ImageFormat.Jpg, + ".bmp" => ImageFormat.Bmp, + _ => throw new NotSupportedException($"Unsupported image format '{extension}'") + }; + } + + internal static Dictionary MergeProperties(Dictionary? baseProperties, Dictionary overrideProperties) + { + if (baseProperties is null) + return overrideProperties ?? new Dictionary(); + + if (overrideProperties is null) + return baseProperties; + + var result = baseProperties.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Clone()); + foreach (var (key, value) in overrideProperties) + { + if (!result.TryGetValue(key, out var baseProp)) + { + result[key] = value; + continue; + } + else + { + if (value is ClassProperty classProp) + { + ((ClassProperty)baseProp).Properties = MergeProperties(((ClassProperty)baseProp).Properties, classProp.Properties); + } + else + { + result[key] = value; + } + } + } + + return result; + } + + internal static void SetAtMostOnce(ref T? field, T value, string fieldName) + { + if (field is not null) + throw new InvalidOperationException($"{fieldName} already set"); + + field = value; + } + + internal static void SetAtMostOnceUsingCounter(ref T? field, T value, string fieldName, ref int counter) + { + if (counter > 0) + throw new InvalidOperationException($"{fieldName} already set"); + + field = value; + counter++; + } +} diff --git a/DotTiled/Serialization/Tmj/ExtensionsUtf8JsonReader.cs b/DotTiled/Serialization/Tmj/ExtensionsUtf8JsonReader.cs deleted file mode 100644 index 1c98bc6..0000000 --- a/DotTiled/Serialization/Tmj/ExtensionsUtf8JsonReader.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Linq; -using System.Text.Json; - -namespace DotTiled; - -internal partial class Tmj -{ - internal abstract class JsonProperty(string propertyName) - { - internal string PropertyName { get; } = propertyName; - } - - internal delegate void UseReader(ref Utf8JsonReader reader); - - internal class RequiredProperty(string propertyName, UseReader useReader) : JsonProperty(propertyName) - { - internal UseReader UseReader { get; } = useReader; - } - - internal class OptionalProperty(string propertyName, UseReader useReader, bool allowNull = true) : JsonProperty(propertyName) - { - internal UseReader UseReader { get; } = useReader; - internal bool AllowNull { get; } = allowNull; - } -} - -internal static class ExtensionsUtf8JsonReader -{ - internal static T Progress(ref this Utf8JsonReader reader, T value) - { - reader.Read(); - return value; - } - - internal static void MoveToContent(this ref Utf8JsonReader reader) - { - while (reader.Read() && reader.TokenType == JsonTokenType.Comment || - reader.TokenType == JsonTokenType.None) - ; - } - - internal delegate void ProcessProperty(ref Utf8JsonReader reader); - - private static void ProcessJsonObject(this ref Utf8JsonReader reader, (string PropertyName, ProcessProperty Processor)[] processors) - { - if (reader.TokenType != JsonTokenType.StartObject) - throw new JsonException("Expected start of object."); - - reader.Read(); - - while (reader.TokenType != JsonTokenType.EndObject) - { - if (reader.TokenType != JsonTokenType.PropertyName) - throw new JsonException("Expected property name."); - - var propertyName = reader.GetString(); - reader.Read(); - - if (!processors.Any(x => x.PropertyName == propertyName)) - { - var depthBefore = reader.CurrentDepth; - - while (reader.TokenType != JsonTokenType.PropertyName || reader.CurrentDepth > depthBefore) - reader.Read(); - - continue; - } - - var processor = processors.First(x => x.PropertyName == propertyName).Processor; - processor(ref reader); - } - - if (reader.TokenType != JsonTokenType.EndObject) - throw new JsonException("Expected end of object."); - - reader.Read(); - } - - internal static void ProcessJsonObject(this ref Utf8JsonReader reader, Tmj.JsonProperty[] properties, string objectTypeName) - { - List processedProperties = []; - - ProcessJsonObject(ref reader, properties.Select(x => (x.PropertyName, (ref Utf8JsonReader reader) => - { - if (processedProperties.Contains(x.PropertyName)) - throw new JsonException($"Property '{x.PropertyName}' was already processed."); - - processedProperties.Add(x.PropertyName); - - if (x is Tmj.RequiredProperty req) - { - if (reader.TokenType == JsonTokenType.Null) - throw new JsonException($"Required property '{req.PropertyName}' cannot be null when reading {objectTypeName}."); - - req.UseReader(ref reader); - } - else if (x is Tmj.OptionalProperty opt) - { - if (reader.TokenType == JsonTokenType.Null && !opt.AllowNull) - throw new JsonException($"Value cannot be null for optional property '{opt.PropertyName}' when reading {objectTypeName}."); - else if (reader.TokenType == JsonTokenType.Null && opt.AllowNull) - return; - - opt.UseReader(ref reader); - } - } - )).ToArray()); - - foreach (var property in properties) - { - if (property is Tmj.RequiredProperty && !processedProperties.Contains(property.PropertyName)) - throw new JsonException($"Required property '{property.PropertyName}' was not found when reading {objectTypeName}."); - } - } - - internal delegate void UseReader(ref Utf8JsonReader reader); - - internal static void ProcessJsonArray(this ref Utf8JsonReader reader, UseReader useReader) - { - if (reader.TokenType != JsonTokenType.StartArray) - throw new JsonException("Expected start of array."); - - reader.Read(); - - while (reader.TokenType != JsonTokenType.EndArray) - { - useReader(ref reader); - } - - if (reader.TokenType != JsonTokenType.EndArray) - throw new JsonException("Expected end of array."); - - reader.Read(); - } -} diff --git a/DotTiled/Serialization/Tmj/Tmj.Data.cs b/DotTiled/Serialization/Tmj/Tmj.Data.cs index 05f24c5..0b05c01 100644 --- a/DotTiled/Serialization/Tmj/Tmj.Data.cs +++ b/DotTiled/Serialization/Tmj/Tmj.Data.cs @@ -49,7 +49,7 @@ internal partial class Tmj { // Array of uint var data = element.GetValueAsList(e => e.GetValueAs()).ToArray(); - var (globalTileIDs, flippingFlags) = Helpers.Data.ReadAndClearFlippingFlagsFromGIDs(data); + var (globalTileIDs, flippingFlags) = Helpers.ReadAndClearFlippingFlagsFromGIDs(data); return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags, Chunks = null }; } else if (encoding == DataEncoding.Base64) @@ -58,21 +58,21 @@ internal partial class Tmj if (compression == null) { - var data = Helpers.Data.ReadBytesAsInt32Array(base64Data); - var (globalTileIDs, flippingFlags) = Helpers.Data.ReadAndClearFlippingFlagsFromGIDs(data); + var data = Helpers.ReadBytesAsInt32Array(base64Data); + var (globalTileIDs, flippingFlags) = Helpers.ReadAndClearFlippingFlagsFromGIDs(data); return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags, Chunks = null }; } using var stream = new MemoryStream(base64Data); var decompressed = compression switch { - DataCompression.GZip => Helpers.Data.DecompressGZip(stream), - DataCompression.ZLib => Helpers.Data.DecompressZLib(stream), + DataCompression.GZip => Helpers.DecompressGZip(stream), + DataCompression.ZLib => Helpers.DecompressZLib(stream), _ => throw new JsonException($"Unsupported compression '{compression}'.") }; { - var (globalTileIDs, flippingFlags) = Helpers.Data.ReadAndClearFlippingFlagsFromGIDs(decompressed); + var (globalTileIDs, flippingFlags) = Helpers.ReadAndClearFlippingFlagsFromGIDs(decompressed); return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags, Chunks = null }; } } diff --git a/DotTiled/Serialization/Tmj/Tmj.Group.cs b/DotTiled/Serialization/Tmj/Tmj.Group.cs new file mode 100644 index 0000000..44e8b4d --- /dev/null +++ b/DotTiled/Serialization/Tmj/Tmj.Group.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Numerics; +using System.Text.Json; + +namespace DotTiled; + +internal partial class Tmj +{ + internal static Group ReadGroup( + JsonElement element, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) + { + var id = element.GetRequiredProperty("id"); + var name = element.GetRequiredProperty("name"); + var @class = element.GetOptionalProperty("class", ""); + var opacity = element.GetOptionalProperty("opacity", 1.0f); + var visible = element.GetOptionalProperty("visible", true); + var tintColor = element.GetOptionalPropertyParseable("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var offsetX = element.GetOptionalProperty("offsetx", 0.0f); + var offsetY = element.GetOptionalProperty("offsety", 0.0f); + var parallaxX = element.GetOptionalProperty("parallaxx", 1.0f); + var parallaxY = element.GetOptionalProperty("parallaxy", 1.0f); + var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); + var layers = element.GetOptionalPropertyCustom>("layers", e => e.GetValueAsList(el => ReadLayer(el, externalTemplateResolver, customTypeDefinitions)), []); + + return new Group + { + ID = id, + Name = name, + Class = @class, + Opacity = opacity, + Visible = visible, + TintColor = tintColor, + OffsetX = offsetX, + OffsetY = offsetY, + ParallaxX = parallaxX, + ParallaxY = parallaxY, + Properties = properties, + Layers = layers + }; + } +} diff --git a/DotTiled/Serialization/Tmj/Tmj.ImageLayer.cs b/DotTiled/Serialization/Tmj/Tmj.ImageLayer.cs new file mode 100644 index 0000000..d315891 --- /dev/null +++ b/DotTiled/Serialization/Tmj/Tmj.ImageLayer.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Numerics; +using System.Text.Json; + +namespace DotTiled; + +internal partial class Tmj +{ + internal static ImageLayer ReadImageLayer( + JsonElement element, + IReadOnlyCollection customTypeDefinitions) + { + var id = element.GetRequiredProperty("id"); + var name = element.GetRequiredProperty("name"); + var @class = element.GetOptionalProperty("class", ""); + var opacity = element.GetOptionalProperty("opacity", 1.0f); + var visible = element.GetOptionalProperty("visible", true); + var tintColor = element.GetOptionalPropertyParseable("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var offsetX = element.GetOptionalProperty("offsetx", 0.0f); + var offsetY = element.GetOptionalProperty("offsety", 0.0f); + var parallaxX = element.GetOptionalProperty("parallaxx", 1.0f); + var parallaxY = element.GetOptionalProperty("parallaxy", 1.0f); + var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); + + var image = element.GetRequiredProperty("image"); + var repeatX = element.GetRequiredProperty("repeatx"); + var repeatY = element.GetRequiredProperty("repeaty"); + var transparentColor = element.GetOptionalPropertyParseable("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var x = element.GetOptionalProperty("x", 0); + var y = element.GetOptionalProperty("y", 0); + + var imgModel = new Image + { + Format = Helpers.ParseImageFormatFromSource(image), + Height = 0, + Width = 0, + Source = image, + TransparentColor = transparentColor + }; + + return new ImageLayer + { + ID = id, + Name = name, + Class = @class, + Opacity = opacity, + Visible = visible, + TintColor = tintColor, + OffsetX = offsetX, + OffsetY = offsetY, + ParallaxX = parallaxX, + ParallaxY = parallaxY, + Properties = properties, + Image = imgModel, + RepeatX = repeatX, + RepeatY = repeatY, + X = x, + Y = y + }; + } +} diff --git a/DotTiled/Serialization/Tmj/Tmj.Layer.cs b/DotTiled/Serialization/Tmj/Tmj.Layer.cs index 68cd92a..f14d614 100644 --- a/DotTiled/Serialization/Tmj/Tmj.Layer.cs +++ b/DotTiled/Serialization/Tmj/Tmj.Layer.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.IO; -using System.IO.Compression; -using System.Linq; using System.Numerics; using System.Text.Json; @@ -22,404 +19,9 @@ internal partial class Tmj { "tilelayer" => ReadTileLayer(element, customTypeDefinitions), "objectgroup" => ReadObjectLayer(element, externalTemplateResolver, customTypeDefinitions), - // "imagelayer" => ReadImageLayer(element), + "imagelayer" => ReadImageLayer(element, customTypeDefinitions), "group" => ReadGroup(element, externalTemplateResolver, customTypeDefinitions), _ => throw new JsonException($"Unsupported layer type '{type}'.") }; } - - internal static TileLayer ReadTileLayer( - JsonElement element, - IReadOnlyCollection customTypeDefinitions) - { - var compression = element.GetOptionalPropertyParseable("compression", s => s switch - { - "zlib" => DataCompression.ZLib, - "gzip" => DataCompression.GZip, - "" => null, - _ => throw new JsonException($"Unsupported compression '{s}'.") - }, null); - var encoding = element.GetOptionalPropertyParseable("encoding", s => s switch - { - "csv" => DataEncoding.Csv, - "base64" => DataEncoding.Base64, - _ => throw new JsonException($"Unsupported encoding '{s}'.") - }, DataEncoding.Csv); - var chunks = element.GetOptionalPropertyCustom("chunks", e => ReadDataAsChunks(e, compression, encoding), null); - var @class = element.GetOptionalProperty("class", ""); - var data = element.GetOptionalPropertyCustom("data", e => ReadDataWithoutChunks(e, compression, encoding), null); - var height = element.GetRequiredProperty("height"); - var id = element.GetRequiredProperty("id"); - var name = element.GetRequiredProperty("name"); - var offsetX = element.GetOptionalProperty("offsetx", 0.0f); - var offsetY = element.GetOptionalProperty("offsety", 0.0f); - var opacity = element.GetOptionalProperty("opacity", 1.0f); - var parallaxx = element.GetOptionalProperty("parallaxx", 1.0f); - var parallaxy = element.GetOptionalProperty("parallaxy", 1.0f); - var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); - var repeatX = element.GetOptionalProperty("repeatx", false); - var repeatY = element.GetOptionalProperty("repeaty", false); - var startX = element.GetOptionalProperty("startx", 0); - var startY = element.GetOptionalProperty("starty", 0); - var tintColor = element.GetOptionalPropertyParseable("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); - var transparentColor = element.GetOptionalPropertyParseable("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); - var visible = element.GetOptionalProperty("visible", true); - var width = element.GetRequiredProperty("width"); - var x = element.GetRequiredProperty("x"); - var y = element.GetRequiredProperty("y"); - - if ((data ?? chunks) is null) - throw new JsonException("Tile layer does not contain data."); - - return new TileLayer - { - ID = id, - Name = name, - Class = @class, - Opacity = opacity, - Visible = visible, - TintColor = tintColor, - OffsetX = offsetX, - OffsetY = offsetY, - ParallaxX = parallaxx, - ParallaxY = parallaxy, - Properties = properties, - X = x, - Y = y, - Width = width, - Height = height, - Data = data ?? chunks - }; - } - - internal static Group ReadGroup( - JsonElement element, - Func externalTemplateResolver, - IReadOnlyCollection customTypeDefinitions) - { - var id = element.GetRequiredProperty("id"); - var name = element.GetRequiredProperty("name"); - var @class = element.GetOptionalProperty("class", ""); - var opacity = element.GetOptionalProperty("opacity", 1.0f); - var visible = element.GetOptionalProperty("visible", true); - var tintColor = element.GetOptionalPropertyParseable("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); - var offsetX = element.GetOptionalProperty("offsetx", 0.0f); - var offsetY = element.GetOptionalProperty("offsety", 0.0f); - var parallaxX = element.GetOptionalProperty("parallaxx", 1.0f); - var parallaxY = element.GetOptionalProperty("parallaxy", 1.0f); - var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); - var layers = element.GetOptionalPropertyCustom>("layers", e => e.GetValueAsList(el => ReadLayer(el, externalTemplateResolver, customTypeDefinitions)), []); - - return new Group - { - ID = id, - Name = name, - Class = @class, - Opacity = opacity, - Visible = visible, - TintColor = tintColor, - OffsetX = offsetX, - OffsetY = offsetY, - ParallaxX = parallaxX, - ParallaxY = parallaxY, - Properties = properties, - Layers = layers - }; - } - - internal static ObjectLayer ReadObjectLayer( - JsonElement element, - Func externalTemplateResolver, - IReadOnlyCollection customTypeDefinitions) - { - var id = element.GetRequiredProperty("id"); - var name = element.GetRequiredProperty("name"); - var @class = element.GetOptionalProperty("class", ""); - var opacity = element.GetOptionalProperty("opacity", 1.0f); - var visible = element.GetOptionalProperty("visible", true); - var tintColor = element.GetOptionalPropertyParseable("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); - var offsetX = element.GetOptionalProperty("offsetx", 0.0f); - var offsetY = element.GetOptionalProperty("offsety", 0.0f); - var parallaxX = element.GetOptionalProperty("parallaxx", 1.0f); - var parallaxY = element.GetOptionalProperty("parallaxy", 1.0f); - var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); - - var x = element.GetOptionalProperty("x", 0); - var y = element.GetOptionalProperty("y", 0); - var width = element.GetOptionalProperty("width", null); - var height = element.GetOptionalProperty("height", null); - var color = element.GetOptionalPropertyParseable("color", s => Color.Parse(s, CultureInfo.InvariantCulture), null); - var drawOrder = element.GetOptionalPropertyParseable("draworder", s => s switch - { - "topdown" => DrawOrder.TopDown, - "index" => DrawOrder.Index, - _ => throw new JsonException($"Unknown draw order '{s}'.") - }, DrawOrder.TopDown); - - var objects = element.GetOptionalPropertyCustom>("objects", e => e.GetValueAsList(el => ReadObject(el, externalTemplateResolver, customTypeDefinitions)), []); - - return new ObjectLayer - { - ID = id, - Name = name, - Class = @class, - Opacity = opacity, - Visible = visible, - TintColor = tintColor, - OffsetX = offsetX, - OffsetY = offsetY, - ParallaxX = parallaxX, - ParallaxY = parallaxY, - Properties = properties, - X = x, - Y = y, - Width = width, - Height = height, - Color = color, - DrawOrder = drawOrder, - Objects = objects - }; - } - - internal static Object ReadObject( - JsonElement element, - Func externalTemplateResolver, - IReadOnlyCollection customTypeDefinitions) - { - uint? idDefault = null; - string nameDefault = ""; - string typeDefault = ""; - float xDefault = 0f; - float yDefault = 0f; - float widthDefault = 0f; - float heightDefault = 0f; - float rotationDefault = 0f; - uint? gidDefault = null; - bool visibleDefault = true; - bool ellipseDefault = false; - bool pointDefault = false; - List? polygonDefault = null; - List? polylineDefault = null; - Dictionary? propertiesDefault = null; - - var template = element.GetOptionalProperty("template", null); - if (template is not null) - { - var resolvedTemplate = externalTemplateResolver(template); - var templObj = resolvedTemplate.Object; - - idDefault = templObj.ID; - nameDefault = templObj.Name; - typeDefault = templObj.Type; - xDefault = templObj.X; - yDefault = templObj.Y; - widthDefault = templObj.Width; - heightDefault = templObj.Height; - rotationDefault = templObj.Rotation; - gidDefault = templObj.GID; - visibleDefault = templObj.Visible; - propertiesDefault = templObj.Properties; - ellipseDefault = templObj is EllipseObject; - pointDefault = templObj is PointObject; - polygonDefault = (templObj is PolygonObject polygonObj) ? polygonObj.Points : null; - polylineDefault = (templObj is PolylineObject polylineObj) ? polylineObj.Points : null; - } - - var ellipse = element.GetOptionalProperty("ellipse", ellipseDefault); - var gid = element.GetOptionalProperty("gid", gidDefault); - var height = element.GetOptionalProperty("height", heightDefault); - var id = element.GetOptionalProperty("id", idDefault); - var name = element.GetOptionalProperty("name", nameDefault); - var point = element.GetOptionalProperty("point", pointDefault); - var polygon = element.GetOptionalPropertyCustom?>("polygon", e => ReadPoints(e), polygonDefault); - var polyline = element.GetOptionalPropertyCustom?>("polyline", e => ReadPoints(e), polylineDefault); - var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), propertiesDefault); - var rotation = element.GetOptionalProperty("rotation", rotationDefault); - var text = element.GetOptionalPropertyCustom("text", ReadText, null); - var type = element.GetOptionalProperty("type", typeDefault); - var visible = element.GetOptionalProperty("visible", visibleDefault); - var width = element.GetOptionalProperty("width", widthDefault); - var x = element.GetOptionalProperty("x", xDefault); - var y = element.GetOptionalProperty("y", yDefault); - - if (ellipse) - { - return new EllipseObject - { - ID = id, - Name = name, - Type = type, - X = x, - Y = y, - Width = width, - Height = height, - Rotation = rotation, - GID = gid, - Visible = visible, - Template = template, - Properties = properties - }; - } - - if (point) - { - return new PointObject - { - ID = id, - Name = name, - Type = type, - X = x, - Y = y, - Width = width, - Height = height, - Rotation = rotation, - GID = gid, - Visible = visible, - Template = template, - Properties = properties - }; - } - - if (polygon is not null) - { - return new PolygonObject - { - ID = id, - Name = name, - Type = type, - X = x, - Y = y, - Width = width, - Height = height, - Rotation = rotation, - GID = gid, - Visible = visible, - Template = template, - Properties = properties, - Points = polygon - }; - } - - if (polyline is not null) - { - return new PolylineObject - { - ID = id, - Name = name, - Type = type, - X = x, - Y = y, - Width = width, - Height = height, - Rotation = rotation, - GID = gid, - Visible = visible, - Template = template, - Properties = properties, - Points = polyline - }; - } - - if (text is not null) - { - text.ID = id; - text.Name = name; - text.Type = type; - text.X = x; - text.Y = y; - text.Width = width; - text.Height = height; - text.Rotation = rotation; - text.GID = gid; - text.Visible = visible; - text.Template = template; - text.Properties = properties; - return text; - } - - return new RectangleObject - { - ID = id, - Name = name, - Type = type, - X = x, - Y = y, - Width = width, - Height = height, - Rotation = rotation, - GID = gid, - Visible = visible, - Template = template, - Properties = properties - }; - } - - internal static List ReadPoints(JsonElement element) => - element.GetValueAsList(e => - { - var x = e.GetRequiredProperty("x"); - var y = e.GetRequiredProperty("y"); - return new Vector2(x, y); - }); - - internal static Template ReadTemplate( - JsonElement element, - Func externalTilesetResolver, - Func externalTemplateResolver, - IReadOnlyCollection customTypeDefinitions) - { - var type = element.GetRequiredProperty("type"); - var tileset = element.GetOptionalPropertyCustom("tileset", el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions), null); - var @object = element.GetRequiredPropertyCustom("object", el => ReadObject(el, externalTemplateResolver, customTypeDefinitions)); - - return new Template - { - Tileset = tileset, - Object = @object - }; - } - - internal static TextObject ReadText(JsonElement element) - { - var bold = element.GetOptionalProperty("bold", false); - var color = element.GetOptionalPropertyParseable("color", s => Color.Parse(s, CultureInfo.InvariantCulture), Color.Parse("#00000000", CultureInfo.InvariantCulture)); - var fontfamily = element.GetOptionalProperty("fontfamily", "sans-serif"); - var halign = element.GetOptionalPropertyParseable("halign", s => s switch - { - "left" => TextHorizontalAlignment.Left, - "center" => TextHorizontalAlignment.Center, - "right" => TextHorizontalAlignment.Right, - _ => throw new JsonException($"Unknown horizontal alignment '{s}'.") - }, TextHorizontalAlignment.Left); - var italic = element.GetOptionalProperty("italic", false); - var kerning = element.GetOptionalProperty("kerning", true); - var pixelsize = element.GetOptionalProperty("pixelsize", 16); - var strikeout = element.GetOptionalProperty("strikeout", false); - var text = element.GetRequiredProperty("text"); - var underline = element.GetOptionalProperty("underline", false); - var valign = element.GetOptionalPropertyParseable("valign", s => s switch - { - "top" => TextVerticalAlignment.Top, - "center" => TextVerticalAlignment.Center, - "bottom" => TextVerticalAlignment.Bottom, - _ => throw new JsonException($"Unknown vertical alignment '{s}'.") - }, TextVerticalAlignment.Top); - var wrap = element.GetOptionalProperty("wrap", false); - - return new TextObject - { - Bold = bold, - Color = color, - FontFamily = fontfamily, - HorizontalAlignment = halign, - Italic = italic, - Kerning = kerning, - PixelSize = pixelsize, - Strikeout = strikeout, - Text = text, - Underline = underline, - VerticalAlignment = valign, - Wrap = wrap - }; - } } diff --git a/DotTiled/Serialization/Tmj/Tmj.ObjectLayer.cs b/DotTiled/Serialization/Tmj/Tmj.ObjectLayer.cs new file mode 100644 index 0000000..2fdf3c9 --- /dev/null +++ b/DotTiled/Serialization/Tmj/Tmj.ObjectLayer.cs @@ -0,0 +1,289 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Numerics; +using System.Text.Json; + +namespace DotTiled; + +internal partial class Tmj +{ + internal static ObjectLayer ReadObjectLayer( + JsonElement element, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) + { + var id = element.GetRequiredProperty("id"); + var name = element.GetRequiredProperty("name"); + var @class = element.GetOptionalProperty("class", ""); + var opacity = element.GetOptionalProperty("opacity", 1.0f); + var visible = element.GetOptionalProperty("visible", true); + var tintColor = element.GetOptionalPropertyParseable("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var offsetX = element.GetOptionalProperty("offsetx", 0.0f); + var offsetY = element.GetOptionalProperty("offsety", 0.0f); + var parallaxX = element.GetOptionalProperty("parallaxx", 1.0f); + var parallaxY = element.GetOptionalProperty("parallaxy", 1.0f); + var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); + + var x = element.GetOptionalProperty("x", 0); + var y = element.GetOptionalProperty("y", 0); + var width = element.GetOptionalProperty("width", null); + var height = element.GetOptionalProperty("height", null); + var color = element.GetOptionalPropertyParseable("color", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var drawOrder = element.GetOptionalPropertyParseable("draworder", s => s switch + { + "topdown" => DrawOrder.TopDown, + "index" => DrawOrder.Index, + _ => throw new JsonException($"Unknown draw order '{s}'.") + }, DrawOrder.TopDown); + + var objects = element.GetOptionalPropertyCustom>("objects", e => e.GetValueAsList(el => ReadObject(el, externalTemplateResolver, customTypeDefinitions)), []); + + return new ObjectLayer + { + ID = id, + Name = name, + Class = @class, + Opacity = opacity, + Visible = visible, + TintColor = tintColor, + OffsetX = offsetX, + OffsetY = offsetY, + ParallaxX = parallaxX, + ParallaxY = parallaxY, + Properties = properties, + X = x, + Y = y, + Width = width, + Height = height, + Color = color, + DrawOrder = drawOrder, + Objects = objects + }; + } + + internal static Object ReadObject( + JsonElement element, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) + { + uint? idDefault = null; + string nameDefault = ""; + string typeDefault = ""; + float xDefault = 0f; + float yDefault = 0f; + float widthDefault = 0f; + float heightDefault = 0f; + float rotationDefault = 0f; + uint? gidDefault = null; + bool visibleDefault = true; + bool ellipseDefault = false; + bool pointDefault = false; + List? polygonDefault = null; + List? polylineDefault = null; + Dictionary? propertiesDefault = null; + + var template = element.GetOptionalProperty("template", null); + if (template is not null) + { + var resolvedTemplate = externalTemplateResolver(template); + var templObj = resolvedTemplate.Object; + + idDefault = templObj.ID; + nameDefault = templObj.Name; + typeDefault = templObj.Type; + xDefault = templObj.X; + yDefault = templObj.Y; + widthDefault = templObj.Width; + heightDefault = templObj.Height; + rotationDefault = templObj.Rotation; + gidDefault = templObj.GID; + visibleDefault = templObj.Visible; + propertiesDefault = templObj.Properties; + ellipseDefault = templObj is EllipseObject; + pointDefault = templObj is PointObject; + polygonDefault = (templObj is PolygonObject polygonObj) ? polygonObj.Points : null; + polylineDefault = (templObj is PolylineObject polylineObj) ? polylineObj.Points : null; + } + + var ellipse = element.GetOptionalProperty("ellipse", ellipseDefault); + var gid = element.GetOptionalProperty("gid", gidDefault); + var height = element.GetOptionalProperty("height", heightDefault); + var id = element.GetOptionalProperty("id", idDefault); + var name = element.GetOptionalProperty("name", nameDefault); + var point = element.GetOptionalProperty("point", pointDefault); + var polygon = element.GetOptionalPropertyCustom?>("polygon", e => ReadPoints(e), polygonDefault); + var polyline = element.GetOptionalPropertyCustom?>("polyline", e => ReadPoints(e), polylineDefault); + var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), propertiesDefault); + var rotation = element.GetOptionalProperty("rotation", rotationDefault); + var text = element.GetOptionalPropertyCustom("text", ReadText, null); + var type = element.GetOptionalProperty("type", typeDefault); + var visible = element.GetOptionalProperty("visible", visibleDefault); + var width = element.GetOptionalProperty("width", widthDefault); + var x = element.GetOptionalProperty("x", xDefault); + var y = element.GetOptionalProperty("y", yDefault); + + if (ellipse) + { + return new EllipseObject + { + ID = id, + Name = name, + Type = type, + X = x, + Y = y, + Width = width, + Height = height, + Rotation = rotation, + GID = gid, + Visible = visible, + Template = template, + Properties = properties + }; + } + + if (point) + { + return new PointObject + { + ID = id, + Name = name, + Type = type, + X = x, + Y = y, + Width = width, + Height = height, + Rotation = rotation, + GID = gid, + Visible = visible, + Template = template, + Properties = properties + }; + } + + if (polygon is not null) + { + return new PolygonObject + { + ID = id, + Name = name, + Type = type, + X = x, + Y = y, + Width = width, + Height = height, + Rotation = rotation, + GID = gid, + Visible = visible, + Template = template, + Properties = properties, + Points = polygon + }; + } + + if (polyline is not null) + { + return new PolylineObject + { + ID = id, + Name = name, + Type = type, + X = x, + Y = y, + Width = width, + Height = height, + Rotation = rotation, + GID = gid, + Visible = visible, + Template = template, + Properties = properties, + Points = polyline + }; + } + + if (text is not null) + { + text.ID = id; + text.Name = name; + text.Type = type; + text.X = x; + text.Y = y; + text.Width = width; + text.Height = height; + text.Rotation = rotation; + text.GID = gid; + text.Visible = visible; + text.Template = template; + text.Properties = properties; + return text; + } + + return new RectangleObject + { + ID = id, + Name = name, + Type = type, + X = x, + Y = y, + Width = width, + Height = height, + Rotation = rotation, + GID = gid, + Visible = visible, + Template = template, + Properties = properties + }; + } + + internal static List ReadPoints(JsonElement element) => + element.GetValueAsList(e => + { + var x = e.GetRequiredProperty("x"); + var y = e.GetRequiredProperty("y"); + return new Vector2(x, y); + }); + + internal static TextObject ReadText(JsonElement element) + { + var bold = element.GetOptionalProperty("bold", false); + var color = element.GetOptionalPropertyParseable("color", s => Color.Parse(s, CultureInfo.InvariantCulture), Color.Parse("#00000000", CultureInfo.InvariantCulture)); + var fontfamily = element.GetOptionalProperty("fontfamily", "sans-serif"); + var halign = element.GetOptionalPropertyParseable("halign", s => s switch + { + "left" => TextHorizontalAlignment.Left, + "center" => TextHorizontalAlignment.Center, + "right" => TextHorizontalAlignment.Right, + _ => throw new JsonException($"Unknown horizontal alignment '{s}'.") + }, TextHorizontalAlignment.Left); + var italic = element.GetOptionalProperty("italic", false); + var kerning = element.GetOptionalProperty("kerning", true); + var pixelsize = element.GetOptionalProperty("pixelsize", 16); + var strikeout = element.GetOptionalProperty("strikeout", false); + var text = element.GetRequiredProperty("text"); + var underline = element.GetOptionalProperty("underline", false); + var valign = element.GetOptionalPropertyParseable("valign", s => s switch + { + "top" => TextVerticalAlignment.Top, + "center" => TextVerticalAlignment.Center, + "bottom" => TextVerticalAlignment.Bottom, + _ => throw new JsonException($"Unknown vertical alignment '{s}'.") + }, TextVerticalAlignment.Top); + var wrap = element.GetOptionalProperty("wrap", false); + + return new TextObject + { + Bold = bold, + Color = color, + FontFamily = fontfamily, + HorizontalAlignment = halign, + Italic = italic, + Kerning = kerning, + PixelSize = pixelsize, + Strikeout = strikeout, + Text = text, + Underline = underline, + VerticalAlignment = valign, + Wrap = wrap + }; + } +} diff --git a/DotTiled/Serialization/Tmj/Tmj.Properties.cs b/DotTiled/Serialization/Tmj/Tmj.Properties.cs index 2e01749..6981521 100644 --- a/DotTiled/Serialization/Tmj/Tmj.Properties.cs +++ b/DotTiled/Serialization/Tmj/Tmj.Properties.cs @@ -57,7 +57,7 @@ internal partial class Tmj var propsInType = CreateInstanceOfCustomClass(ccd); var props = element.GetOptionalPropertyCustom>("value", el => ReadCustomClassProperties(el, ccd, customTypeDefinitions), []); - var mergedProps = MergeProperties(propsInType, props); + var mergedProps = Helpers.MergeProperties(propsInType, props); return new ClassProperty { @@ -105,36 +105,4 @@ internal partial class Tmj { return customClassDefinition.Members.ToDictionary(m => m.Name, m => m.Clone()); } - - internal static Dictionary MergeProperties(Dictionary? baseProperties, Dictionary overrideProperties) - { - if (baseProperties is null) - return overrideProperties ?? new Dictionary(); - - if (overrideProperties is null) - return baseProperties; - - var result = baseProperties.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Clone()); - foreach (var (key, value) in overrideProperties) - { - if (!result.TryGetValue(key, out var baseProp)) - { - result[key] = value; - continue; - } - else - { - if (value is ClassProperty classProp) - { - ((ClassProperty)baseProp).Properties = MergeProperties(((ClassProperty)baseProp).Properties, classProp.Properties); - } - else - { - result[key] = value; - } - } - } - - return result; - } } diff --git a/DotTiled/Serialization/Tmj/Tmj.Template.cs b/DotTiled/Serialization/Tmj/Tmj.Template.cs new file mode 100644 index 0000000..79c7860 --- /dev/null +++ b/DotTiled/Serialization/Tmj/Tmj.Template.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Numerics; +using System.Text.Json; + +namespace DotTiled; + +internal partial class Tmj +{ + internal static Template ReadTemplate( + JsonElement element, + Func externalTilesetResolver, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) + { + var type = element.GetRequiredProperty("type"); + var tileset = element.GetOptionalPropertyCustom("tileset", el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions), null); + var @object = element.GetRequiredPropertyCustom("object", el => ReadObject(el, externalTemplateResolver, customTypeDefinitions)); + + return new Template + { + Tileset = tileset, + Object = @object + }; + } +} diff --git a/DotTiled/Serialization/Tmj/Tmj.TileLayer.cs b/DotTiled/Serialization/Tmj/Tmj.TileLayer.cs new file mode 100644 index 0000000..5528177 --- /dev/null +++ b/DotTiled/Serialization/Tmj/Tmj.TileLayer.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Numerics; +using System.Text.Json; + +namespace DotTiled; + +internal partial class Tmj +{ + + internal static TileLayer ReadTileLayer( + JsonElement element, + IReadOnlyCollection customTypeDefinitions) + { + var compression = element.GetOptionalPropertyParseable("compression", s => s switch + { + "zlib" => DataCompression.ZLib, + "gzip" => DataCompression.GZip, + "" => null, + _ => throw new JsonException($"Unsupported compression '{s}'.") + }, null); + var encoding = element.GetOptionalPropertyParseable("encoding", s => s switch + { + "csv" => DataEncoding.Csv, + "base64" => DataEncoding.Base64, + _ => throw new JsonException($"Unsupported encoding '{s}'.") + }, DataEncoding.Csv); + var chunks = element.GetOptionalPropertyCustom("chunks", e => ReadDataAsChunks(e, compression, encoding), null); + var @class = element.GetOptionalProperty("class", ""); + var data = element.GetOptionalPropertyCustom("data", e => ReadDataWithoutChunks(e, compression, encoding), null); + var height = element.GetRequiredProperty("height"); + var id = element.GetRequiredProperty("id"); + var name = element.GetRequiredProperty("name"); + var offsetX = element.GetOptionalProperty("offsetx", 0.0f); + var offsetY = element.GetOptionalProperty("offsety", 0.0f); + var opacity = element.GetOptionalProperty("opacity", 1.0f); + var parallaxx = element.GetOptionalProperty("parallaxx", 1.0f); + var parallaxy = element.GetOptionalProperty("parallaxy", 1.0f); + var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); + var repeatX = element.GetOptionalProperty("repeatx", false); + var repeatY = element.GetOptionalProperty("repeaty", false); + var startX = element.GetOptionalProperty("startx", 0); + var startY = element.GetOptionalProperty("starty", 0); + var tintColor = element.GetOptionalPropertyParseable("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var transparentColor = element.GetOptionalPropertyParseable("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var visible = element.GetOptionalProperty("visible", true); + var width = element.GetRequiredProperty("width"); + var x = element.GetRequiredProperty("x"); + var y = element.GetRequiredProperty("y"); + + if ((data ?? chunks) is null) + throw new JsonException("Tile layer does not contain data."); + + return new TileLayer + { + ID = id, + Name = name, + Class = @class, + Opacity = opacity, + Visible = visible, + TintColor = tintColor, + OffsetX = offsetX, + OffsetY = offsetY, + ParallaxX = parallaxx, + ParallaxY = parallaxy, + Properties = properties, + X = x, + Y = y, + Width = width, + Height = height, + Data = data ?? chunks + }; + } +} diff --git a/DotTiled/Serialization/Tmj/Tmj.Tileset.cs b/DotTiled/Serialization/Tmj/Tmj.Tileset.cs index 47b8e17..fd5088b 100644 --- a/DotTiled/Serialization/Tmj/Tmj.Tileset.cs +++ b/DotTiled/Serialization/Tmj/Tmj.Tileset.cs @@ -79,7 +79,7 @@ internal partial class Tmj var imageModel = new Image { - Format = ParseImageFormatFromSource(image!), + Format = Helpers.ParseImageFormatFromSource(image!), Source = image, Height = imageHeight, Width = imageWidth, @@ -112,20 +112,6 @@ internal partial class Tmj }; } - private static ImageFormat ParseImageFormatFromSource(string source) - { - var extension = Path.GetExtension(source).ToLowerInvariant(); - return extension switch - { - ".png" => ImageFormat.Png, - ".gif" => ImageFormat.Gif, - ".jpg" => ImageFormat.Jpg, - ".jpeg" => ImageFormat.Jpg, - ".bmp" => ImageFormat.Bmp, - _ => throw new JsonException($"Unsupported image format '{extension}'") - }; - } - internal static Grid ReadGrid(JsonElement element) { var orientation = element.GetOptionalPropertyParseable("orientation", s => s switch @@ -163,7 +149,7 @@ internal partial class Tmj IReadOnlyCollection customTypeDefinitions) => element.GetValueAsList(e => { - var animation = e.GetOptionalPropertyCustom>("animation", e => e.GetValueAsList(ReadFrame), null); + var animation = e.GetOptionalPropertyCustom?>("animation", e => e.GetValueAsList(ReadFrame), null); var id = e.GetRequiredProperty("id"); var image = e.GetOptionalProperty("image", null); var imageHeight = e.GetOptionalProperty("imageheight", null); @@ -180,7 +166,7 @@ internal partial class Tmj var imageModel = image != null ? new Image { - Format = ParseImageFormatFromSource(image), + Format = Helpers.ParseImageFormatFromSource(image), Source = image, Height = imageHeight ?? 0, Width = imageWidth ?? 0 @@ -213,4 +199,61 @@ internal partial class Tmj TileID = tileID }; } + + internal static Wangset ReadWangset( + JsonElement element, + IReadOnlyCollection customTypeDefinitions) + { + var @clalss = element.GetOptionalProperty("class", ""); + var colors = element.GetOptionalPropertyCustom>("colors", e => e.GetValueAsList(el => ReadWangColor(el, customTypeDefinitions)), []); + var name = element.GetRequiredProperty("name"); + var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); + var tile = element.GetOptionalProperty("tile", 0); + var type = element.GetOptionalProperty("type", ""); + var wangTiles = element.GetOptionalPropertyCustom>("wangtiles", e => e.GetValueAsList(ReadWangTile), []); + + return new Wangset + { + Class = @clalss, + WangColors = colors, + Name = name, + Properties = properties, + Tile = tile, + WangTiles = wangTiles + }; + } + + internal static WangColor ReadWangColor( + JsonElement element, + IReadOnlyCollection customTypeDefinitions) + { + var @class = element.GetOptionalProperty("class", ""); + var color = element.GetRequiredPropertyParseable("color", s => Color.Parse(s, CultureInfo.InvariantCulture)); + var name = element.GetRequiredProperty("name"); + var probability = element.GetOptionalProperty("probability", 1.0f); + var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); + var tile = element.GetOptionalProperty("tile", 0); + + return new WangColor + { + Class = @class, + Color = color, + Name = name, + Probability = probability, + Properties = properties, + Tile = tile + }; + } + + internal static WangTile ReadWangTile(JsonElement element) + { + var tileID = element.GetRequiredProperty("tileid"); + var wangID = element.GetOptionalPropertyCustom>("wangid", e => e.GetValueAsList(el => (byte)el.GetUInt32()), []); + + return new WangTile + { + TileID = tileID, + WangID = [.. wangID] + }; + } } diff --git a/DotTiled/Serialization/Tmj/Tmj.Wangset.cs b/DotTiled/Serialization/Tmj/Tmj.Wangset.cs new file mode 100644 index 0000000..cf9f024 --- /dev/null +++ b/DotTiled/Serialization/Tmj/Tmj.Wangset.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text.Json; + +namespace DotTiled; + +internal partial class Tmj +{ +} diff --git a/DotTiled/Serialization/Tmx/Tmx.Helpers.cs b/DotTiled/Serialization/Tmx/Tmx.Helpers.cs deleted file mode 100644 index bfc04f4..0000000 --- a/DotTiled/Serialization/Tmx/Tmx.Helpers.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - -namespace DotTiled; - -internal partial class Tmx -{ - private static class Helpers - { - public static void SetAtMostOnce(ref T? field, T value, string fieldName) - { - if (field is not null) - throw new InvalidOperationException($"{fieldName} already set"); - - field = value; - } - - public static void SetAtMostOnceUsingCounter(ref T? field, T value, string fieldName, ref int counter) - { - if (counter > 0) - throw new InvalidOperationException($"{fieldName} already set"); - - field = value; - counter++; - } - } -} diff --git a/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs b/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs index 4fb0ce6..2367974 100644 --- a/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs +++ b/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs @@ -128,7 +128,7 @@ internal partial class Tmx reader.ProcessChildren("object", (r, elementName) => elementName switch { - "properties" => () => Helpers.SetAtMostOnceUsingCounter(ref properties, MergeProperties(properties, ReadProperties(r, customTypeDefinitions)), "Properties", ref propertiesCounter), + "properties" => () => Helpers.SetAtMostOnceUsingCounter(ref properties, Helpers.MergeProperties(properties, ReadProperties(r, customTypeDefinitions)), "Properties", ref propertiesCounter), "ellipse" => () => Helpers.SetAtMostOnce(ref obj, ReadEllipseObject(r), "Object marker"), "point" => () => Helpers.SetAtMostOnce(ref obj, ReadPointObject(r), "Object marker"), "polygon" => () => Helpers.SetAtMostOnce(ref obj, ReadPolygonObject(r), "Object marker"), @@ -158,38 +158,6 @@ internal partial class Tmx return obj; } - internal static Dictionary MergeProperties(Dictionary? baseProperties, Dictionary overrideProperties) - { - if (baseProperties is null) - return overrideProperties ?? new Dictionary(); - - if (overrideProperties is null) - return baseProperties; - - var result = new Dictionary(baseProperties); - foreach (var (key, value) in overrideProperties) - { - if (!result.TryGetValue(key, out var baseProp)) - { - result[key] = value; - continue; - } - else - { - if (value is ClassProperty classProp) - { - ((ClassProperty)baseProp).Properties = MergeProperties(((ClassProperty)baseProp).Properties, classProp.Properties); - } - else - { - result[key] = value; - } - } - } - - return result; - } - internal static EllipseObject ReadEllipseObject(XmlReader reader) { reader.Skip(); diff --git a/DotTiled/Serialization/Tmx/Tmx.Properties.cs b/DotTiled/Serialization/Tmx/Tmx.Properties.cs index 0de4adf..6ea65e5 100644 --- a/DotTiled/Serialization/Tmx/Tmx.Properties.cs +++ b/DotTiled/Serialization/Tmx/Tmx.Properties.cs @@ -56,7 +56,7 @@ internal partial class Tmx var propsInType = CreateInstanceOfCustomClass(ccd); var props = ReadProperties(reader, customTypeDefinitions); - var mergedProps = MergeProperties(propsInType, props); + var mergedProps = Helpers.MergeProperties(propsInType, props); reader.ReadEndElement(); return new ClassProperty { Name = name, PropertyType = propertyType, Properties = mergedProps };