Big properties work

This commit is contained in:
Daniel Cronqvist 2024-08-10 19:18:33 +02:00
parent 1168917c23
commit 5e93716d2c
15 changed files with 820 additions and 55 deletions

View file

@ -17,7 +17,7 @@ public static partial class DotTiledAssert
Assert.Equal(expected.Visible, actual.Visible);
Assert.Equal(expected.Template, actual.Template);
AssertProperties(actual.Properties, expected.Properties);
AssertProperties(expected.Properties, actual.Properties);
AssertObject((dynamic)expected, (dynamic)actual);
}

View file

@ -19,51 +19,51 @@ public static partial class DotTiledAssert
}
}
private static void AssertProperty(IProperty actual, IProperty expected)
private static void AssertProperty(IProperty expected, IProperty actual)
{
Assert.Equal(expected.Type, actual.Type);
Assert.Equal(expected.Name, actual.Name);
AssertProperties((dynamic)actual, (dynamic)expected);
}
private static void AssertProperty(StringProperty actual, StringProperty expected)
private static void AssertProperty(StringProperty expected, StringProperty actual)
{
Assert.Equal(expected.Value, actual.Value);
}
private static void AssertProperty(IntProperty actual, IntProperty expected)
private static void AssertProperty(IntProperty expected, IntProperty actual)
{
Assert.Equal(expected.Value, actual.Value);
}
private static void AssertProperty(FloatProperty actual, FloatProperty expected)
private static void AssertProperty(FloatProperty expected, FloatProperty actual)
{
Assert.Equal(expected.Value, actual.Value);
}
private static void AssertProperty(BoolProperty actual, BoolProperty expected)
private static void AssertProperty(BoolProperty expected, BoolProperty actual)
{
Assert.Equal(expected.Value, actual.Value);
}
private static void AssertProperty(ColorProperty actual, ColorProperty expected)
private static void AssertProperty(ColorProperty expected, ColorProperty actual)
{
Assert.Equal(expected.Value, actual.Value);
}
private static void AssertProperty(FileProperty actual, FileProperty expected)
private static void AssertProperty(FileProperty expected, FileProperty actual)
{
Assert.Equal(expected.Value, actual.Value);
}
private static void AssertProperty(ObjectProperty actual, ObjectProperty expected)
private static void AssertProperty(ObjectProperty expected, ObjectProperty actual)
{
Assert.Equal(expected.Value, actual.Value);
}
private static void AssertProperty(ClassProperty actual, ClassProperty expected)
private static void AssertProperty(ClassProperty expected, ClassProperty actual)
{
Assert.Equal(expected.PropertyType, actual.PropertyType);
AssertProperties(actual.Properties, expected.Properties);
AssertProperties(expected.Properties, actual.Properties);
}
}

View file

@ -2,7 +2,7 @@ namespace DotTiled.Tests;
public partial class TestData
{
public static Map MapWithObjectTemplate() => new Map
public static Map MapWithObjectTemplate(string templateExtension) => new Map
{
Version = "1.10",
TiledVersion = "1.11.0",
@ -49,7 +49,7 @@ public partial class TestData
new RectangleObject
{
ID = 1,
Template = "map-with-object-template.tx",
Template = $"map-with-object-template.{templateExtension}",
Name = "Thingy 2",
X = 94.5749f,
Y = 33.6842f,
@ -73,7 +73,7 @@ public partial class TestData
new RectangleObject
{
ID = 2,
Template = "map-with-object-template.tx",
Template = $"map-with-object-template.{templateExtension}",
Name = "Thingy",
X = 29.7976f,
Y = 33.8693f,
@ -97,7 +97,7 @@ public partial class TestData
new RectangleObject
{
ID = 3,
Template = "map-with-object-template.tx",
Template = $"map-with-object-template.{templateExtension}",
Name = "Thingy 3",
X = 5,
Y = 5,
@ -112,7 +112,7 @@ public partial class TestData
PropertyType = "TestClass",
Properties = new Dictionary<string, IProperty>
{
["Amount"] = new FloatProperty { Name = "Amount", Value = 4.2f },
["Amount"] = new FloatProperty { Name = "Amount", Value = 0.0f },
["Name"] = new StringProperty { Name = "Name", Value = "I am here 3" }
}
}

View file

@ -0,0 +1,24 @@
[
{
"color": "#ffa0a0a4",
"drawFill": true,
"id": 1,
"members": [
{
"name": "Amount",
"type": "float",
"value": 0
},
{
"name": "Name",
"type": "string",
"value": ""
}
],
"name": "TestClass",
"type": "class",
"useAs": [
"property"
]
}
]

View file

@ -0,0 +1,103 @@
[
{
"id": 4,
"name": "Enum0String",
"storageType": "string",
"type": "enum",
"values": [
"Enum0_1",
"Enum0_2",
"Enum0_3"
],
"valuesAsFlags": false
},
{
"id": 5,
"name": "Enum1Num",
"storageType": "int",
"type": "enum",
"values": [
"Enum1Num_1",
"Enum1Num_2",
"Enum1Num_3",
"Enum1Num_4"
],
"valuesAsFlags": false
},
{
"id": 6,
"name": "Enum2StringFlags",
"storageType": "string",
"type": "enum",
"values": [
"Enum2StringFlags_1",
"Enum2StringFlags_2",
"Enum2StringFlags_3",
"Enum2StringFlags_4"
],
"valuesAsFlags": true
},
{
"id": 7,
"name": "Enum3NumFlags",
"storageType": "int",
"type": "enum",
"values": [
"Enum3NumFlags_1",
"Enum3NumFlags_2",
"Enum3NumFlags_3",
"Enum3NumFlags_4",
"Enum3NumFlags_5"
],
"valuesAsFlags": true
},
{
"color": "#ffa0a0a4",
"drawFill": true,
"id": 2,
"members": [
{
"name": "Yep",
"propertyType": "TestClass",
"type": "class",
"value": {
}
}
],
"name": "Test",
"type": "class",
"useAs": [
"property",
"map",
"layer",
"object",
"tile",
"tileset",
"wangcolor",
"wangset",
"project"
]
},
{
"color": "#ffa0a0a4",
"drawFill": true,
"id": 1,
"members": [
{
"name": "Amount",
"type": "float",
"value": 0
},
{
"name": "Name",
"type": "string",
"value": ""
}
],
"name": "TestClass",
"type": "class",
"useAs": [
"property"
]
}
]

View file

@ -20,17 +20,13 @@ public partial class TmjMapReaderTests
var json = TestData.GetRawStringFor(testDataFile);
static Template ResolveTemplate(string source)
{
var templateJson = TestData.GetRawStringFor($"Serialization.TestData.Template.{source}");
//var templateReader = new TmjTemplateReader(templateJson, ResolveTemplate);
return null;
throw new NotSupportedException("External templates are not supported in this test.");
}
static Tileset ResolveTileset(string source)
{
var tilesetJson = TestData.GetXmlReaderFor($"Serialization.TestData.Tileset.{source}");
//var tilesetReader = new TmjTilesetReader(tilesetJson, ResolveTileset, ResolveTemplate);
return null;
throw new NotSupportedException("External tilesets are not supported in this test.");
}
using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate);
using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate, []);
// Act
var map = mapReader.ReadMap();
@ -42,7 +38,7 @@ public partial class TmjMapReaderTests
public static IEnumerable<object[]> DeserializeMap_ValidTmjExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data =>
[
["Serialization.TestData.Map.map-with-object-template.tmj", TestData.MapWithObjectTemplate()],
["Serialization.TestData.Map.map-with-object-template.tmj", TestData.MapWithObjectTemplate("tj")],
["Serialization.TestData.Map.map-with-group.tmj", TestData.MapWithGroup()],
];
@ -51,20 +47,55 @@ public partial class TmjMapReaderTests
public void TmxMapReaderReadMap_ValidTmjExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap)
{
// Arrange
CustomTypeDefinition[] customTypeDefinitions = [
new CustomClassDefinition
{
Name = "TestClass",
ID = 1,
UseAs = CustomClassUseAs.Property,
Members = [
new StringProperty
{
Name = "Name",
Value = ""
},
new FloatProperty
{
Name = "Amount",
Value = 0f
}
]
},
new CustomClassDefinition
{
Name = "Test",
ID = 2,
UseAs = CustomClassUseAs.All,
Members = [
new ClassProperty
{
Name = "Yep",
PropertyType = "TestClass",
Properties = []
}
]
}
];
var json = TestData.GetRawStringFor(testDataFile);
static Template ResolveTemplate(string source)
Template ResolveTemplate(string source)
{
var templateJson = TestData.GetRawStringFor($"Serialization.TestData.Template.{source}");
//var templateReader = new TmjTemplateReader(templateJson, ResolveTemplate);
return null;
using var templateReader = new TjTemplateReader(templateJson, ResolveTileset, ResolveTemplate, customTypeDefinitions);
return templateReader.ReadTemplate();
}
static Tileset ResolveTileset(string source)
Tileset ResolveTileset(string source)
{
var tilesetJson = TestData.GetXmlReaderFor($"Serialization.TestData.Tileset.{source}");
//var tilesetReader = new TmjTilesetReader(tilesetJson, ResolveTileset, ResolveTemplate);
return null;
var tilesetJson = TestData.GetRawStringFor($"Serialization.TestData.Tileset.{source}");
using var tilesetReader = new TsjTilesetReader(tilesetJson, ResolveTemplate, customTypeDefinitions);
return tilesetReader.ReadTileset();
}
using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate);
using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate, customTypeDefinitions);
// Act
var map = mapReader.ReadMap();

View file

@ -116,7 +116,7 @@ public partial class TmxMapReaderTests
public static IEnumerable<object[]> DeserializeMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data =>
[
["Serialization.TestData.Map.map-with-object-template.tmx", TestData.MapWithObjectTemplate()],
["Serialization.TestData.Map.map-with-object-template.tmx", TestData.MapWithObjectTemplate("tx")],
["Serialization.TestData.Map.map-with-group.tmx", TestData.MapWithGroup()],
];

View file

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace DotTiled;
@ -18,6 +20,8 @@ public interface IProperty
{
public string Name { get; set; }
public PropertyType Type { get; }
IProperty Clone();
}
public class StringProperty : IProperty
@ -25,6 +29,12 @@ public class StringProperty : IProperty
public required string Name { get; set; }
public PropertyType Type => PropertyType.String;
public required string Value { get; set; }
public IProperty Clone() => new StringProperty
{
Name = Name,
Value = Value
};
}
public class IntProperty : IProperty
@ -32,6 +42,12 @@ public class IntProperty : IProperty
public required string Name { get; set; }
public PropertyType Type => PropertyType.Int;
public required int Value { get; set; }
public IProperty Clone() => new IntProperty
{
Name = Name,
Value = Value
};
}
public class FloatProperty : IProperty
@ -39,6 +55,12 @@ public class FloatProperty : IProperty
public required string Name { get; set; }
public PropertyType Type => PropertyType.Float;
public required float Value { get; set; }
public IProperty Clone() => new FloatProperty
{
Name = Name,
Value = Value
};
}
public class BoolProperty : IProperty
@ -46,6 +68,12 @@ public class BoolProperty : IProperty
public required string Name { get; set; }
public PropertyType Type => PropertyType.Bool;
public required bool Value { get; set; }
public IProperty Clone() => new BoolProperty
{
Name = Name,
Value = Value
};
}
public class ColorProperty : IProperty
@ -53,6 +81,12 @@ public class ColorProperty : IProperty
public required string Name { get; set; }
public PropertyType Type => PropertyType.Color;
public required Color Value { get; set; }
public IProperty Clone() => new ColorProperty
{
Name = Name,
Value = Value
};
}
public class FileProperty : IProperty
@ -60,6 +94,12 @@ public class FileProperty : IProperty
public required string Name { get; set; }
public PropertyType Type => PropertyType.File;
public required string Value { get; set; }
public IProperty Clone() => new FileProperty
{
Name = Name,
Value = Value
};
}
public class ObjectProperty : IProperty
@ -67,6 +107,12 @@ public class ObjectProperty : IProperty
public required string Name { get; set; }
public PropertyType Type => PropertyType.Object;
public required uint Value { get; set; }
public IProperty Clone() => new ObjectProperty
{
Name = Name,
Value = Value
};
}
public class ClassProperty : IProperty
@ -75,4 +121,53 @@ public class ClassProperty : IProperty
public PropertyType Type => DotTiled.PropertyType.Class;
public required string PropertyType { get; set; }
public required Dictionary<string, IProperty> Properties { get; set; }
public IProperty Clone() => new ClassProperty
{
Name = Name,
PropertyType = PropertyType,
Properties = Properties.ToDictionary(p => p.Key, p => p.Value.Clone())
};
}
public abstract class CustomTypeDefinition
{
public uint ID { get; set; }
public string Name { get; set; } = "";
}
[Flags]
public enum CustomClassUseAs
{
Property,
Map,
Layer,
Object,
Tile,
Tileset,
WangColor,
Wangset,
Project,
All = Property | Map | Layer | Object | Tile | Tileset | WangColor | Wangset | Project
}
public class CustomClassDefinition : CustomTypeDefinition
{
public Color Color { get; set; }
public bool DrawFill { get; set; }
public CustomClassUseAs UseAs { get; set; }
public List<IProperty> Members { get; set; }
}
public enum CustomEnumStorageType
{
Int,
String
}
public class CustomEnumDefinition : CustomTypeDefinition
{
public CustomEnumStorageType StorageType { get; set; }
public List<string> Values { get; set; } = [];
public bool ValueAsFlags { get; set; }
}

View file

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
namespace DotTiled;
public class TjTemplateReader : 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<CustomTypeDefinition> _customTypeDefinitions;
public TjTemplateReader(
string jsonString,
Func<string, Tileset> externalTilesetResolver,
Func<string, Template> externalTemplateResolver,
IReadOnlyCollection<CustomTypeDefinition> 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));
}
public Template ReadTemplate()
{
var jsonDoc = System.Text.Json.JsonDocument.Parse(_jsonString);
var rootElement = jsonDoc.RootElement;
return Tmj.ReadTemplate(rootElement, _externalTilesetResolver, _externalTemplateResolver, _customTypeDefinitions);
}
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);
// }
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}

View file

@ -4,27 +4,33 @@ using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Numerics;
using System.Text.Json;
namespace DotTiled;
internal partial class Tmj
{
internal static BaseLayer ReadLayer(JsonElement element)
internal static BaseLayer ReadLayer(
JsonElement element,
Func<string, Template> externalTemplateResolver,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
{
var type = element.GetRequiredProperty<string>("type");
return type switch
{
"tilelayer" => ReadTileLayer(element),
// "objectgroup" => ReadObjectGroup(element),
"tilelayer" => ReadTileLayer(element, customTypeDefinitions),
"objectgroup" => ReadObjectLayer(element, externalTemplateResolver, customTypeDefinitions),
// "imagelayer" => ReadImageLayer(element),
// "group" => ReadGroup(element),
"group" => ReadGroup(element, externalTemplateResolver, customTypeDefinitions),
_ => throw new JsonException($"Unsupported layer type '{type}'.")
};
}
internal static TileLayer ReadTileLayer(JsonElement element)
internal static TileLayer ReadTileLayer(
JsonElement element,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
{
var compression = element.GetOptionalPropertyParseable<DataCompression?>("compression", s => s switch
{
@ -50,7 +56,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<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e), null);
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);
@ -85,4 +91,276 @@ internal partial class Tmj
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
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
};
}
// 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
};
}
}

View file

@ -9,7 +9,11 @@ namespace DotTiled;
internal partial class Tmj
{
internal static Map ReadMap(JsonElement element, Func<string, Tileset>? externalTilesetResolver, Func<string, Template> externalTemplateResolver)
internal static Map ReadMap(
JsonElement element,
Func<string, Tileset>? externalTilesetResolver,
Func<string, Template> externalTemplateResolver,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
{
var version = element.GetRequiredProperty<string>("version");
var tiledVersion = element.GetRequiredProperty<string>("tiledversion");
@ -55,10 +59,10 @@ internal partial class Tmj
var nextObjectID = element.GetRequiredProperty<uint>("nextobjectid");
var infinite = element.GetOptionalProperty<bool>("infinite", false);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", ReadProperties, null);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", el => ReadProperties(el, customTypeDefinitions), null);
List<BaseLayer> layers = element.GetOptionalPropertyCustom<List<BaseLayer>>("layers", e => e.GetValueAsList<BaseLayer>(ReadLayer), []);
List<Tileset> tilesets = element.GetOptionalPropertyCustom<List<Tileset>>("tilesets", e => e.GetValueAsList<Tileset>(el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver)), []);
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)), []);
return new Map
{

View file

@ -8,7 +8,9 @@ namespace DotTiled;
internal partial class Tmj
{
internal static Dictionary<string, IProperty> ReadProperties(JsonElement element) =>
internal static Dictionary<string, IProperty> ReadProperties(
JsonElement element,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions) =>
element.GetValueAsList<IProperty>(e =>
{
var name = e.GetRequiredProperty<string>("name");
@ -34,20 +36,105 @@ internal partial class Tmj
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),
PropertyType.Class => ReadClassProperty(e, customTypeDefinitions),
_ => throw new JsonException("Invalid property type")
};
return property!;
}).ToDictionary(p => p.Name);
internal static ClassProperty ReadClassProperty(JsonElement element)
internal static ClassProperty ReadClassProperty(
JsonElement element,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
{
var name = element.GetRequiredProperty<string>("name");
var propertyType = element.GetRequiredProperty<string>("propertytype");
var properties = element.GetRequiredPropertyCustom<Dictionary<string, IProperty>>("properties", ReadProperties);
var customTypeDef = customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == propertyType);
return new ClassProperty { Name = name, PropertyType = propertyType, Properties = properties };
if (customTypeDef is CustomClassDefinition ccd)
{
var propsInType = CreateInstanceOfCustomClass(ccd);
var props = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>>("value", el => ReadCustomClassProperties(el, ccd, customTypeDefinitions), []);
var mergedProps = MergeProperties(propsInType, props);
return new ClassProperty
{
Name = name,
PropertyType = propertyType,
Properties = mergedProps
};
}
throw new JsonException($"Unknown custom class '{propertyType}'.");
}
internal static Dictionary<string, IProperty> ReadCustomClassProperties(
JsonElement element,
CustomClassDefinition customClassDefinition,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
{
Dictionary<string, IProperty> resultingProps = [];
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")
};
resultingProps[prop.Name] = property;
}
return resultingProps;
}
internal static Dictionary<string, IProperty> CreateInstanceOfCustomClass(CustomClassDefinition customClassDefinition)
{
return customClassDefinition.Members.ToDictionary(m => m.Name, m => m.Clone());
}
internal static Dictionary<string, IProperty> MergeProperties(Dictionary<string, IProperty>? baseProperties, Dictionary<string, IProperty> overrideProperties)
{
if (baseProperties is null)
return overrideProperties ?? new Dictionary<string, IProperty>();
if (overrideProperties is null)
return baseProperties;
var result = baseProperties.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Clone());
foreach (var (key, value) in overrideProperties)
{
if (!result.TryGetValue(key, out var baseProp))
{
result[key] = value;
continue;
}
else
{
if (value is ClassProperty classProp)
{
((ClassProperty)baseProp).Properties = MergeProperties(((ClassProperty)baseProp).Properties, classProp.Properties);
}
else
{
result[key] = value;
}
}
}
return result;
}
}

View file

@ -10,7 +10,11 @@ namespace DotTiled;
internal partial class Tmj
{
internal static Tileset ReadTileset(JsonElement element, Func<string, Tileset>? externalTilesetResolver, Func<string, Template> externalTemplateResolver)
internal static Tileset ReadTileset(
JsonElement element,
Func<string, Tileset>? externalTilesetResolver,
Func<string, Template> externalTemplateResolver,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
{
var backgroundColor = element.GetOptionalPropertyParseable<Color?>("backgroundcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
var @class = element.GetOptionalProperty<string>("class", "");
@ -42,7 +46,7 @@ internal partial class Tmj
"bottomright" => ObjectAlignment.BottomRight,
_ => throw new JsonException($"Unknown object alignment '{s}'")
}, ObjectAlignment.Unspecified);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", ReadProperties, null);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", el => ReadProperties(el, customTypeDefinitions), null);
var source = element.GetOptionalProperty<string?>("source", null);
var spacing = element.GetOptionalProperty<uint?>("spacing", null);
var tileCount = element.GetOptionalProperty<uint?>("tilecount", null);
@ -55,7 +59,7 @@ internal partial class Tmj
"grid" => TileRenderSize.Grid,
_ => throw new JsonException($"Unknown tile render size '{s}'")
}, TileRenderSize.Tile);
var tiles = element.GetOptionalPropertyCustom<List<Tile>>("tiles", ReadTiles, []);
var tiles = element.GetOptionalPropertyCustom<List<Tile>>("tiles", el => ReadTiles(el, customTypeDefinitions), []);
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);
@ -153,7 +157,9 @@ internal partial class Tmj
};
}
internal static List<Tile> ReadTiles(JsonElement element) =>
internal static List<Tile> ReadTiles(
JsonElement element,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions) =>
element.GetValueAsList<Tile>(e =>
{
//var animation = e.GetOptionalPropertyCustom<List<Frame>>("animation", ReadFrames, null);
@ -167,7 +173,7 @@ internal partial class Tmj
var height = e.GetOptionalProperty<uint>("height", imageHeight ?? 0);
//var objectGroup = e.GetOptionalPropertyCustom<ObjectLayer?>("objectgroup", ReadObjectLayer, null);
var probability = e.GetOptionalProperty<float>("probability", 1.0f);
var properties = e.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", ReadProperties, null);
var properties = e.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", el => ReadProperties(el, customTypeDefinitions), null);
// var terrain, replaced by wangsets
var type = e.GetOptionalProperty<string>("type", "");

View file

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.Json;
@ -14,18 +15,25 @@ public class TmjMapReader : IMapReader
private string _jsonString;
private bool disposedValue;
public TmjMapReader(string jsonString, Func<string, Tileset> externalTilesetResolver, Func<string, Template> externalTemplateResolver)
private readonly IReadOnlyCollection<CustomTypeDefinition> _customTypeDefinitions;
public TmjMapReader(
string jsonString,
Func<string, Tileset> externalTilesetResolver,
Func<string, Template> externalTemplateResolver,
IReadOnlyCollection<CustomTypeDefinition> 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));
}
public Map ReadMap()
{
var jsonDoc = JsonDocument.Parse(_jsonString);
var rootElement = jsonDoc.RootElement;
return Tmj.ReadMap(rootElement, _externalTilesetResolver, _externalTemplateResolver);
return Tmj.ReadMap(rootElement, _externalTilesetResolver, _externalTemplateResolver, _customTypeDefinitions);
}
protected virtual void Dispose(bool disposing)

View file

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
namespace DotTiled;
public class TsjTilesetReader : ITilesetReader
{
// External resolvers
private readonly Func<string, Template> _externalTemplateResolver;
private readonly string _jsonString;
private bool disposedValue;
private readonly IReadOnlyCollection<CustomTypeDefinition> _customTypeDefinitions;
public TsjTilesetReader(
string jsonString,
Func<string, Template> externalTemplateResolver,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
{
_jsonString = jsonString ?? throw new ArgumentNullException(nameof(jsonString));
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
_customTypeDefinitions = customTypeDefinitions ?? throw new ArgumentNullException(nameof(customTypeDefinitions));
}
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);
}
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);
// }
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
System.GC.SuppressFinalize(this);
}
}