Splitting up Tmj parsing more

This commit is contained in:
Daniel Cronqvist 2024-08-11 17:02:16 +02:00
parent cc57b172a8
commit 36f600dcdf
16 changed files with 705 additions and 716 deletions

View file

@ -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<uint>();
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);
}
}
}

View file

@ -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<uint>();
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<string, IProperty> MergeProperties(Dictionary<string, IProperty>? baseProperties, Dictionary<string, IProperty> overrideProperties)
{
if (baseProperties is null)
return overrideProperties ?? new Dictionary<string, IProperty>();
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<T>(ref T? field, T value, string fieldName)
{
if (field is not null)
throw new InvalidOperationException($"{fieldName} already set");
field = value;
}
internal static void SetAtMostOnceUsingCounter<T>(ref T? field, T value, string fieldName, ref int counter)
{
if (counter > 0)
throw new InvalidOperationException($"{fieldName} already set");
field = value;
counter++;
}
}

View file

@ -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<T>(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<string> processedProperties = [];
ProcessJsonObject(ref reader, properties.Select<Tmj.JsonProperty, (string, ProcessProperty)>(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();
}
}

View file

@ -49,7 +49,7 @@ internal partial class Tmj
{
// Array of uint
var data = element.GetValueAsList<uint>(e => e.GetValueAs<uint>()).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 };
}
}

View file

@ -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<string, Template> externalTemplateResolver,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
{
var id = element.GetRequiredProperty<uint>("id");
var name = element.GetRequiredProperty<string>("name");
var @class = element.GetOptionalProperty<string>("class", "");
var opacity = element.GetOptionalProperty<float>("opacity", 1.0f);
var visible = element.GetOptionalProperty<bool>("visible", true);
var tintColor = element.GetOptionalPropertyParseable<Color?>("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
var offsetX = element.GetOptionalProperty<float>("offsetx", 0.0f);
var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f);
var parallaxX = element.GetOptionalProperty<float>("parallaxx", 1.0f);
var parallaxY = element.GetOptionalProperty<float>("parallaxy", 1.0f);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null);
var layers = element.GetOptionalPropertyCustom<List<BaseLayer>>("layers", e => e.GetValueAsList<BaseLayer>(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
};
}
}

View file

@ -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<CustomTypeDefinition> customTypeDefinitions)
{
var id = element.GetRequiredProperty<uint>("id");
var name = element.GetRequiredProperty<string>("name");
var @class = element.GetOptionalProperty<string>("class", "");
var opacity = element.GetOptionalProperty<float>("opacity", 1.0f);
var visible = element.GetOptionalProperty<bool>("visible", true);
var tintColor = element.GetOptionalPropertyParseable<Color?>("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
var offsetX = element.GetOptionalProperty<float>("offsetx", 0.0f);
var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f);
var parallaxX = element.GetOptionalProperty<float>("parallaxx", 1.0f);
var parallaxY = element.GetOptionalProperty<float>("parallaxy", 1.0f);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null);
var image = element.GetRequiredProperty<string>("image");
var repeatX = element.GetRequiredProperty<bool>("repeatx");
var repeatY = element.GetRequiredProperty<bool>("repeaty");
var transparentColor = element.GetOptionalPropertyParseable<Color?>("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
var x = element.GetOptionalProperty<uint>("x", 0);
var y = element.GetOptionalProperty<uint>("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
};
}
}

View file

@ -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<CustomTypeDefinition> customTypeDefinitions)
{
var compression = element.GetOptionalPropertyParseable<DataCompression?>("compression", s => s switch
{
"zlib" => DataCompression.ZLib,
"gzip" => DataCompression.GZip,
"" => null,
_ => throw new JsonException($"Unsupported compression '{s}'.")
}, null);
var encoding = element.GetOptionalPropertyParseable<DataEncoding>("encoding", s => s switch
{
"csv" => DataEncoding.Csv,
"base64" => DataEncoding.Base64,
_ => throw new JsonException($"Unsupported encoding '{s}'.")
}, DataEncoding.Csv);
var chunks = element.GetOptionalPropertyCustom<Data?>("chunks", e => ReadDataAsChunks(e, compression, encoding), null);
var @class = element.GetOptionalProperty<string>("class", "");
var data = element.GetOptionalPropertyCustom<Data?>("data", e => ReadDataWithoutChunks(e, compression, encoding), null);
var height = element.GetRequiredProperty<uint>("height");
var id = element.GetRequiredProperty<uint>("id");
var name = element.GetRequiredProperty<string>("name");
var offsetX = element.GetOptionalProperty<float>("offsetx", 0.0f);
var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f);
var opacity = element.GetOptionalProperty<float>("opacity", 1.0f);
var parallaxx = element.GetOptionalProperty<float>("parallaxx", 1.0f);
var parallaxy = element.GetOptionalProperty<float>("parallaxy", 1.0f);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null);
var repeatX = element.GetOptionalProperty<bool>("repeatx", false);
var repeatY = element.GetOptionalProperty<bool>("repeaty", false);
var startX = element.GetOptionalProperty<int>("startx", 0);
var startY = element.GetOptionalProperty<int>("starty", 0);
var tintColor = element.GetOptionalPropertyParseable<Color?>("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
var transparentColor = element.GetOptionalPropertyParseable<Color?>("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
var visible = element.GetOptionalProperty<bool>("visible", true);
var width = element.GetRequiredProperty<uint>("width");
var x = element.GetRequiredProperty<uint>("x");
var y = element.GetRequiredProperty<uint>("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<string, Template> externalTemplateResolver,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
{
var id = element.GetRequiredProperty<uint>("id");
var name = element.GetRequiredProperty<string>("name");
var @class = element.GetOptionalProperty<string>("class", "");
var opacity = element.GetOptionalProperty<float>("opacity", 1.0f);
var visible = element.GetOptionalProperty<bool>("visible", true);
var tintColor = element.GetOptionalPropertyParseable<Color?>("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
var offsetX = element.GetOptionalProperty<float>("offsetx", 0.0f);
var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f);
var parallaxX = element.GetOptionalProperty<float>("parallaxx", 1.0f);
var parallaxY = element.GetOptionalProperty<float>("parallaxy", 1.0f);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null);
var layers = element.GetOptionalPropertyCustom<List<BaseLayer>>("layers", e => e.GetValueAsList<BaseLayer>(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<string, Template> externalTemplateResolver,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
{
var id = element.GetRequiredProperty<uint>("id");
var name = element.GetRequiredProperty<string>("name");
var @class = element.GetOptionalProperty<string>("class", "");
var opacity = element.GetOptionalProperty<float>("opacity", 1.0f);
var visible = element.GetOptionalProperty<bool>("visible", true);
var tintColor = element.GetOptionalPropertyParseable<Color?>("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
var offsetX = element.GetOptionalProperty<float>("offsetx", 0.0f);
var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f);
var parallaxX = element.GetOptionalProperty<float>("parallaxx", 1.0f);
var parallaxY = element.GetOptionalProperty<float>("parallaxy", 1.0f);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null);
var x = element.GetOptionalProperty<uint>("x", 0);
var y = element.GetOptionalProperty<uint>("y", 0);
var width = element.GetOptionalProperty<uint?>("width", null);
var height = element.GetOptionalProperty<uint?>("height", null);
var color = element.GetOptionalPropertyParseable<Color?>("color", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
var drawOrder = element.GetOptionalPropertyParseable<DrawOrder>("draworder", s => s switch
{
"topdown" => DrawOrder.TopDown,
"index" => DrawOrder.Index,
_ => throw new JsonException($"Unknown draw order '{s}'.")
}, DrawOrder.TopDown);
var objects = element.GetOptionalPropertyCustom<List<Object>>("objects", e => e.GetValueAsList<Object>(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<string, Template> externalTemplateResolver,
IReadOnlyCollection<CustomTypeDefinition> 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<Vector2>? polygonDefault = null;
List<Vector2>? polylineDefault = null;
Dictionary<string, IProperty>? propertiesDefault = null;
var template = element.GetOptionalProperty<string?>("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<bool>("ellipse", ellipseDefault);
var gid = element.GetOptionalProperty<uint?>("gid", gidDefault);
var height = element.GetOptionalProperty<float>("height", heightDefault);
var id = element.GetOptionalProperty<uint?>("id", idDefault);
var name = element.GetOptionalProperty<string>("name", nameDefault);
var point = element.GetOptionalProperty<bool>("point", pointDefault);
var polygon = element.GetOptionalPropertyCustom<List<Vector2>?>("polygon", e => ReadPoints(e), polygonDefault);
var polyline = element.GetOptionalPropertyCustom<List<Vector2>?>("polyline", e => ReadPoints(e), polylineDefault);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), propertiesDefault);
var rotation = element.GetOptionalProperty<float>("rotation", rotationDefault);
var text = element.GetOptionalPropertyCustom<TextObject?>("text", ReadText, null);
var type = element.GetOptionalProperty<string>("type", typeDefault);
var visible = element.GetOptionalProperty<bool>("visible", visibleDefault);
var width = element.GetOptionalProperty<float>("width", widthDefault);
var x = element.GetOptionalProperty<float>("x", xDefault);
var y = element.GetOptionalProperty<float>("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<Vector2> ReadPoints(JsonElement element) =>
element.GetValueAsList<Vector2>(e =>
{
var x = e.GetRequiredProperty<float>("x");
var y = e.GetRequiredProperty<float>("y");
return new Vector2(x, y);
});
internal static Template ReadTemplate(
JsonElement element,
Func<string, Tileset> externalTilesetResolver,
Func<string, Template> externalTemplateResolver,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
{
var type = element.GetRequiredProperty<string>("type");
var tileset = element.GetOptionalPropertyCustom<Tileset?>("tileset", el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions), null);
var @object = element.GetRequiredPropertyCustom<Object>("object", el => ReadObject(el, externalTemplateResolver, customTypeDefinitions));
return new Template
{
Tileset = tileset,
Object = @object
};
}
internal static TextObject ReadText(JsonElement element)
{
var bold = element.GetOptionalProperty<bool>("bold", false);
var color = element.GetOptionalPropertyParseable<Color>("color", s => Color.Parse(s, CultureInfo.InvariantCulture), Color.Parse("#00000000", CultureInfo.InvariantCulture));
var fontfamily = element.GetOptionalProperty<string>("fontfamily", "sans-serif");
var halign = element.GetOptionalPropertyParseable<TextHorizontalAlignment>("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<bool>("italic", false);
var kerning = element.GetOptionalProperty<bool>("kerning", true);
var pixelsize = element.GetOptionalProperty<int>("pixelsize", 16);
var strikeout = element.GetOptionalProperty<bool>("strikeout", false);
var text = element.GetRequiredProperty<string>("text");
var underline = element.GetOptionalProperty<bool>("underline", false);
var valign = element.GetOptionalPropertyParseable<TextVerticalAlignment>("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<bool>("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
};
}
}

View file

@ -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<string, Template> externalTemplateResolver,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
{
var id = element.GetRequiredProperty<uint>("id");
var name = element.GetRequiredProperty<string>("name");
var @class = element.GetOptionalProperty<string>("class", "");
var opacity = element.GetOptionalProperty<float>("opacity", 1.0f);
var visible = element.GetOptionalProperty<bool>("visible", true);
var tintColor = element.GetOptionalPropertyParseable<Color?>("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
var offsetX = element.GetOptionalProperty<float>("offsetx", 0.0f);
var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f);
var parallaxX = element.GetOptionalProperty<float>("parallaxx", 1.0f);
var parallaxY = element.GetOptionalProperty<float>("parallaxy", 1.0f);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null);
var x = element.GetOptionalProperty<uint>("x", 0);
var y = element.GetOptionalProperty<uint>("y", 0);
var width = element.GetOptionalProperty<uint?>("width", null);
var height = element.GetOptionalProperty<uint?>("height", null);
var color = element.GetOptionalPropertyParseable<Color?>("color", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
var drawOrder = element.GetOptionalPropertyParseable<DrawOrder>("draworder", s => s switch
{
"topdown" => DrawOrder.TopDown,
"index" => DrawOrder.Index,
_ => throw new JsonException($"Unknown draw order '{s}'.")
}, DrawOrder.TopDown);
var objects = element.GetOptionalPropertyCustom<List<Object>>("objects", e => e.GetValueAsList<Object>(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<string, Template> externalTemplateResolver,
IReadOnlyCollection<CustomTypeDefinition> 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<Vector2>? polygonDefault = null;
List<Vector2>? polylineDefault = null;
Dictionary<string, IProperty>? propertiesDefault = null;
var template = element.GetOptionalProperty<string?>("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<bool>("ellipse", ellipseDefault);
var gid = element.GetOptionalProperty<uint?>("gid", gidDefault);
var height = element.GetOptionalProperty<float>("height", heightDefault);
var id = element.GetOptionalProperty<uint?>("id", idDefault);
var name = element.GetOptionalProperty<string>("name", nameDefault);
var point = element.GetOptionalProperty<bool>("point", pointDefault);
var polygon = element.GetOptionalPropertyCustom<List<Vector2>?>("polygon", e => ReadPoints(e), polygonDefault);
var polyline = element.GetOptionalPropertyCustom<List<Vector2>?>("polyline", e => ReadPoints(e), polylineDefault);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), propertiesDefault);
var rotation = element.GetOptionalProperty<float>("rotation", rotationDefault);
var text = element.GetOptionalPropertyCustom<TextObject?>("text", ReadText, null);
var type = element.GetOptionalProperty<string>("type", typeDefault);
var visible = element.GetOptionalProperty<bool>("visible", visibleDefault);
var width = element.GetOptionalProperty<float>("width", widthDefault);
var x = element.GetOptionalProperty<float>("x", xDefault);
var y = element.GetOptionalProperty<float>("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<Vector2> ReadPoints(JsonElement element) =>
element.GetValueAsList<Vector2>(e =>
{
var x = e.GetRequiredProperty<float>("x");
var y = e.GetRequiredProperty<float>("y");
return new Vector2(x, y);
});
internal static TextObject ReadText(JsonElement element)
{
var bold = element.GetOptionalProperty<bool>("bold", false);
var color = element.GetOptionalPropertyParseable<Color>("color", s => Color.Parse(s, CultureInfo.InvariantCulture), Color.Parse("#00000000", CultureInfo.InvariantCulture));
var fontfamily = element.GetOptionalProperty<string>("fontfamily", "sans-serif");
var halign = element.GetOptionalPropertyParseable<TextHorizontalAlignment>("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<bool>("italic", false);
var kerning = element.GetOptionalProperty<bool>("kerning", true);
var pixelsize = element.GetOptionalProperty<int>("pixelsize", 16);
var strikeout = element.GetOptionalProperty<bool>("strikeout", false);
var text = element.GetRequiredProperty<string>("text");
var underline = element.GetOptionalProperty<bool>("underline", false);
var valign = element.GetOptionalPropertyParseable<TextVerticalAlignment>("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<bool>("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
};
}
}

View file

@ -57,7 +57,7 @@ internal partial class Tmj
var propsInType = CreateInstanceOfCustomClass(ccd);
var props = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>>("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<string, IProperty> MergeProperties(Dictionary<string, IProperty>? baseProperties, Dictionary<string, IProperty> overrideProperties)
{
if (baseProperties is null)
return overrideProperties ?? new Dictionary<string, IProperty>();
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;
}
}

View file

@ -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<string, Tileset> externalTilesetResolver,
Func<string, Template> externalTemplateResolver,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
{
var type = element.GetRequiredProperty<string>("type");
var tileset = element.GetOptionalPropertyCustom<Tileset?>("tileset", el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions), null);
var @object = element.GetRequiredPropertyCustom<Object>("object", el => ReadObject(el, externalTemplateResolver, customTypeDefinitions));
return new Template
{
Tileset = tileset,
Object = @object
};
}
}

View file

@ -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<CustomTypeDefinition> customTypeDefinitions)
{
var compression = element.GetOptionalPropertyParseable<DataCompression?>("compression", s => s switch
{
"zlib" => DataCompression.ZLib,
"gzip" => DataCompression.GZip,
"" => null,
_ => throw new JsonException($"Unsupported compression '{s}'.")
}, null);
var encoding = element.GetOptionalPropertyParseable<DataEncoding>("encoding", s => s switch
{
"csv" => DataEncoding.Csv,
"base64" => DataEncoding.Base64,
_ => throw new JsonException($"Unsupported encoding '{s}'.")
}, DataEncoding.Csv);
var chunks = element.GetOptionalPropertyCustom<Data?>("chunks", e => ReadDataAsChunks(e, compression, encoding), null);
var @class = element.GetOptionalProperty<string>("class", "");
var data = element.GetOptionalPropertyCustom<Data?>("data", e => ReadDataWithoutChunks(e, compression, encoding), null);
var height = element.GetRequiredProperty<uint>("height");
var id = element.GetRequiredProperty<uint>("id");
var name = element.GetRequiredProperty<string>("name");
var offsetX = element.GetOptionalProperty<float>("offsetx", 0.0f);
var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f);
var opacity = element.GetOptionalProperty<float>("opacity", 1.0f);
var parallaxx = element.GetOptionalProperty<float>("parallaxx", 1.0f);
var parallaxy = element.GetOptionalProperty<float>("parallaxy", 1.0f);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null);
var repeatX = element.GetOptionalProperty<bool>("repeatx", false);
var repeatY = element.GetOptionalProperty<bool>("repeaty", false);
var startX = element.GetOptionalProperty<int>("startx", 0);
var startY = element.GetOptionalProperty<int>("starty", 0);
var tintColor = element.GetOptionalPropertyParseable<Color?>("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
var transparentColor = element.GetOptionalPropertyParseable<Color?>("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
var visible = element.GetOptionalProperty<bool>("visible", true);
var width = element.GetRequiredProperty<uint>("width");
var x = element.GetRequiredProperty<uint>("x");
var y = element.GetRequiredProperty<uint>("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
};
}
}

View file

@ -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<GridOrientation>("orientation", s => s switch
@ -163,7 +149,7 @@ internal partial class Tmj
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions) =>
element.GetValueAsList<Tile>(e =>
{
var animation = e.GetOptionalPropertyCustom<List<Frame>>("animation", e => e.GetValueAsList<Frame>(ReadFrame), null);
var animation = e.GetOptionalPropertyCustom<List<Frame>?>("animation", e => e.GetValueAsList<Frame>(ReadFrame), null);
var id = e.GetRequiredProperty<uint>("id");
var image = e.GetOptionalProperty<string?>("image", null);
var imageHeight = e.GetOptionalProperty<uint?>("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<CustomTypeDefinition> customTypeDefinitions)
{
var @clalss = element.GetOptionalProperty<string>("class", "");
var colors = element.GetOptionalPropertyCustom<List<WangColor>>("colors", e => e.GetValueAsList<WangColor>(el => ReadWangColor(el, customTypeDefinitions)), []);
var name = element.GetRequiredProperty<string>("name");
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null);
var tile = element.GetOptionalProperty<uint>("tile", 0);
var type = element.GetOptionalProperty<string>("type", "");
var wangTiles = element.GetOptionalPropertyCustom<List<WangTile>>("wangtiles", e => e.GetValueAsList<WangTile>(ReadWangTile), []);
return new Wangset
{
Class = @clalss,
WangColors = colors,
Name = name,
Properties = properties,
Tile = tile,
WangTiles = wangTiles
};
}
internal static WangColor ReadWangColor(
JsonElement element,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
{
var @class = element.GetOptionalProperty<string>("class", "");
var color = element.GetRequiredPropertyParseable<Color>("color", s => Color.Parse(s, CultureInfo.InvariantCulture));
var name = element.GetRequiredProperty<string>("name");
var probability = element.GetOptionalProperty<float>("probability", 1.0f);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null);
var tile = element.GetOptionalProperty<uint>("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<uint>("tileid");
var wangID = element.GetOptionalPropertyCustom<List<byte>>("wangid", e => e.GetValueAsList<byte>(el => (byte)el.GetUInt32()), []);
return new WangTile
{
TileID = tileID,
WangID = [.. wangID]
};
}
}

View file

@ -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
{
}

View file

@ -1,26 +0,0 @@
using System;
namespace DotTiled;
internal partial class Tmx
{
private static class Helpers
{
public static void SetAtMostOnce<T>(ref T? field, T value, string fieldName)
{
if (field is not null)
throw new InvalidOperationException($"{fieldName} already set");
field = value;
}
public static void SetAtMostOnceUsingCounter<T>(ref T? field, T value, string fieldName, ref int counter)
{
if (counter > 0)
throw new InvalidOperationException($"{fieldName} already set");
field = value;
counter++;
}
}
}

View file

@ -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<string, IProperty> MergeProperties(Dictionary<string, IProperty>? baseProperties, Dictionary<string, IProperty> overrideProperties)
{
if (baseProperties is null)
return overrideProperties ?? new Dictionary<string, IProperty>();
if (overrideProperties is null)
return baseProperties;
var result = new Dictionary<string, IProperty>(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();

View file

@ -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 };