mirror of
https://github.com/dcronqvist/DotTiled.git
synced 2025-05-08 21:56:03 +03:00
Tmj reader is now base class and new properties docs
This commit is contained in:
parent
11f1ef783e
commit
ab8173bb06
29 changed files with 702 additions and 433 deletions
|
@ -47,7 +47,7 @@ namespace DotTiled.Benchmark
|
|||
[Benchmark(Baseline = true, Description = "DotTiled")]
|
||||
public DotTiled.Model.Map LoadWithDotTiledFromInMemoryTmjString()
|
||||
{
|
||||
using var mapReader = new DotTiled.Serialization.Tmj.TmjMapReader(_tmjContents, _ => throw new NotSupportedException(), _ => throw new NotSupportedException(), []);
|
||||
using var mapReader = new DotTiled.Serialization.Tmj.TmjMapReader(_tmjContents, _ => throw new NotSupportedException(), _ => throw new NotSupportedException(), _ => throw new NotSupportedException());
|
||||
return mapReader.ReadMap();
|
||||
}
|
||||
|
||||
|
|
|
@ -41,5 +41,6 @@ public static partial class TestData
|
|||
["Serialization/TestData/Map/map_external_tileset_multi/map-external-tileset-multi", (string f) => MapExternalTilesetMulti(f), Array.Empty<ICustomTypeDefinition>()],
|
||||
["Serialization/TestData/Map/map_external_tileset_wangset/map-external-tileset-wangset", (string f) => MapExternalTilesetWangset(f), Array.Empty<ICustomTypeDefinition>()],
|
||||
["Serialization/TestData/Map/map_with_many_layers/map-with-many-layers", (string f) => MapWithManyLayers(f), Array.Empty<ICustomTypeDefinition>()],
|
||||
["Serialization/TestData/Map/map_with_deep_props/map-with-deep-props", (string f) => MapWithDeepProps(), MapWithDeepPropsCustomTypeDefinitions()],
|
||||
];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
using System.Globalization;
|
||||
using DotTiled.Model;
|
||||
|
||||
namespace DotTiled.Tests;
|
||||
|
||||
public partial class TestData
|
||||
{
|
||||
public static Map MapWithDeepProps() => new Map
|
||||
{
|
||||
Class = "",
|
||||
Orientation = MapOrientation.Orthogonal,
|
||||
Width = 5,
|
||||
Height = 5,
|
||||
TileWidth = 32,
|
||||
TileHeight = 32,
|
||||
Infinite = false,
|
||||
HexSideLength = null,
|
||||
StaggerAxis = null,
|
||||
StaggerIndex = null,
|
||||
ParallaxOriginX = 0,
|
||||
ParallaxOriginY = 0,
|
||||
RenderOrder = RenderOrder.RightDown,
|
||||
CompressionLevel = -1,
|
||||
BackgroundColor = Color.Parse("#00000000", CultureInfo.InvariantCulture),
|
||||
Version = "1.10",
|
||||
TiledVersion = "1.11.0",
|
||||
NextLayerID = 2,
|
||||
NextObjectID = 1,
|
||||
Layers = [
|
||||
new TileLayer
|
||||
{
|
||||
ID = 1,
|
||||
Name = "Tile Layer 1",
|
||||
Width = 5,
|
||||
Height = 5,
|
||||
Data = new Data
|
||||
{
|
||||
Encoding = DataEncoding.Csv,
|
||||
Chunks = null,
|
||||
Compression = null,
|
||||
GlobalTileIDs = [
|
||||
0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0
|
||||
],
|
||||
FlippingFlags = [
|
||||
FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None,
|
||||
FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None,
|
||||
FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None,
|
||||
FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None,
|
||||
FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
Properties = [
|
||||
new ClassProperty
|
||||
{
|
||||
Name = "customouterclassprop",
|
||||
PropertyType = "CustomOuterClass",
|
||||
Value = [
|
||||
new ClassProperty
|
||||
{
|
||||
Name = "customclasspropinclass",
|
||||
PropertyType = "CustomClass",
|
||||
Value = [
|
||||
new BoolProperty { Name = "boolinclass", Value = false },
|
||||
new ColorProperty { Name = "colorinclass", Value = Color.Parse("#000000ff", CultureInfo.InvariantCulture) },
|
||||
new FileProperty { Name = "fileinclass", Value = "" },
|
||||
new FloatProperty { Name = "floatinclass", Value = 0f },
|
||||
new IntProperty { Name = "intinclass", Value = 0 },
|
||||
new ObjectProperty { Name = "objectinclass", Value = 0 },
|
||||
new StringProperty { Name = "stringinclass", Value = "" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
new ClassProperty
|
||||
{
|
||||
Name = "customouterclasspropset",
|
||||
PropertyType = "CustomOuterClass",
|
||||
Value = [
|
||||
new ClassProperty
|
||||
{
|
||||
Name = "customclasspropinclass",
|
||||
PropertyType = "CustomClass",
|
||||
Value = [
|
||||
new BoolProperty { Name = "boolinclass", Value = true },
|
||||
new ColorProperty { Name = "colorinclass", Value = Color.Parse("#000000ff", CultureInfo.InvariantCulture) },
|
||||
new FileProperty { Name = "fileinclass", Value = "" },
|
||||
new FloatProperty { Name = "floatinclass", Value = 13.37f },
|
||||
new IntProperty { Name = "intinclass", Value = 0 },
|
||||
new ObjectProperty { Name = "objectinclass", Value = 0 },
|
||||
new StringProperty { Name = "stringinclass", Value = "" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
public static IReadOnlyCollection<ICustomTypeDefinition> MapWithDeepPropsCustomTypeDefinitions() => [
|
||||
new CustomClassDefinition
|
||||
{
|
||||
Name = "CustomClass",
|
||||
UseAs = CustomClassUseAs.Property,
|
||||
Members = [
|
||||
new BoolProperty
|
||||
{
|
||||
Name = "boolinclass",
|
||||
Value = false
|
||||
},
|
||||
new ColorProperty
|
||||
{
|
||||
Name = "colorinclass",
|
||||
Value = Color.Parse("#000000ff", CultureInfo.InvariantCulture)
|
||||
},
|
||||
new FileProperty
|
||||
{
|
||||
Name = "fileinclass",
|
||||
Value = ""
|
||||
},
|
||||
new FloatProperty
|
||||
{
|
||||
Name = "floatinclass",
|
||||
Value = 0f
|
||||
},
|
||||
new IntProperty
|
||||
{
|
||||
Name = "intinclass",
|
||||
Value = 0
|
||||
},
|
||||
new ObjectProperty
|
||||
{
|
||||
Name = "objectinclass",
|
||||
Value = 0
|
||||
},
|
||||
new StringProperty
|
||||
{
|
||||
Name = "stringinclass",
|
||||
Value = ""
|
||||
}
|
||||
]
|
||||
},
|
||||
new CustomClassDefinition
|
||||
{
|
||||
Name = "CustomOuterClass",
|
||||
UseAs = CustomClassUseAs.Property,
|
||||
Members = [
|
||||
new ClassProperty
|
||||
{
|
||||
Name = "customclasspropinclass",
|
||||
PropertyType = "CustomClass",
|
||||
Value = [] // So no overrides of defaults in CustomClass
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
{ "compressionlevel":-1,
|
||||
"height":5,
|
||||
"infinite":false,
|
||||
"layers":[
|
||||
{
|
||||
"data":[0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0],
|
||||
"height":5,
|
||||
"id":1,
|
||||
"name":"Tile Layer 1",
|
||||
"opacity":1,
|
||||
"type":"tilelayer",
|
||||
"visible":true,
|
||||
"width":5,
|
||||
"x":0,
|
||||
"y":0
|
||||
}],
|
||||
"nextlayerid":2,
|
||||
"nextobjectid":1,
|
||||
"orientation":"orthogonal",
|
||||
"properties":[
|
||||
{
|
||||
"name":"customouterclassprop",
|
||||
"propertytype":"CustomOuterClass",
|
||||
"type":"class",
|
||||
"value":
|
||||
{
|
||||
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"customouterclasspropset",
|
||||
"propertytype":"CustomOuterClass",
|
||||
"type":"class",
|
||||
"value":
|
||||
{
|
||||
"customclasspropinclass":
|
||||
{
|
||||
"boolinclass":true,
|
||||
"floatinclass":13.37
|
||||
}
|
||||
}
|
||||
}],
|
||||
"renderorder":"right-down",
|
||||
"tiledversion":"1.11.0",
|
||||
"tileheight":32,
|
||||
"tilesets":[],
|
||||
"tilewidth":32,
|
||||
"type":"map",
|
||||
"version":"1.10",
|
||||
"width":5
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="5" height="5" tilewidth="32" tileheight="32" infinite="0" nextlayerid="2" nextobjectid="1">
|
||||
<properties>
|
||||
<property name="customouterclassprop" type="class" propertytype="CustomOuterClass"/>
|
||||
<property name="customouterclasspropset" type="class" propertytype="CustomOuterClass">
|
||||
<properties>
|
||||
<property name="customclasspropinclass" type="class" propertytype="CustomClass">
|
||||
<properties>
|
||||
<property name="boolinclass" type="bool" value="true"/>
|
||||
<property name="floatinclass" type="float" value="13.37"/>
|
||||
</properties>
|
||||
</property>
|
||||
</properties>
|
||||
</property>
|
||||
</properties>
|
||||
<layer id="1" name="Tile Layer 1" width="5" height="5">
|
||||
<data encoding="csv">
|
||||
0,0,0,0,0,
|
||||
0,0,0,0,0,
|
||||
0,0,0,0,0,
|
||||
0,0,0,0,0,
|
||||
0,0,0,0,0
|
||||
</data>
|
||||
</layer>
|
||||
</map>
|
|
@ -20,16 +20,20 @@ public partial class TmjMapReaderTests
|
|||
Template ResolveTemplate(string source)
|
||||
{
|
||||
var templateJson = TestData.GetRawStringFor($"{fileDir}/{source}");
|
||||
using var templateReader = new TjTemplateReader(templateJson, ResolveTileset, ResolveTemplate, customTypeDefinitions);
|
||||
using var templateReader = new TjTemplateReader(templateJson, ResolveTileset, ResolveTemplate, ResolveCustomType);
|
||||
return templateReader.ReadTemplate();
|
||||
}
|
||||
Tileset ResolveTileset(string source)
|
||||
{
|
||||
var tilesetJson = TestData.GetRawStringFor($"{fileDir}/{source}");
|
||||
using var tilesetReader = new TsjTilesetReader(tilesetJson, ResolveTemplate, customTypeDefinitions);
|
||||
using var tilesetReader = new TsjTilesetReader(tilesetJson, ResolveTileset, ResolveTemplate, ResolveCustomType);
|
||||
return tilesetReader.ReadTileset();
|
||||
}
|
||||
using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate, customTypeDefinitions);
|
||||
ICustomTypeDefinition ResolveCustomType(string name)
|
||||
{
|
||||
return customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name)!;
|
||||
}
|
||||
using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate, ResolveCustomType);
|
||||
|
||||
// Act
|
||||
var map = mapReader.ReadMap();
|
||||
|
|
|
@ -73,8 +73,25 @@ internal static partial class Helpers
|
|||
};
|
||||
}
|
||||
|
||||
internal static List<IProperty> CreateInstanceOfCustomClass(CustomClassDefinition customClassDefinition) =>
|
||||
customClassDefinition.Members.Select(x => x.Clone()).ToList();
|
||||
internal static List<IProperty> CreateInstanceOfCustomClass(
|
||||
CustomClassDefinition customClassDefinition,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver)
|
||||
{
|
||||
return customClassDefinition.Members.Select(x =>
|
||||
{
|
||||
if (x is ClassProperty cp)
|
||||
{
|
||||
return new ClassProperty
|
||||
{
|
||||
Name = cp.Name,
|
||||
PropertyType = cp.PropertyType,
|
||||
Value = CreateInstanceOfCustomClass((CustomClassDefinition)customTypeResolver(cp.PropertyType), customTypeResolver)
|
||||
};
|
||||
}
|
||||
|
||||
return x.Clone();
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
internal static IList<IProperty> MergeProperties(IList<IProperty>? baseProperties, IList<IProperty>? overrideProperties)
|
||||
{
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DotTiled.Model;
|
||||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
@ -7,73 +6,24 @@ namespace DotTiled.Serialization.Tmj;
|
|||
/// <summary>
|
||||
/// A template reader for reading Tiled JSON templates.
|
||||
/// </summary>
|
||||
public class TjTemplateReader : ITemplateReader
|
||||
public class TjTemplateReader : TmjReaderBase, ITemplateReader
|
||||
{
|
||||
// External resolvers
|
||||
private readonly Func<string, Tileset> _externalTilesetResolver;
|
||||
private readonly Func<string, Template> _externalTemplateResolver;
|
||||
|
||||
private readonly string _jsonString;
|
||||
private bool disposedValue;
|
||||
|
||||
private readonly IReadOnlyCollection<ICustomTypeDefinition> _customTypeDefinitions;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="TjTemplateReader"/>.
|
||||
/// </summary>
|
||||
/// <param name="jsonString">A string containing a Tiled template in the Tiled JSON format.</param>
|
||||
/// <param name="jsonString">A string containing a Tiled map in the Tiled JSON format.</param>
|
||||
/// <param name="externalTilesetResolver">A function that resolves external tilesets given their source.</param>
|
||||
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
|
||||
/// <param name="customTypeDefinitions">A collection of custom type definitions that can be used to resolve custom types when encountering <see cref="ClassProperty"/>.</param>
|
||||
/// <param name="customTypeResolver">A function that resolves custom types given their name.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
|
||||
public TjTemplateReader(
|
||||
string jsonString,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
||||
{
|
||||
_jsonString = jsonString ?? throw new ArgumentNullException(nameof(jsonString));
|
||||
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
||||
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
||||
_customTypeDefinitions = customTypeDefinitions ?? throw new ArgumentNullException(nameof(customTypeDefinitions));
|
||||
}
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||
jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Template ReadTemplate()
|
||||
{
|
||||
var jsonDoc = System.Text.Json.JsonDocument.Parse(_jsonString);
|
||||
var rootElement = jsonDoc.RootElement;
|
||||
return Tmj.ReadTemplate(rootElement, _externalTilesetResolver, _externalTemplateResolver, _customTypeDefinitions);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// TODO: dispose managed state (managed objects)
|
||||
}
|
||||
|
||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
||||
// TODO: set large fields to null
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||
// ~TjTemplateReader()
|
||||
// {
|
||||
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
// Dispose(disposing: false);
|
||||
// }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
public Template ReadTemplate() => ReadTemplate(RootElement);
|
||||
}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using DotTiled.Model;
|
||||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
||||
internal partial class Tmj
|
||||
{
|
||||
internal static BaseLayer ReadLayer(
|
||||
JsonElement element,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
||||
{
|
||||
var type = element.GetRequiredProperty<string>("type");
|
||||
|
||||
return type switch
|
||||
{
|
||||
"tilelayer" => ReadTileLayer(element, customTypeDefinitions),
|
||||
"objectgroup" => ReadObjectLayer(element, externalTemplateResolver, customTypeDefinitions),
|
||||
"imagelayer" => ReadImageLayer(element, customTypeDefinitions),
|
||||
"group" => ReadGroup(element, externalTemplateResolver, customTypeDefinitions),
|
||||
_ => throw new JsonException($"Unsupported layer type '{type}'.")
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using DotTiled.Model;
|
||||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
||||
internal partial class Tmj
|
||||
{
|
||||
internal static List<IProperty> ReadProperties(
|
||||
JsonElement element,
|
||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions) =>
|
||||
element.GetValueAsList<IProperty>(e =>
|
||||
{
|
||||
var name = e.GetRequiredProperty<string>("name");
|
||||
var type = e.GetOptionalPropertyParseable<PropertyType>("type", s => s switch
|
||||
{
|
||||
"string" => PropertyType.String,
|
||||
"int" => PropertyType.Int,
|
||||
"float" => PropertyType.Float,
|
||||
"bool" => PropertyType.Bool,
|
||||
"color" => PropertyType.Color,
|
||||
"file" => PropertyType.File,
|
||||
"object" => PropertyType.Object,
|
||||
"class" => PropertyType.Class,
|
||||
_ => throw new JsonException("Invalid property type")
|
||||
}, PropertyType.String);
|
||||
|
||||
IProperty property = type switch
|
||||
{
|
||||
PropertyType.String => new StringProperty { Name = name, Value = e.GetRequiredProperty<string>("value") },
|
||||
PropertyType.Int => new IntProperty { Name = name, Value = e.GetRequiredProperty<int>("value") },
|
||||
PropertyType.Float => new FloatProperty { Name = name, Value = e.GetRequiredProperty<float>("value") },
|
||||
PropertyType.Bool => new BoolProperty { Name = name, Value = e.GetRequiredProperty<bool>("value") },
|
||||
PropertyType.Color => new ColorProperty { Name = name, Value = e.GetRequiredPropertyParseable<Color>("value") },
|
||||
PropertyType.File => new FileProperty { Name = name, Value = e.GetRequiredProperty<string>("value") },
|
||||
PropertyType.Object => new ObjectProperty { Name = name, Value = e.GetRequiredProperty<uint>("value") },
|
||||
PropertyType.Class => ReadClassProperty(e, customTypeDefinitions),
|
||||
_ => throw new JsonException("Invalid property type")
|
||||
};
|
||||
|
||||
return property!;
|
||||
});
|
||||
|
||||
internal static ClassProperty ReadClassProperty(
|
||||
JsonElement element,
|
||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
||||
{
|
||||
var name = element.GetRequiredProperty<string>("name");
|
||||
var propertyType = element.GetRequiredProperty<string>("propertytype");
|
||||
|
||||
var customTypeDef = customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == propertyType);
|
||||
|
||||
if (customTypeDef is CustomClassDefinition ccd)
|
||||
{
|
||||
var propsInType = Helpers.CreateInstanceOfCustomClass(ccd);
|
||||
var props = element.GetOptionalPropertyCustom<List<IProperty>>("value", el => ReadCustomClassProperties(el, ccd, customTypeDefinitions), []);
|
||||
|
||||
var mergedProps = Helpers.MergeProperties(propsInType, props);
|
||||
|
||||
return new ClassProperty
|
||||
{
|
||||
Name = name,
|
||||
PropertyType = propertyType,
|
||||
Value = props
|
||||
};
|
||||
}
|
||||
|
||||
throw new JsonException($"Unknown custom class '{propertyType}'.");
|
||||
}
|
||||
|
||||
internal static List<IProperty> ReadCustomClassProperties(
|
||||
JsonElement element,
|
||||
CustomClassDefinition customClassDefinition,
|
||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
||||
{
|
||||
List<IProperty> resultingProps = Helpers.CreateInstanceOfCustomClass(customClassDefinition);
|
||||
|
||||
foreach (var prop in customClassDefinition.Members)
|
||||
{
|
||||
if (!element.TryGetProperty(prop.Name, out var propElement))
|
||||
continue; // Property not present in element, therefore will use default value
|
||||
|
||||
IProperty property = prop.Type switch
|
||||
{
|
||||
PropertyType.String => new StringProperty { Name = prop.Name, Value = propElement.GetValueAs<string>() },
|
||||
PropertyType.Int => new IntProperty { Name = prop.Name, Value = propElement.GetValueAs<int>() },
|
||||
PropertyType.Float => new FloatProperty { Name = prop.Name, Value = propElement.GetValueAs<float>() },
|
||||
PropertyType.Bool => new BoolProperty { Name = prop.Name, Value = propElement.GetValueAs<bool>() },
|
||||
PropertyType.Color => new ColorProperty { Name = prop.Name, Value = Color.Parse(propElement.GetValueAs<string>(), CultureInfo.InvariantCulture) },
|
||||
PropertyType.File => new FileProperty { Name = prop.Name, Value = propElement.GetValueAs<string>() },
|
||||
PropertyType.Object => new ObjectProperty { Name = prop.Name, Value = propElement.GetValueAs<uint>() },
|
||||
PropertyType.Class => ReadClassProperty(propElement, customTypeDefinitions),
|
||||
_ => throw new JsonException("Invalid property type")
|
||||
};
|
||||
|
||||
Helpers.ReplacePropertyInList(resultingProps, property);
|
||||
}
|
||||
|
||||
return resultingProps;
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using DotTiled.Model;
|
||||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
||||
internal partial class Tmj
|
||||
{
|
||||
internal static Template ReadTemplate(
|
||||
JsonElement element,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
||||
{
|
||||
var type = element.GetRequiredProperty<string>("type");
|
||||
var tileset = element.GetOptionalPropertyCustom<Tileset?>("tileset", el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions), null);
|
||||
var @object = element.GetRequiredPropertyCustom<Model.Object>("object", el => ReadObject(el, externalTemplateResolver, customTypeDefinitions));
|
||||
|
||||
return new Template
|
||||
{
|
||||
Tileset = tileset,
|
||||
Object = @object
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using DotTiled.Model;
|
||||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
@ -8,73 +6,24 @@ namespace DotTiled.Serialization.Tmj;
|
|||
/// <summary>
|
||||
/// A map reader for reading Tiled JSON maps.
|
||||
/// </summary>
|
||||
public class TmjMapReader : IMapReader
|
||||
public class TmjMapReader : TmjReaderBase, IMapReader
|
||||
{
|
||||
// External resolvers
|
||||
private readonly Func<string, Tileset> _externalTilesetResolver;
|
||||
private readonly Func<string, Template> _externalTemplateResolver;
|
||||
|
||||
private readonly string _jsonString;
|
||||
private bool disposedValue;
|
||||
|
||||
private readonly IReadOnlyCollection<ICustomTypeDefinition> _customTypeDefinitions;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="TmjMapReader"/>.
|
||||
/// </summary>
|
||||
/// <param name="jsonString">A string containing a Tiled map in the Tiled JSON format.</param>
|
||||
/// <param name="externalTilesetResolver">A function that resolves external tilesets given their source.</param>
|
||||
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
|
||||
/// <param name="customTypeDefinitions">A collection of custom type definitions that can be used to resolve custom types when encountering <see cref="ClassProperty"/>.</param>
|
||||
/// <param name="customTypeResolver">A function that resolves custom types given their name.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
|
||||
public TmjMapReader(
|
||||
string jsonString,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
||||
{
|
||||
_jsonString = jsonString ?? throw new ArgumentNullException(nameof(jsonString));
|
||||
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
||||
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
||||
_customTypeDefinitions = customTypeDefinitions ?? throw new ArgumentNullException(nameof(customTypeDefinitions));
|
||||
}
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||
jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Map ReadMap()
|
||||
{
|
||||
var jsonDoc = JsonDocument.Parse(_jsonString);
|
||||
var rootElement = jsonDoc.RootElement;
|
||||
return Tmj.ReadMap(rootElement, _externalTilesetResolver, _externalTemplateResolver, _customTypeDefinitions);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// TODO: dispose managed state (managed objects)
|
||||
}
|
||||
|
||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
||||
// TODO: set large fields to null
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||
// ~TmjMapReader()
|
||||
// {
|
||||
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
// Dispose(disposing: false);
|
||||
// }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
public Map ReadMap() => ReadMap(RootElement);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ using DotTiled.Model;
|
|||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
||||
internal partial class Tmj
|
||||
public abstract partial class TmjReaderBase
|
||||
{
|
||||
internal static Data ReadDataAsChunks(JsonElement element, DataCompression? compression, DataEncoding encoding)
|
||||
{
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
|
@ -6,12 +5,9 @@ using DotTiled.Model;
|
|||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
||||
internal partial class Tmj
|
||||
public abstract partial class TmjReaderBase
|
||||
{
|
||||
internal static Group ReadGroup(
|
||||
JsonElement element,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
||||
internal Group ReadGroup(JsonElement element)
|
||||
{
|
||||
var id = element.GetRequiredProperty<uint>("id");
|
||||
var name = element.GetRequiredProperty<string>("name");
|
||||
|
@ -23,8 +19,8 @@ internal partial class Tmj
|
|||
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("properties", e => ReadProperties(e, customTypeDefinitions), []);
|
||||
var layers = element.GetOptionalPropertyCustom<List<BaseLayer>>("layers", e => e.GetValueAsList<BaseLayer>(el => ReadLayer(el, externalTemplateResolver, customTypeDefinitions)), []);
|
||||
var properties = element.GetOptionalPropertyCustom("properties", ReadProperties, []);
|
||||
var layers = element.GetOptionalPropertyCustom<List<BaseLayer>>("layers", e => e.GetValueAsList<BaseLayer>(ReadLayer), []);
|
||||
|
||||
return new Group
|
||||
{
|
|
@ -1,15 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using DotTiled.Model;
|
||||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
||||
internal partial class Tmj
|
||||
public abstract partial class TmjReaderBase
|
||||
{
|
||||
internal static ImageLayer ReadImageLayer(
|
||||
JsonElement element,
|
||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
||||
internal ImageLayer ReadImageLayer(JsonElement element)
|
||||
{
|
||||
var id = element.GetRequiredProperty<uint>("id");
|
||||
var name = element.GetRequiredProperty<string>("name");
|
||||
|
@ -21,7 +18,7 @@ internal partial class Tmj
|
|||
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("properties", e => ReadProperties(e, customTypeDefinitions), []);
|
||||
var properties = element.GetOptionalPropertyCustom("properties", ReadProperties, []);
|
||||
|
||||
var image = element.GetRequiredProperty<string>("image");
|
||||
var repeatX = element.GetOptionalProperty<bool>("repeatx", false);
|
21
src/DotTiled/Serialization/Tmj/TmjReaderBase.Layer.cs
Normal file
21
src/DotTiled/Serialization/Tmj/TmjReaderBase.Layer.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System.Text.Json;
|
||||
using DotTiled.Model;
|
||||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
||||
public abstract partial class TmjReaderBase
|
||||
{
|
||||
internal BaseLayer ReadLayer(JsonElement element)
|
||||
{
|
||||
var type = element.GetRequiredProperty<string>("type");
|
||||
|
||||
return type switch
|
||||
{
|
||||
"tilelayer" => ReadTileLayer(element),
|
||||
"objectgroup" => ReadObjectLayer(element),
|
||||
"imagelayer" => ReadImageLayer(element),
|
||||
"group" => ReadGroup(element),
|
||||
_ => throw new JsonException($"Unsupported layer type '{type}'.")
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
|
@ -6,13 +5,9 @@ using DotTiled.Model;
|
|||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
||||
internal partial class Tmj
|
||||
public abstract partial class TmjReaderBase
|
||||
{
|
||||
internal static Map ReadMap(
|
||||
JsonElement element,
|
||||
Func<string, Tileset>? externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
||||
internal Map ReadMap(JsonElement element)
|
||||
{
|
||||
var version = element.GetRequiredProperty<string>("version");
|
||||
var tiledVersion = element.GetRequiredProperty<string>("tiledversion");
|
||||
|
@ -58,10 +53,10 @@ internal partial class Tmj
|
|||
var nextObjectID = element.GetRequiredProperty<uint>("nextobjectid");
|
||||
var infinite = element.GetOptionalProperty<bool>("infinite", false);
|
||||
|
||||
var properties = element.GetOptionalPropertyCustom("properties", el => ReadProperties(el, customTypeDefinitions), []);
|
||||
var properties = element.GetOptionalPropertyCustom("properties", ReadProperties, []);
|
||||
|
||||
List<BaseLayer> layers = element.GetOptionalPropertyCustom<List<BaseLayer>>("layers", e => e.GetValueAsList<BaseLayer>(el => ReadLayer(el, externalTemplateResolver, customTypeDefinitions)), []);
|
||||
List<Tileset> tilesets = element.GetOptionalPropertyCustom<List<Tileset>>("tilesets", e => e.GetValueAsList<Tileset>(el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions)), []);
|
||||
List<BaseLayer> layers = element.GetOptionalPropertyCustom<List<BaseLayer>>("layers", e => e.GetValueAsList<BaseLayer>(el => ReadLayer(el)), []);
|
||||
List<Tileset> tilesets = element.GetOptionalPropertyCustom<List<Tileset>>("tilesets", e => e.GetValueAsList<Tileset>(el => ReadTileset(el)), []);
|
||||
|
||||
return new Map
|
||||
{
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
|
@ -7,12 +6,9 @@ using DotTiled.Model;
|
|||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
||||
internal partial class Tmj
|
||||
public abstract partial class TmjReaderBase
|
||||
{
|
||||
internal static ObjectLayer ReadObjectLayer(
|
||||
JsonElement element,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
||||
internal ObjectLayer ReadObjectLayer(JsonElement element)
|
||||
{
|
||||
var id = element.GetRequiredProperty<uint>("id");
|
||||
var name = element.GetRequiredProperty<string>("name");
|
||||
|
@ -24,7 +20,7 @@ internal partial class Tmj
|
|||
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("properties", e => ReadProperties(e, customTypeDefinitions), []);
|
||||
var properties = element.GetOptionalPropertyCustom("properties", ReadProperties, []);
|
||||
|
||||
var x = element.GetOptionalProperty<uint>("x", 0);
|
||||
var y = element.GetOptionalProperty<uint>("y", 0);
|
||||
|
@ -38,7 +34,7 @@ internal partial class Tmj
|
|||
_ => throw new JsonException($"Unknown draw order '{s}'.")
|
||||
}, DrawOrder.TopDown);
|
||||
|
||||
var objects = element.GetOptionalPropertyCustom<List<Model.Object>>("objects", e => e.GetValueAsList<Model.Object>(el => ReadObject(el, externalTemplateResolver, customTypeDefinitions)), []);
|
||||
var objects = element.GetOptionalPropertyCustom<List<Model.Object>>("objects", e => e.GetValueAsList<Model.Object>(el => ReadObject(el)), []);
|
||||
|
||||
return new ObjectLayer
|
||||
{
|
||||
|
@ -63,10 +59,7 @@ internal partial class Tmj
|
|||
};
|
||||
}
|
||||
|
||||
internal static Model.Object ReadObject(
|
||||
JsonElement element,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
||||
internal Model.Object ReadObject(JsonElement element)
|
||||
{
|
||||
uint? idDefault = null;
|
||||
string nameDefault = "";
|
||||
|
@ -87,7 +80,7 @@ internal partial class Tmj
|
|||
var template = element.GetOptionalProperty<string?>("template", null);
|
||||
if (template is not null)
|
||||
{
|
||||
var resolvedTemplate = externalTemplateResolver(template);
|
||||
var resolvedTemplate = _externalTemplateResolver(template);
|
||||
var templObj = resolvedTemplate.Object;
|
||||
|
||||
idDefault = templObj.ID;
|
||||
|
@ -114,7 +107,7 @@ internal partial class Tmj
|
|||
var point = element.GetOptionalProperty<bool>("point", pointDefault);
|
||||
var polygon = element.GetOptionalPropertyCustom<List<Vector2>?>("polygon", ReadPoints, polygonDefault);
|
||||
var polyline = element.GetOptionalPropertyCustom<List<Vector2>?>("polyline", ReadPoints, polylineDefault);
|
||||
var properties = element.GetOptionalPropertyCustom("properties", e => ReadProperties(e, customTypeDefinitions), propertiesDefault);
|
||||
var properties = element.GetOptionalPropertyCustom("properties", ReadProperties, propertiesDefault);
|
||||
var rotation = element.GetOptionalProperty<float>("rotation", rotationDefault);
|
||||
var text = element.GetOptionalPropertyCustom<TextObject?>("text", ReadText, null);
|
||||
var type = element.GetOptionalProperty<string>("type", typeDefault);
|
174
src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs
Normal file
174
src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs
Normal file
|
@ -0,0 +1,174 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using DotTiled.Model;
|
||||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
||||
public abstract partial class TmjReaderBase
|
||||
{
|
||||
internal List<IProperty> ReadProperties(JsonElement element) =>
|
||||
element.GetValueAsList<IProperty>(e =>
|
||||
{
|
||||
var name = e.GetRequiredProperty<string>("name");
|
||||
var type = e.GetOptionalPropertyParseable<PropertyType>("type", s => s switch
|
||||
{
|
||||
"string" => PropertyType.String,
|
||||
"int" => PropertyType.Int,
|
||||
"float" => PropertyType.Float,
|
||||
"bool" => PropertyType.Bool,
|
||||
"color" => PropertyType.Color,
|
||||
"file" => PropertyType.File,
|
||||
"object" => PropertyType.Object,
|
||||
"class" => PropertyType.Class,
|
||||
_ => throw new JsonException("Invalid property type")
|
||||
}, PropertyType.String);
|
||||
var propertyType = e.GetOptionalProperty<string?>("propertytype", null);
|
||||
if (propertyType is not null)
|
||||
{
|
||||
return ReadPropertyWithCustomType(e);
|
||||
}
|
||||
|
||||
IProperty property = type switch
|
||||
{
|
||||
PropertyType.String => new StringProperty { Name = name, Value = e.GetRequiredProperty<string>("value") },
|
||||
PropertyType.Int => new IntProperty { Name = name, Value = e.GetRequiredProperty<int>("value") },
|
||||
PropertyType.Float => new FloatProperty { Name = name, Value = e.GetRequiredProperty<float>("value") },
|
||||
PropertyType.Bool => new BoolProperty { Name = name, Value = e.GetRequiredProperty<bool>("value") },
|
||||
PropertyType.Color => new ColorProperty { Name = name, Value = e.GetRequiredPropertyParseable<Color>("value") },
|
||||
PropertyType.File => new FileProperty { Name = name, Value = e.GetRequiredProperty<string>("value") },
|
||||
PropertyType.Object => new ObjectProperty { Name = name, Value = e.GetRequiredProperty<uint>("value") },
|
||||
PropertyType.Class => throw new JsonException("Class property must have a property type"),
|
||||
PropertyType.Enum => throw new JsonException("Enum property must have a property type"),
|
||||
_ => throw new JsonException("Invalid property type")
|
||||
};
|
||||
|
||||
return property!;
|
||||
});
|
||||
|
||||
internal IProperty ReadPropertyWithCustomType(JsonElement element)
|
||||
{
|
||||
var isClass = element.GetOptionalProperty<string?>("type", null) == "class";
|
||||
if (isClass)
|
||||
{
|
||||
return ReadClassProperty(element);
|
||||
}
|
||||
|
||||
return ReadEnumProperty(element);
|
||||
}
|
||||
|
||||
internal ClassProperty ReadClassProperty(JsonElement element)
|
||||
{
|
||||
var name = element.GetRequiredProperty<string>("name");
|
||||
var propertyType = element.GetRequiredProperty<string>("propertytype");
|
||||
var customTypeDef = _customTypeResolver(propertyType);
|
||||
|
||||
if (customTypeDef is CustomClassDefinition ccd)
|
||||
{
|
||||
var propsInType = Helpers.CreateInstanceOfCustomClass(ccd, _customTypeResolver);
|
||||
var props = element.GetOptionalPropertyCustom<List<IProperty>>("value", e => ReadPropertiesInsideClass(e, ccd), []);
|
||||
var mergedProps = Helpers.MergeProperties(propsInType, props);
|
||||
|
||||
return new ClassProperty
|
||||
{
|
||||
Name = name,
|
||||
PropertyType = propertyType,
|
||||
Value = mergedProps
|
||||
};
|
||||
}
|
||||
|
||||
throw new JsonException($"Unknown custom class '{propertyType}'.");
|
||||
}
|
||||
|
||||
internal List<IProperty> ReadPropertiesInsideClass(
|
||||
JsonElement element,
|
||||
CustomClassDefinition customClassDefinition)
|
||||
{
|
||||
List<IProperty> resultingProps = [];
|
||||
|
||||
foreach (var prop in customClassDefinition.Members)
|
||||
{
|
||||
if (!element.TryGetProperty(prop.Name, out var propElement))
|
||||
continue;
|
||||
|
||||
IProperty property = prop.Type switch
|
||||
{
|
||||
PropertyType.String => new StringProperty { Name = prop.Name, Value = propElement.GetValueAs<string>() },
|
||||
PropertyType.Int => new IntProperty { Name = prop.Name, Value = propElement.GetValueAs<int>() },
|
||||
PropertyType.Float => new FloatProperty { Name = prop.Name, Value = propElement.GetValueAs<float>() },
|
||||
PropertyType.Bool => new BoolProperty { Name = prop.Name, Value = propElement.GetValueAs<bool>() },
|
||||
PropertyType.Color => new ColorProperty { Name = prop.Name, Value = Color.Parse(propElement.GetValueAs<string>(), CultureInfo.InvariantCulture) },
|
||||
PropertyType.File => new FileProperty { Name = prop.Name, Value = propElement.GetValueAs<string>() },
|
||||
PropertyType.Object => new ObjectProperty { Name = prop.Name, Value = propElement.GetValueAs<uint>() },
|
||||
PropertyType.Class => new ClassProperty { Name = prop.Name, PropertyType = ((ClassProperty)prop).PropertyType, Value = ReadPropertiesInsideClass(propElement, (CustomClassDefinition)_customTypeResolver(((ClassProperty)prop).PropertyType)) },
|
||||
PropertyType.Enum => ReadEnumProperty(propElement),
|
||||
_ => throw new JsonException("Invalid property type")
|
||||
};
|
||||
|
||||
resultingProps.Add(property);
|
||||
}
|
||||
|
||||
return resultingProps;
|
||||
}
|
||||
|
||||
internal EnumProperty ReadEnumProperty(JsonElement element)
|
||||
{
|
||||
var name = element.GetRequiredProperty<string>("name");
|
||||
var propertyType = element.GetRequiredProperty<string>("propertytype");
|
||||
var typeInXml = element.GetOptionalPropertyParseable<PropertyType>("type", (s) => s switch
|
||||
{
|
||||
"string" => PropertyType.String,
|
||||
"int" => PropertyType.Int,
|
||||
_ => throw new JsonException("Invalid property type")
|
||||
}, PropertyType.String);
|
||||
var customTypeDef = _customTypeResolver(propertyType);
|
||||
|
||||
if (customTypeDef is not CustomEnumDefinition ced)
|
||||
throw new JsonException($"Unknown custom enum '{propertyType}'. Enums must be defined");
|
||||
|
||||
if (ced.StorageType == CustomEnumStorageType.String)
|
||||
{
|
||||
var value = element.GetRequiredProperty<string>("value");
|
||||
if (value.Contains(',') && !ced.ValueAsFlags)
|
||||
throw new JsonException("Enum value must not contain ',' if not ValueAsFlags is set to true.");
|
||||
|
||||
if (ced.ValueAsFlags)
|
||||
{
|
||||
var values = value.Split(',').Select(v => v.Trim()).ToHashSet();
|
||||
return new EnumProperty { Name = name, PropertyType = propertyType, Value = values };
|
||||
}
|
||||
else
|
||||
{
|
||||
return new EnumProperty { Name = name, PropertyType = propertyType, Value = new HashSet<string> { value } };
|
||||
}
|
||||
}
|
||||
else if (ced.StorageType == CustomEnumStorageType.Int)
|
||||
{
|
||||
var value = element.GetRequiredProperty<int>("value");
|
||||
if (ced.ValueAsFlags)
|
||||
{
|
||||
var allValues = ced.Values;
|
||||
var enumValues = new HashSet<string>();
|
||||
for (var i = 0; i < allValues.Count; i++)
|
||||
{
|
||||
var mask = 1 << i;
|
||||
if ((value & mask) == mask)
|
||||
{
|
||||
var enumValue = allValues[i];
|
||||
_ = enumValues.Add(enumValue);
|
||||
}
|
||||
}
|
||||
return new EnumProperty { Name = name, PropertyType = propertyType, Value = enumValues };
|
||||
}
|
||||
else
|
||||
{
|
||||
var allValues = ced.Values;
|
||||
var enumValue = allValues[value];
|
||||
return new EnumProperty { Name = name, PropertyType = propertyType, Value = new HashSet<string> { enumValue } };
|
||||
}
|
||||
}
|
||||
|
||||
throw new JsonException($"Unknown custom enum '{propertyType}'. Enums must be defined");
|
||||
}
|
||||
}
|
20
src/DotTiled/Serialization/Tmj/TmjReaderBase.Template.cs
Normal file
20
src/DotTiled/Serialization/Tmj/TmjReaderBase.Template.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using System.Text.Json;
|
||||
using DotTiled.Model;
|
||||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
||||
public abstract partial class TmjReaderBase
|
||||
{
|
||||
internal Template ReadTemplate(JsonElement element)
|
||||
{
|
||||
var type = element.GetRequiredProperty<string>("type");
|
||||
var tileset = element.GetOptionalPropertyCustom<Tileset?>("tileset", ReadTileset, null);
|
||||
var @object = element.GetRequiredPropertyCustom<Model.Object>("object", ReadObject);
|
||||
|
||||
return new Template
|
||||
{
|
||||
Tileset = tileset,
|
||||
Object = @object
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,15 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using DotTiled.Model;
|
||||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
||||
internal partial class Tmj
|
||||
public abstract partial class TmjReaderBase
|
||||
{
|
||||
internal static TileLayer ReadTileLayer(
|
||||
JsonElement element,
|
||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
||||
internal TileLayer ReadTileLayer(JsonElement element)
|
||||
{
|
||||
var compression = element.GetOptionalPropertyParseable<DataCompression?>("compression", s => s switch
|
||||
{
|
||||
|
@ -35,7 +32,7 @@ internal partial class Tmj
|
|||
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("properties", e => ReadProperties(e, customTypeDefinitions), []);
|
||||
var properties = element.GetOptionalPropertyCustom("properties", ReadProperties, []);
|
||||
var repeatX = element.GetOptionalProperty<bool>("repeatx", false);
|
||||
var repeatY = element.GetOptionalProperty<bool>("repeaty", false);
|
||||
var startX = element.GetOptionalProperty<int>("startx", 0);
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
|
@ -6,13 +5,9 @@ using DotTiled.Model;
|
|||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
||||
internal partial class Tmj
|
||||
public abstract partial class TmjReaderBase
|
||||
{
|
||||
internal static Tileset ReadTileset(
|
||||
JsonElement element,
|
||||
Func<string, Tileset>? externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
||||
internal Tileset ReadTileset(JsonElement element)
|
||||
{
|
||||
var backgroundColor = element.GetOptionalPropertyParseable<Color?>("backgroundcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
|
||||
var @class = element.GetOptionalProperty<string>("class", "");
|
||||
|
@ -44,7 +39,7 @@ internal partial class Tmj
|
|||
"bottomright" => ObjectAlignment.BottomRight,
|
||||
_ => throw new JsonException($"Unknown object alignment '{s}'")
|
||||
}, ObjectAlignment.Unspecified);
|
||||
var properties = element.GetOptionalPropertyCustom("properties", el => ReadProperties(el, customTypeDefinitions), []);
|
||||
var properties = element.GetOptionalPropertyCustom("properties", ReadProperties, []);
|
||||
var source = element.GetOptionalProperty<string?>("source", null);
|
||||
var spacing = element.GetOptionalProperty<uint?>("spacing", null);
|
||||
var tileCount = element.GetOptionalProperty<uint?>("tilecount", null);
|
||||
|
@ -57,20 +52,17 @@ internal partial class Tmj
|
|||
"grid" => TileRenderSize.Grid,
|
||||
_ => throw new JsonException($"Unknown tile render size '{s}'")
|
||||
}, TileRenderSize.Tile);
|
||||
var tiles = element.GetOptionalPropertyCustom<List<Tile>>("tiles", el => ReadTiles(el, externalTemplateResolver, customTypeDefinitions), []);
|
||||
var tiles = element.GetOptionalPropertyCustom<List<Tile>>("tiles", ReadTiles, []);
|
||||
var tileWidth = element.GetOptionalProperty<uint?>("tilewidth", null);
|
||||
var transparentColor = element.GetOptionalPropertyParseable<Color?>("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
|
||||
var type = element.GetOptionalProperty<string?>("type", null);
|
||||
var version = element.GetOptionalProperty<string?>("version", null);
|
||||
var transformations = element.GetOptionalPropertyCustom<Transformations?>("transformations", ReadTransformations, null);
|
||||
var wangsets = element.GetOptionalPropertyCustom<List<Wangset>?>("wangsets", el => el.GetValueAsList<Wangset>(e => ReadWangset(e, customTypeDefinitions)), null);
|
||||
var wangsets = element.GetOptionalPropertyCustom<List<Wangset>?>("wangsets", el => el.GetValueAsList<Wangset>(e => ReadWangset(e)), null);
|
||||
|
||||
if (source is not null)
|
||||
{
|
||||
if (externalTilesetResolver is null)
|
||||
throw new JsonException("External tileset resolver is required to resolve external tilesets.");
|
||||
|
||||
var resolvedTileset = externalTilesetResolver(source);
|
||||
var resolvedTileset = _externalTilesetResolver(source);
|
||||
resolvedTileset.FirstGID = firstGID;
|
||||
resolvedTileset.Source = source;
|
||||
return resolvedTileset;
|
||||
|
@ -159,10 +151,7 @@ internal partial class Tmj
|
|||
};
|
||||
}
|
||||
|
||||
internal static List<Tile> ReadTiles(
|
||||
JsonElement element,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions) =>
|
||||
internal List<Tile> ReadTiles(JsonElement element) =>
|
||||
element.GetValueAsList<Tile>(e =>
|
||||
{
|
||||
var animation = e.GetOptionalPropertyCustom<List<Frame>?>("animation", e => e.GetValueAsList<Frame>(ReadFrame), null);
|
||||
|
@ -174,9 +163,9 @@ internal partial class Tmj
|
|||
var y = e.GetOptionalProperty<uint>("y", 0);
|
||||
var width = e.GetOptionalProperty<uint>("width", imageWidth ?? 0);
|
||||
var height = e.GetOptionalProperty<uint>("height", imageHeight ?? 0);
|
||||
var objectGroup = e.GetOptionalPropertyCustom<ObjectLayer?>("objectgroup", e => ReadObjectLayer(e, externalTemplateResolver, customTypeDefinitions), null);
|
||||
var objectGroup = e.GetOptionalPropertyCustom<ObjectLayer?>("objectgroup", e => ReadObjectLayer(e), null);
|
||||
var probability = e.GetOptionalProperty<float>("probability", 0.0f);
|
||||
var properties = e.GetOptionalPropertyCustom("properties", el => ReadProperties(el, customTypeDefinitions), []);
|
||||
var properties = e.GetOptionalPropertyCustom("properties", ReadProperties, []);
|
||||
// var terrain, replaced by wangsets
|
||||
var type = e.GetOptionalProperty<string>("type", "");
|
||||
|
||||
|
@ -216,14 +205,12 @@ internal partial class Tmj
|
|||
};
|
||||
}
|
||||
|
||||
internal static Wangset ReadWangset(
|
||||
JsonElement element,
|
||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
||||
internal Wangset ReadWangset(JsonElement element)
|
||||
{
|
||||
var @clalss = element.GetOptionalProperty<string>("class", "");
|
||||
var colors = element.GetOptionalPropertyCustom<List<WangColor>>("colors", e => e.GetValueAsList<WangColor>(el => ReadWangColor(el, customTypeDefinitions)), []);
|
||||
var colors = element.GetOptionalPropertyCustom<List<WangColor>>("colors", e => e.GetValueAsList<WangColor>(el => ReadWangColor(el)), []);
|
||||
var name = element.GetRequiredProperty<string>("name");
|
||||
var properties = element.GetOptionalPropertyCustom("properties", e => ReadProperties(e, customTypeDefinitions), []);
|
||||
var properties = element.GetOptionalPropertyCustom("properties", ReadProperties, []);
|
||||
var tile = element.GetOptionalProperty<int>("tile", 0);
|
||||
var type = element.GetOptionalProperty<string>("type", "");
|
||||
var wangTiles = element.GetOptionalPropertyCustom<List<WangTile>>("wangtiles", e => e.GetValueAsList<WangTile>(ReadWangTile), []);
|
||||
|
@ -239,15 +226,13 @@ internal partial class Tmj
|
|||
};
|
||||
}
|
||||
|
||||
internal static WangColor ReadWangColor(
|
||||
JsonElement element,
|
||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
||||
internal WangColor ReadWangColor(JsonElement element)
|
||||
{
|
||||
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("properties", e => ReadProperties(e, customTypeDefinitions), []);
|
||||
var properties = element.GetOptionalPropertyCustom("properties", ReadProperties, []);
|
||||
var tile = element.GetOptionalProperty<int>("tile", 0);
|
||||
|
||||
return new WangColor
|
74
src/DotTiled/Serialization/Tmj/TmjReaderBase.cs
Normal file
74
src/DotTiled/Serialization/Tmj/TmjReaderBase.cs
Normal file
|
@ -0,0 +1,74 @@
|
|||
using System;
|
||||
using System.Text.Json;
|
||||
using DotTiled.Model;
|
||||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for Tiled JSON format readers.
|
||||
/// </summary>
|
||||
public abstract partial class TmjReaderBase : IDisposable
|
||||
{
|
||||
// External resolvers
|
||||
private readonly Func<string, Tileset> _externalTilesetResolver;
|
||||
private readonly Func<string, Template> _externalTemplateResolver;
|
||||
private readonly Func<string, ICustomTypeDefinition> _customTypeResolver;
|
||||
|
||||
/// <summary>
|
||||
/// The root element of the JSON document being read.
|
||||
/// </summary>
|
||||
protected JsonElement RootElement { get; private set; }
|
||||
|
||||
private bool disposedValue;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="TmjMapReader"/>.
|
||||
/// </summary>
|
||||
/// <param name="jsonString">A string containing a Tiled map in the Tiled JSON format.</param>
|
||||
/// <param name="externalTilesetResolver">A function that resolves external tilesets given their source.</param>
|
||||
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
|
||||
/// <param name="customTypeResolver">A collection of custom type definitions that can be used to resolve custom types when encountering <see cref="ClassProperty"/>.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
|
||||
protected TmjReaderBase(
|
||||
string jsonString,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver)
|
||||
{
|
||||
RootElement = JsonDocument.Parse(jsonString ?? throw new ArgumentNullException(nameof(jsonString))).RootElement;
|
||||
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
||||
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
||||
_customTypeResolver = customTypeResolver ?? throw new ArgumentNullException(nameof(customTypeResolver));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// TODO: dispose managed state (managed objects)
|
||||
}
|
||||
|
||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
||||
// TODO: set large fields to null
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||
// ~TmjMapReader()
|
||||
// {
|
||||
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
// Dispose(disposing: false);
|
||||
// }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DotTiled.Model;
|
||||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
@ -7,73 +6,24 @@ namespace DotTiled.Serialization.Tmj;
|
|||
/// <summary>
|
||||
/// A tileset reader for the Tiled JSON format.
|
||||
/// </summary>
|
||||
public class TsjTilesetReader : ITilesetReader
|
||||
public class TsjTilesetReader : TmjReaderBase, ITilesetReader
|
||||
{
|
||||
// External resolvers
|
||||
private readonly Func<string, Template> _externalTemplateResolver;
|
||||
|
||||
private readonly string _jsonString;
|
||||
private bool disposedValue;
|
||||
|
||||
private readonly IReadOnlyCollection<ICustomTypeDefinition> _customTypeDefinitions;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="TsjTilesetReader"/>.
|
||||
/// </summary>
|
||||
/// <param name="jsonString">A string containing a Tiled tileset in the Tiled JSON format.</param>
|
||||
/// <param name="jsonString">A string containing a Tiled map in the Tiled JSON format.</param>
|
||||
/// <param name="externalTilesetResolver">A function that resolves external tilesets given their source.</param>
|
||||
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
|
||||
/// <param name="customTypeDefinitions">A collection of custom type definitions that can be used to resolve custom types when encountering <see cref="ClassProperty"/>.</param>
|
||||
/// <param name="customTypeResolver">A function that resolves custom types given their name.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
|
||||
public TsjTilesetReader(
|
||||
string jsonString,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
||||
{
|
||||
_jsonString = jsonString ?? throw new ArgumentNullException(nameof(jsonString));
|
||||
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
||||
_customTypeDefinitions = customTypeDefinitions ?? throw new ArgumentNullException(nameof(customTypeDefinitions));
|
||||
}
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||
jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Tileset ReadTileset()
|
||||
{
|
||||
var jsonDoc = System.Text.Json.JsonDocument.Parse(_jsonString);
|
||||
var rootElement = jsonDoc.RootElement;
|
||||
return Tmj.ReadTileset(
|
||||
rootElement,
|
||||
_ => throw new NotSupportedException("External tilesets cannot refer to other external tilesets."),
|
||||
_externalTemplateResolver,
|
||||
_customTypeDefinitions);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// TODO: dispose managed state (managed objects)
|
||||
}
|
||||
|
||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
||||
// TODO: set large fields to null
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||
// ~TsjTilesetReader()
|
||||
// {
|
||||
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
// Dispose(disposing: false);
|
||||
// }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
public Tileset ReadTileset() => ReadTileset(RootElement);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,9 @@ public abstract partial class TmxReaderBase
|
|||
{
|
||||
internal List<IProperty> ReadProperties()
|
||||
{
|
||||
if (!_reader.IsStartElement("properties"))
|
||||
return [];
|
||||
|
||||
return _reader.ReadList("properties", "property", (r) =>
|
||||
{
|
||||
var name = r.GetRequiredAttribute("name");
|
||||
|
@ -39,7 +42,8 @@ public abstract partial class TmxReaderBase
|
|||
PropertyType.Color => new ColorProperty { Name = name, Value = r.GetRequiredAttributeParseable<Color>("value") },
|
||||
PropertyType.File => new FileProperty { Name = name, Value = r.GetRequiredAttribute("value") },
|
||||
PropertyType.Object => new ObjectProperty { Name = name, Value = r.GetRequiredAttributeParseable<uint>("value") },
|
||||
PropertyType.Class => ReadClassProperty(),
|
||||
PropertyType.Class => throw new XmlException("Class property must have a property type"),
|
||||
PropertyType.Enum => throw new XmlException("Enum property must have a property type"),
|
||||
_ => throw new XmlException("Invalid property type")
|
||||
};
|
||||
return property;
|
||||
|
@ -49,7 +53,6 @@ public abstract partial class TmxReaderBase
|
|||
internal IProperty ReadPropertyWithCustomType()
|
||||
{
|
||||
var isClass = _reader.GetOptionalAttribute("type") == "class";
|
||||
|
||||
if (isClass)
|
||||
{
|
||||
return ReadClassProperty();
|
||||
|
@ -62,17 +65,24 @@ public abstract partial class TmxReaderBase
|
|||
{
|
||||
var name = _reader.GetRequiredAttribute("name");
|
||||
var propertyType = _reader.GetRequiredAttribute("propertytype");
|
||||
|
||||
var customTypeDef = _customTypeResolver(propertyType);
|
||||
|
||||
if (customTypeDef is CustomClassDefinition ccd)
|
||||
{
|
||||
_reader.ReadStartElement("property");
|
||||
var propsInType = Helpers.CreateInstanceOfCustomClass(ccd);
|
||||
var props = ReadProperties();
|
||||
var mergedProps = Helpers.MergeProperties(propsInType, props);
|
||||
|
||||
_reader.ReadEndElement();
|
||||
return new ClassProperty { Name = name, PropertyType = propertyType, Value = mergedProps };
|
||||
if (!_reader.IsEmptyElement)
|
||||
{
|
||||
_reader.ReadStartElement("property");
|
||||
var propsInType = Helpers.CreateInstanceOfCustomClass(ccd, _customTypeResolver);
|
||||
var props = ReadProperties();
|
||||
var mergedProps = Helpers.MergeProperties(propsInType, props);
|
||||
_reader.ReadEndElement();
|
||||
return new ClassProperty { Name = name, PropertyType = propertyType, Value = mergedProps };
|
||||
}
|
||||
else
|
||||
{
|
||||
var propsInType = Helpers.CreateInstanceOfCustomClass(ccd, _customTypeResolver);
|
||||
return new ClassProperty { Name = name, PropertyType = propertyType, Value = propsInType };
|
||||
}
|
||||
}
|
||||
|
||||
throw new XmlException($"Unkonwn custom class definition: {propertyType}");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue