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
|
// Array of uint
|
||||||
var data = element.GetValueAsList<uint>(e => e.GetValueAs<uint>()).ToArray();
|
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 };
|
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags, Chunks = null };
|
||||||
}
|
}
|
||||||
else if (encoding == DataEncoding.Base64)
|
else if (encoding == DataEncoding.Base64)
|
||||||
|
@ -58,21 +58,21 @@ internal partial class Tmj
|
||||||
|
|
||||||
if (compression == null)
|
if (compression == null)
|
||||||
{
|
{
|
||||||
var data = Helpers.Data.ReadBytesAsInt32Array(base64Data);
|
var data = Helpers.ReadBytesAsInt32Array(base64Data);
|
||||||
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 };
|
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags, Chunks = null };
|
||||||
}
|
}
|
||||||
|
|
||||||
using var stream = new MemoryStream(base64Data);
|
using var stream = new MemoryStream(base64Data);
|
||||||
var decompressed = compression switch
|
var decompressed = compression switch
|
||||||
{
|
{
|
||||||
DataCompression.GZip => Helpers.Data.DecompressGZip(stream),
|
DataCompression.GZip => Helpers.DecompressGZip(stream),
|
||||||
DataCompression.ZLib => Helpers.Data.DecompressZLib(stream),
|
DataCompression.ZLib => Helpers.DecompressZLib(stream),
|
||||||
_ => throw new JsonException($"Unsupported compression '{compression}'.")
|
_ => 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 };
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
|
@ -22,404 +19,9 @@ internal partial class Tmj
|
||||||
{
|
{
|
||||||
"tilelayer" => ReadTileLayer(element, customTypeDefinitions),
|
"tilelayer" => ReadTileLayer(element, customTypeDefinitions),
|
||||||
"objectgroup" => ReadObjectLayer(element, externalTemplateResolver, customTypeDefinitions),
|
"objectgroup" => ReadObjectLayer(element, externalTemplateResolver, customTypeDefinitions),
|
||||||
// "imagelayer" => ReadImageLayer(element),
|
"imagelayer" => ReadImageLayer(element, customTypeDefinitions),
|
||||||
"group" => ReadGroup(element, externalTemplateResolver, customTypeDefinitions),
|
"group" => ReadGroup(element, externalTemplateResolver, customTypeDefinitions),
|
||||||
_ => throw new JsonException($"Unsupported layer type '{type}'.")
|
_ => 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 propsInType = CreateInstanceOfCustomClass(ccd);
|
||||||
var props = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>>("value", el => ReadCustomClassProperties(el, ccd, customTypeDefinitions), []);
|
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
|
return new ClassProperty
|
||||||
{
|
{
|
||||||
|
@ -105,36 +105,4 @@ internal partial class Tmj
|
||||||
{
|
{
|
||||||
return customClassDefinition.Members.ToDictionary(m => m.Name, m => m.Clone());
|
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
|
var imageModel = new Image
|
||||||
{
|
{
|
||||||
Format = ParseImageFormatFromSource(image!),
|
Format = Helpers.ParseImageFormatFromSource(image!),
|
||||||
Source = image,
|
Source = image,
|
||||||
Height = imageHeight,
|
Height = imageHeight,
|
||||||
Width = imageWidth,
|
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)
|
internal static Grid ReadGrid(JsonElement element)
|
||||||
{
|
{
|
||||||
var orientation = element.GetOptionalPropertyParseable<GridOrientation>("orientation", s => s switch
|
var orientation = element.GetOptionalPropertyParseable<GridOrientation>("orientation", s => s switch
|
||||||
|
@ -163,7 +149,7 @@ internal partial class Tmj
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions) =>
|
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions) =>
|
||||||
element.GetValueAsList<Tile>(e =>
|
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 id = e.GetRequiredProperty<uint>("id");
|
||||||
var image = e.GetOptionalProperty<string?>("image", null);
|
var image = e.GetOptionalProperty<string?>("image", null);
|
||||||
var imageHeight = e.GetOptionalProperty<uint?>("imageheight", null);
|
var imageHeight = e.GetOptionalProperty<uint?>("imageheight", null);
|
||||||
|
@ -180,7 +166,7 @@ internal partial class Tmj
|
||||||
|
|
||||||
var imageModel = image != null ? new Image
|
var imageModel = image != null ? new Image
|
||||||
{
|
{
|
||||||
Format = ParseImageFormatFromSource(image),
|
Format = Helpers.ParseImageFormatFromSource(image),
|
||||||
Source = image,
|
Source = image,
|
||||||
Height = imageHeight ?? 0,
|
Height = imageHeight ?? 0,
|
||||||
Width = imageWidth ?? 0
|
Width = imageWidth ?? 0
|
||||||
|
@ -213,4 +199,61 @@ internal partial class Tmj
|
||||||
TileID = tileID
|
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
|
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"),
|
"ellipse" => () => Helpers.SetAtMostOnce(ref obj, ReadEllipseObject(r), "Object marker"),
|
||||||
"point" => () => Helpers.SetAtMostOnce(ref obj, ReadPointObject(r), "Object marker"),
|
"point" => () => Helpers.SetAtMostOnce(ref obj, ReadPointObject(r), "Object marker"),
|
||||||
"polygon" => () => Helpers.SetAtMostOnce(ref obj, ReadPolygonObject(r), "Object marker"),
|
"polygon" => () => Helpers.SetAtMostOnce(ref obj, ReadPolygonObject(r), "Object marker"),
|
||||||
|
@ -158,38 +158,6 @@ internal partial class Tmx
|
||||||
return obj;
|
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)
|
internal static EllipseObject ReadEllipseObject(XmlReader reader)
|
||||||
{
|
{
|
||||||
reader.Skip();
|
reader.Skip();
|
||||||
|
|
|
@ -56,7 +56,7 @@ internal partial class Tmx
|
||||||
var propsInType = CreateInstanceOfCustomClass(ccd);
|
var propsInType = CreateInstanceOfCustomClass(ccd);
|
||||||
var props = ReadProperties(reader, customTypeDefinitions);
|
var props = ReadProperties(reader, customTypeDefinitions);
|
||||||
|
|
||||||
var mergedProps = MergeProperties(propsInType, props);
|
var mergedProps = Helpers.MergeProperties(propsInType, props);
|
||||||
|
|
||||||
reader.ReadEndElement();
|
reader.ReadEndElement();
|
||||||
return new ClassProperty { Name = name, PropertyType = propertyType, Properties = mergedProps };
|
return new ClassProperty { Name = name, PropertyType = propertyType, Properties = mergedProps };
|
||||||
|
|
Loading…
Add table
Reference in a new issue