mirror of
https://github.com/dcronqvist/DotTiled.git
synced 2025-02-05 08:52:50 +02:00
Splitting up Tmj parsing more
This commit is contained in:
parent
cc57b172a8
commit
36f600dcdf
16 changed files with 705 additions and 716 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
123
DotTiled/Serialization/Helpers.cs
Normal file
123
DotTiled/Serialization/Helpers.cs
Normal 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++;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
|
|
45
DotTiled/Serialization/Tmj/Tmj.Group.cs
Normal file
45
DotTiled/Serialization/Tmj/Tmj.Group.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
63
DotTiled/Serialization/Tmj/Tmj.ImageLayer.cs
Normal file
63
DotTiled/Serialization/Tmj/Tmj.ImageLayer.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
289
DotTiled/Serialization/Tmj/Tmj.ObjectLayer.cs
Normal file
289
DotTiled/Serialization/Tmj/Tmj.ObjectLayer.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
27
DotTiled/Serialization/Tmj/Tmj.Template.cs
Normal file
27
DotTiled/Serialization/Tmj/Tmj.Template.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
75
DotTiled/Serialization/Tmj/Tmj.TileLayer.cs
Normal file
75
DotTiled/Serialization/Tmj/Tmj.TileLayer.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
13
DotTiled/Serialization/Tmj/Tmj.Wangset.cs
Normal file
13
DotTiled/Serialization/Tmj/Tmj.Wangset.cs
Normal 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
|
||||
{
|
||||
}
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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 };
|
||||
|
|
Loading…
Add table
Reference in a new issue