Getting further with the json support

This commit is contained in:
Daniel Cronqvist 2024-08-09 23:12:45 +02:00
parent 5626614acd
commit 1168917c23
38 changed files with 1072 additions and 269 deletions

View file

@ -30,11 +30,11 @@ public static partial class DotTiledAssert
Assert.NotNull(actual.Tilesets); Assert.NotNull(actual.Tilesets);
Assert.Equal(expected.Tilesets.Count, actual.Tilesets.Count); Assert.Equal(expected.Tilesets.Count, actual.Tilesets.Count);
for (var i = 0; i < expected.Tilesets.Count; i++) for (var i = 0; i < expected.Tilesets.Count; i++)
AssertTileset(actual.Tilesets[i], expected.Tilesets[i]); AssertTileset(expected.Tilesets[i], actual.Tilesets[i]);
Assert.NotNull(actual.Layers); Assert.NotNull(actual.Layers);
Assert.Equal(expected.Layers.Count, actual.Layers.Count); Assert.Equal(expected.Layers.Count, actual.Layers.Count);
for (var i = 0; i < expected.Layers.Count; i++) for (var i = 0; i < expected.Layers.Count; i++)
AssertLayer(actual.Layers[i], expected.Layers[i]); AssertLayer(expected.Layers[i], actual.Layers[i]);
} }
} }

View file

@ -25,8 +25,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<!-- TmxSerializer test data --> <!-- Test data -->
<EmbeddedResource Include="Serialization/Tmx/TestData/**/*" /> <EmbeddedResource Include="Serialization/TestData/**/*" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -2,12 +2,12 @@ using System.Xml;
namespace DotTiled.Tests; namespace DotTiled.Tests;
public static class TmxMapReaderTestData public static partial class TestData
{ {
public static XmlReader GetXmlReaderFor(string testDataFile) public static XmlReader GetXmlReaderFor(string testDataFile)
{ {
var fullyQualifiedTestDataFile = $"DotTiled.Tests.{testDataFile}"; var fullyQualifiedTestDataFile = $"DotTiled.Tests.{testDataFile}";
using var stream = typeof(TmxMapReaderTestData).Assembly.GetManifestResourceStream(fullyQualifiedTestDataFile) using var stream = typeof(TestData).Assembly.GetManifestResourceStream(fullyQualifiedTestDataFile)
?? throw new ArgumentException($"Test data file '{fullyQualifiedTestDataFile}' not found"); ?? throw new ArgumentException($"Test data file '{fullyQualifiedTestDataFile}' not found");
using var stringReader = new StreamReader(stream); using var stringReader = new StreamReader(stream);
@ -19,7 +19,7 @@ public static class TmxMapReaderTestData
public static string GetRawStringFor(string testDataFile) public static string GetRawStringFor(string testDataFile)
{ {
var fullyQualifiedTestDataFile = $"DotTiled.Tests.{testDataFile}"; var fullyQualifiedTestDataFile = $"DotTiled.Tests.{testDataFile}";
using var stream = typeof(TmxMapReaderTestData).Assembly.GetManifestResourceStream(fullyQualifiedTestDataFile) using var stream = typeof(TestData).Assembly.GetManifestResourceStream(fullyQualifiedTestDataFile)
?? throw new ArgumentException($"Test data file '{fullyQualifiedTestDataFile}' not found"); ?? throw new ArgumentException($"Test data file '{fullyQualifiedTestDataFile}' not found");
using var stringReader = new StreamReader(stream); using var stringReader = new StreamReader(stream);

View file

@ -0,0 +1,30 @@
{ "compressionlevel":-1,
"height":5,
"infinite":false,
"layers":[
{
"compression":"gzip",
"data":"H4sIAAAAAAAACmNgoD0AAMrGiJlkAAAA",
"encoding":"base64",
"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",
"renderorder":"right-down",
"tiledversion":"1.11.0",
"tileheight":32,
"tilesets":[],
"tilewidth":32,
"type":"map",
"version":"1.10",
"width":5
}

View file

@ -0,0 +1,30 @@
{ "compressionlevel":-1,
"height":5,
"infinite":false,
"layers":[
{
"compression":"zlib",
"data":"eJxjYKA9AAAAZAAB",
"encoding":"base64",
"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",
"renderorder":"right-down",
"tiledversion":"1.11.0",
"tileheight":32,
"tilesets":[],
"tilewidth":32,
"type":"map",
"version":"1.10",
"width":5
}

View file

@ -0,0 +1,30 @@
{ "compressionlevel":-1,
"height":5,
"infinite":false,
"layers":[
{
"compression":"",
"data":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
"encoding":"base64",
"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",
"renderorder":"right-down",
"tiledversion":"1.11.0",
"tileheight":32,
"tilesets":[],
"tilewidth":32,
"type":"map",
"version":"1.10",
"width":5
}

View file

@ -0,0 +1,32 @@
{ "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",
"renderorder":"right-down",
"tiledversion":"1.11.0",
"tileheight":32,
"tilesets":[],
"tilewidth":32,
"type":"map",
"version":"1.10",
"width":5
}

View file

@ -1,8 +1,8 @@
namespace DotTiled.Tests; namespace DotTiled.Tests;
public partial class TmxMapReaderTests public partial class TestData
{ {
private static Map EmptyMapWithProperties() => new Map public static Map EmptyMapWithProperties() => new Map
{ {
Version = "1.10", Version = "1.10",
TiledVersion = "1.11.0", TiledVersion = "1.11.0",

View file

@ -0,0 +1,69 @@
{ "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":"MapBool",
"type":"bool",
"value":true
},
{
"name":"MapColor",
"type":"color",
"value":"#ffff0000"
},
{
"name":"MapFile",
"type":"file",
"value":"file.png"
},
{
"name":"MapFloat",
"type":"float",
"value":5.2
},
{
"name":"MapInt",
"type":"int",
"value":42
},
{
"name":"MapObject",
"type":"object",
"value":5
},
{
"name":"MapString",
"type":"string",
"value":"string in map"
}],
"renderorder":"right-down",
"tiledversion":"1.11.0",
"tileheight":32,
"tilesets":[],
"tilewidth":32,
"type":"map",
"version":"1.10",
"width":5
}

View file

@ -1,8 +1,8 @@
namespace DotTiled.Tests; namespace DotTiled.Tests;
public partial class TmxMapReaderTests public partial class TestData
{ {
private static Map EmptyMapWithEncodingAndCompression(DataEncoding dataEncoding, DataCompression? compression) => new Map public static Map EmptyMapWithEncodingAndCompression(DataEncoding dataEncoding, DataCompression? compression) => new Map
{ {
Version = "1.10", Version = "1.10",
TiledVersion = "1.11.0", TiledVersion = "1.11.0",

View file

@ -1,8 +1,8 @@
namespace DotTiled.Tests; namespace DotTiled.Tests;
public partial class TmxMapReaderTests public partial class TestData
{ {
private static Map MapWithGroup() => new Map public static Map MapWithGroup() => new Map
{ {
Version = "1.10", Version = "1.10",
TiledVersion = "1.11.0", TiledVersion = "1.11.0",

View file

@ -0,0 +1,80 @@
{ "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":4,
"name":"Tile Layer 2",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":5,
"x":0,
"y":0
},
{
"id":3,
"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
},
{
"draworder":"topdown",
"id":2,
"name":"Object Layer 1",
"objects":[
{
"height":64.5,
"id":1,
"name":"Name",
"rotation":0,
"type":"",
"visible":true,
"width":64.5,
"x":35.5,
"y":26
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"name":"Group 1",
"opacity":1,
"type":"group",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":5,
"nextobjectid":2,
"orientation":"orthogonal",
"renderorder":"right-down",
"tiledversion":"1.11.0",
"tileheight":32,
"tilesets":[],
"tilewidth":32,
"type":"map",
"version":"1.10",
"width":5
}

View file

@ -1,8 +1,8 @@
namespace DotTiled.Tests; namespace DotTiled.Tests;
public partial class TmxMapReaderTests public partial class TestData
{ {
private static Map MapWithObjectTemplate() => new Map public static Map MapWithObjectTemplate() => new Map
{ {
Version = "1.10", Version = "1.10",
TiledVersion = "1.11.0", TiledVersion = "1.11.0",

View file

@ -0,0 +1,104 @@
{ "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
},
{
"draworder":"topdown",
"id":2,
"name":"Object Layer 1",
"objects":[
{
"height":37.0156,
"id":1,
"template":"map-with-object-template.tj",
"name":"Thingy 2",
"properties":[
{
"name":"Bool",
"type":"bool",
"value":true
},
{
"name":"TestClassInTemplate",
"propertytype":"TestClass",
"type":"class",
"value":
{
"Amount":37,
"Name":"I am here"
}
}],
"rotation":0,
"type":"",
"visible":true,
"width":37.0156,
"x":94.5749,
"y":33.6842
},
{
"id":2,
"template":"map-with-object-template.tj",
"x":29.7976,
"y":33.8693
},
{
"height":37.0156,
"id":3,
"template":"map-with-object-template.tj",
"name":"Thingy 3",
"properties":[
{
"name":"Bool",
"type":"bool",
"value":true
},
{
"name":"TestClassInTemplate",
"propertytype":"TestClass",
"type":"class",
"value":
{
"Name":"I am here 3"
}
}],
"rotation":0,
"type":"",
"visible":true,
"width":37.0156,
"x":5,
"y":5
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":3,
"nextobjectid":3,
"orientation":"orthogonal",
"renderorder":"right-down",
"tiledversion":"1.11.0",
"tileheight":32,
"tilesets":[],
"tilewidth":32,
"type":"map",
"version":"1.10",
"width":5
}

View file

@ -1,8 +1,8 @@
namespace DotTiled.Tests; namespace DotTiled.Tests;
public partial class TmxMapReaderTests public partial class TestData
{ {
private static Map SimpleMapWithEmbeddedTileset() => new Map public static Map SimpleMapWithEmbeddedTileset() => new Map
{ {
Version = "1.10", Version = "1.10",
TiledVersion = "1.11.0", TiledVersion = "1.11.0",
@ -26,6 +26,7 @@ public partial class TmxMapReaderTests
Columns = 4, Columns = 4,
Image = new Image Image = new Image
{ {
Format = ImageFormat.Png,
Source = "tiles.png", Source = "tiles.png",
Width = 128, Width = 128,
Height = 64 Height = 64

View file

@ -0,0 +1,45 @@
{ "compressionlevel":-1,
"height":5,
"infinite":false,
"layers":[
{
"data":[1, 1, 1, 1, 1,
1, 1, 1, 1, 1,
1, 1, 1, 1, 1,
2, 2, 2, 2, 2,
2, 2, 2, 2, 2],
"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",
"renderorder":"right-down",
"tiledversion":"1.11.0",
"tileheight":32,
"tilesets":[
{
"columns":4,
"firstgid":1,
"image":"tiles.png",
"imageheight":64,
"imagewidth":128,
"margin":0,
"name":"Tileset 1",
"spacing":0,
"tilecount":8,
"tileheight":32,
"tilewidth":32
}],
"tilewidth":32,
"type":"map",
"version":"1.10",
"width":5
}

View file

@ -0,0 +1,28 @@
{ "object":
{
"height":37.0156,
"id":2,
"name":"Thingy",
"properties":[
{
"name":"Bool",
"type":"bool",
"value":true
},
{
"name":"TestClassInTemplate",
"propertytype":"TestClass",
"type":"class",
"value":
{
"Amount":4.2,
"Name":"Hello there"
}
}],
"rotation":0,
"type":"",
"visible":true,
"width":37.0156
},
"type":"template"
}

View file

@ -2,64 +2,75 @@ namespace DotTiled.Tests;
public partial class TmjMapReaderTests public partial class TmjMapReaderTests
{ {
[Fact] public static IEnumerable<object[]> DeserializeMap_ValidTmjNoExternalTilesets_ReturnsMapWithoutThrowing_Data =>
public void Test1() [
["Serialization.TestData.Map.empty-map-csv.tmj", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Csv, null)],
["Serialization.TestData.Map.empty-map-base64.tmj", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Base64, null)],
["Serialization.TestData.Map.empty-map-base64-gzip.tmj", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.GZip)],
["Serialization.TestData.Map.empty-map-base64-zlib.tmj", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.ZLib)],
["Serialization.TestData.Map.simple-tileset-embed.tmj", TestData.SimpleMapWithEmbeddedTileset()],
["Serialization.TestData.Map.empty-map-properties.tmj", TestData.EmptyMapWithProperties()],
];
[Theory]
[MemberData(nameof(DeserializeMap_ValidTmjNoExternalTilesets_ReturnsMapWithoutThrowing_Data))]
public void TmxMapReaderReadMap_ValidTmjNoExternalTilesets_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap)
{ {
// Arrange // Arrange
var jsonString = var json = TestData.GetRawStringFor(testDataFile);
""" static Template ResolveTemplate(string source)
{ {
"backgroundcolor":"#656667", var templateJson = TestData.GetRawStringFor($"Serialization.TestData.Template.{source}");
"height":4, //var templateReader = new TmjTemplateReader(templateJson, ResolveTemplate);
"nextobjectid":1, return null;
"nextlayerid":1,
"orientation":"orthogonal",
"properties": [
{
"name":"mapProperty1",
"type":"string",
"value":"one"
},
{
"name":"mapProperty3",
"type":"string",
"value":"twoeee"
} }
], static Tileset ResolveTileset(string source)
"renderorder":"right-down",
"tileheight":32,
"tilewidth":32,
"version":"1",
"tiledversion":"1.0.3",
"width":4,
"tilesets": [
{ {
"columns":19, var tilesetJson = TestData.GetXmlReaderFor($"Serialization.TestData.Tileset.{source}");
"firstgid":1, //var tilesetReader = new TmjTilesetReader(tilesetJson, ResolveTileset, ResolveTemplate);
"image":"image/fishbaddie_parts.png", return null;
"imageheight":480,
"imagewidth":640,
"margin":3,
"name":"",
"properties":[
{
"name":"myProperty1",
"type":"string",
"value":"myProperty1_value"
}],
"spacing":1,
"tilecount":266,
"tileheight":32,
"tilewidth":32
} }
] using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate);
}
""";
// Act // Act
using var tmjMapReader = new TmjMapReader(jsonString); var map = mapReader.ReadMap();
// Assert // Assert
var map = tmjMapReader.ReadMap(); Assert.NotNull(map);
DotTiledAssert.AssertMap(expectedMap, map);
}
public static IEnumerable<object[]> DeserializeMap_ValidTmjExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data =>
[
["Serialization.TestData.Map.map-with-object-template.tmj", TestData.MapWithObjectTemplate()],
["Serialization.TestData.Map.map-with-group.tmj", TestData.MapWithGroup()],
];
[Theory]
[MemberData(nameof(DeserializeMap_ValidTmjExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data))]
public void TmxMapReaderReadMap_ValidTmjExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap)
{
// Arrange
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;
}
static Tileset ResolveTileset(string source)
{
var tilesetJson = TestData.GetXmlReaderFor($"Serialization.TestData.Tileset.{source}");
//var tilesetReader = new TmjTilesetReader(tilesetJson, ResolveTileset, ResolveTemplate);
return null;
}
using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate);
// Act
var map = mapReader.ReadMap();
// Assert
Assert.NotNull(map);
DotTiledAssert.AssertMap(expectedMap, map);
} }
} }

View file

@ -78,12 +78,12 @@ public partial class TmxMapReaderTests
public static IEnumerable<object[]> DeserializeMap_ValidXmlNoExternalTilesets_ReturnsMapWithoutThrowing_Data => public static IEnumerable<object[]> DeserializeMap_ValidXmlNoExternalTilesets_ReturnsMapWithoutThrowing_Data =>
[ [
["Serialization.Tmx.TestData.Map.empty-map-csv.tmx", EmptyMapWithEncodingAndCompression(DataEncoding.Csv, null)], ["Serialization.TestData.Map.empty-map-csv.tmx", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Csv, null)],
["Serialization.Tmx.TestData.Map.empty-map-base64.tmx", EmptyMapWithEncodingAndCompression(DataEncoding.Base64, null)], ["Serialization.TestData.Map.empty-map-base64.tmx", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Base64, null)],
["Serialization.Tmx.TestData.Map.empty-map-base64-gzip.tmx", EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.GZip)], ["Serialization.TestData.Map.empty-map-base64-gzip.tmx", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.GZip)],
["Serialization.Tmx.TestData.Map.empty-map-base64-zlib.tmx", EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.ZLib)], ["Serialization.TestData.Map.empty-map-base64-zlib.tmx", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.ZLib)],
["Serialization.Tmx.TestData.Map.simple-tileset-embed.tmx", SimpleMapWithEmbeddedTileset()], ["Serialization.TestData.Map.simple-tileset-embed.tmx", TestData.SimpleMapWithEmbeddedTileset()],
["Serialization.Tmx.TestData.Map.empty-map-properties.tmx", EmptyMapWithProperties()], ["Serialization.TestData.Map.empty-map-properties.tmx", TestData.EmptyMapWithProperties()],
]; ];
[Theory] [Theory]
@ -91,16 +91,16 @@ public partial class TmxMapReaderTests
public void TmxMapReaderReadMap_ValidXmlNoExternalTilesets_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) public void TmxMapReaderReadMap_ValidXmlNoExternalTilesets_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap)
{ {
// Arrange // Arrange
using var reader = TmxMapReaderTestData.GetXmlReaderFor(testDataFile); using var reader = TestData.GetXmlReaderFor(testDataFile);
static Template ResolveTemplate(string source) static Template ResolveTemplate(string source)
{ {
using var xmlTemplateReader = TmxMapReaderTestData.GetXmlReaderFor($"Serialization.Tmx.TestData.Template.{source}"); using var xmlTemplateReader = TestData.GetXmlReaderFor($"Serialization.TestData.Template.{source}");
using var templateReader = new TxTemplateReader(xmlTemplateReader, ResolveTileset, ResolveTemplate); using var templateReader = new TxTemplateReader(xmlTemplateReader, ResolveTileset, ResolveTemplate);
return templateReader.ReadTemplate(); return templateReader.ReadTemplate();
} }
static Tileset ResolveTileset(string source) static Tileset ResolveTileset(string source)
{ {
using var xmlTilesetReader = TmxMapReaderTestData.GetXmlReaderFor($"Serialization.Tmx.TestData.Tileset.{source}"); using var xmlTilesetReader = TestData.GetXmlReaderFor($"Serialization.TestData.Tileset.{source}");
using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTemplate); using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTemplate);
return tilesetReader.ReadTileset(); return tilesetReader.ReadTileset();
} }
@ -116,8 +116,8 @@ public partial class TmxMapReaderTests
public static IEnumerable<object[]> DeserializeMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data => public static IEnumerable<object[]> DeserializeMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data =>
[ [
["Serialization.Tmx.TestData.Map.map-with-object-template.tmx", MapWithObjectTemplate()], ["Serialization.TestData.Map.map-with-object-template.tmx", TestData.MapWithObjectTemplate()],
["Serialization.Tmx.TestData.Map.map-with-group.tmx", MapWithGroup()], ["Serialization.TestData.Map.map-with-group.tmx", TestData.MapWithGroup()],
]; ];
[Theory] [Theory]
@ -125,16 +125,16 @@ public partial class TmxMapReaderTests
public void TmxMapReaderReadMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) public void TmxMapReaderReadMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap)
{ {
// Arrange // Arrange
using var reader = TmxMapReaderTestData.GetXmlReaderFor(testDataFile); using var reader = TestData.GetXmlReaderFor(testDataFile);
static Template ResolveTemplate(string source) static Template ResolveTemplate(string source)
{ {
using var xmlTemplateReader = TmxMapReaderTestData.GetXmlReaderFor($"Serialization.Tmx.TestData.Template.{source}"); using var xmlTemplateReader = TestData.GetXmlReaderFor($"Serialization.TestData.Template.{source}");
using var templateReader = new TxTemplateReader(xmlTemplateReader, ResolveTileset, ResolveTemplate); using var templateReader = new TxTemplateReader(xmlTemplateReader, ResolveTileset, ResolveTemplate);
return templateReader.ReadTemplate(); return templateReader.ReadTemplate();
} }
static Tileset ResolveTileset(string source) static Tileset ResolveTileset(string source)
{ {
using var xmlTilesetReader = TmxMapReaderTestData.GetXmlReaderFor($"Serialization.Tmx.TestData.Tileset.{source}"); using var xmlTilesetReader = TestData.GetXmlReaderFor($"Serialization.TestData.Tileset.{source}");
using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTemplate); using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTemplate);
return tilesetReader.ReadTileset(); return tilesetReader.ReadTileset();
} }

View file

@ -39,8 +39,8 @@ public class Tileset
public string Class { get; set; } = ""; public string Class { get; set; } = "";
public uint? TileWidth { get; set; } public uint? TileWidth { get; set; }
public uint? TileHeight { get; set; } public uint? TileHeight { get; set; }
public float? Spacing { get; set; } public float? Spacing { get; set; } = 0f;
public float? Margin { get; set; } public float? Margin { get; set; } = 0f;
public uint? TileCount { get; set; } public uint? TileCount { get; set; }
public uint? Columns { get; set; } public uint? Columns { get; set; }
public ObjectAlignment ObjectAlignment { get; set; } = ObjectAlignment.Unspecified; public ObjectAlignment ObjectAlignment { get; set; } = ObjectAlignment.Unspecified;

View file

@ -20,21 +20,32 @@ internal static class ExtensionsJsonElement
if (!element.TryGetProperty(propertyName, out var property)) if (!element.TryGetProperty(propertyName, out var property))
return defaultValue; return defaultValue;
if (property.ValueKind == JsonValueKind.Null)
return defaultValue;
return property.GetValueAs<T>(); return property.GetValueAs<T>();
} }
internal static T GetValueAs<T>(this JsonElement element) internal static T GetValueAs<T>(this JsonElement element)
{ {
string val = typeof(T) switch bool isNullable = Nullable.GetUnderlyingType(typeof(T)) != null;
if (isNullable && element.ValueKind == JsonValueKind.Null)
return default!;
var realType = isNullable ? Nullable.GetUnderlyingType(typeof(T))! : typeof(T);
string val = realType switch
{ {
Type t when t == typeof(string) => element.GetString()!, Type t when t == typeof(string) => element.GetString()!,
Type t when t == typeof(int) => element.GetInt32().ToString(CultureInfo.InvariantCulture), Type t when t == typeof(int) => element.GetInt32().ToString(CultureInfo.InvariantCulture),
Type t when t == typeof(uint) => element.GetUInt32().ToString(CultureInfo.InvariantCulture), Type t when t == typeof(uint) => element.GetUInt32().ToString(CultureInfo.InvariantCulture),
Type t when t == typeof(float) => element.GetSingle().ToString(CultureInfo.InvariantCulture), Type t when t == typeof(float) => element.GetSingle().ToString(CultureInfo.InvariantCulture),
Type t when t == typeof(bool) => element.GetBoolean().ToString(CultureInfo.InvariantCulture),
_ => throw new JsonException($"Unsupported type '{typeof(T)}'.") _ => throw new JsonException($"Unsupported type '{typeof(T)}'.")
}; };
return (T)Convert.ChangeType(val, typeof(T), CultureInfo.InvariantCulture); return (T)Convert.ChangeType(val, realType, CultureInfo.InvariantCulture);
} }
internal static T GetRequiredPropertyParseable<T>(this JsonElement element, string propertyName) where T : IParsable<T> internal static T GetRequiredPropertyParseable<T>(this JsonElement element, string propertyName) where T : IParsable<T>

View file

@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text.Json;
namespace DotTiled;
internal partial class Tmj
{
internal static Data ReadDataAsChunks(JsonElement element, DataCompression? compression, DataEncoding encoding)
{
var chunks = element.GetValueAsList<Chunk>(e => ReadChunk(e, compression, encoding)).ToArray();
return new Data
{
Chunks = chunks,
Compression = compression,
Encoding = encoding,
FlippingFlags = null,
GlobalTileIDs = null
};
}
internal static Chunk ReadChunk(JsonElement element, DataCompression? compression, DataEncoding encoding)
{
var data = ReadDataWithoutChunks(element, compression, encoding);
var x = element.GetRequiredProperty<int>("x");
var y = element.GetRequiredProperty<int>("y");
var width = element.GetRequiredProperty<uint>("width");
var height = element.GetRequiredProperty<uint>("height");
return new Chunk
{
X = x,
Y = y,
Width = width,
Height = height,
GlobalTileIDs = data.GlobalTileIDs!,
FlippingFlags = data.FlippingFlags!
};
}
internal static Data ReadDataWithoutChunks(JsonElement element, DataCompression? compression, DataEncoding encoding)
{
if (encoding == DataEncoding.Csv)
{
// Array of uint
var data = element.GetValueAsList<uint>(e => e.GetValueAs<uint>()).ToArray();
var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(data);
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags, Chunks = null };
}
else if (encoding == DataEncoding.Base64)
{
var base64Data = element.GetBytesFromBase64();
if (compression == null)
{
var data = ReadBytesAsInt32Array(base64Data);
var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(data);
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags, Chunks = null };
}
using var stream = new MemoryStream(base64Data);
var decompressed = compression switch
{
DataCompression.GZip => DecompressGZip(stream),
DataCompression.ZLib => DecompressZLib(stream),
_ => throw new JsonException($"Unsupported compression '{compression}'.")
};
{
var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(decompressed);
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags, Chunks = null };
}
}
throw new JsonException($"Unsupported encoding '{encoding}'.");
}
internal static uint[] ReadMemoryStreamAsInt32Array(Stream stream)
{
var finalValues = new List<uint>();
var int32Bytes = new byte[4];
while (stream.Read(int32Bytes, 0, 4) == 4)
{
var value = BitConverter.ToUInt32(int32Bytes, 0);
finalValues.Add(value);
}
return finalValues.ToArray();
}
internal static uint[] DecompressGZip(MemoryStream stream)
{
using var decompressedStream = new GZipStream(stream, CompressionMode.Decompress);
return ReadMemoryStreamAsInt32Array(decompressedStream);
}
internal static uint[] DecompressZLib(MemoryStream stream)
{
using var decompressedStream = new ZLibStream(stream, CompressionMode.Decompress);
return ReadMemoryStreamAsInt32Array(decompressedStream);
}
internal static uint[] ReadBytesAsInt32Array(byte[] bytes)
{
var intArray = new uint[bytes.Length / 4];
for (var i = 0; i < intArray.Length; i++)
{
intArray[i] = BitConverter.ToUInt32(bytes, i * 4);
}
return intArray;
}
internal static (uint[] GlobalTileIDs, FlippingFlags[] FlippingFlags) ReadAndClearFlippingFlagsFromGIDs(uint[] globalTileIDs)
{
var clearedGlobalTileIDs = new uint[globalTileIDs.Length];
var flippingFlags = new FlippingFlags[globalTileIDs.Length];
for (var i = 0; i < globalTileIDs.Length; i++)
{
var gid = globalTileIDs[i];
var flags = gid & 0xF0000000u;
flippingFlags[i] = (FlippingFlags)flags;
clearedGlobalTileIDs[i] = gid & 0x0FFFFFFFu;
}
return (clearedGlobalTileIDs, flippingFlags);
}
}

View file

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text.Json;
namespace DotTiled;
internal partial class Tmj
{
internal static BaseLayer ReadLayer(JsonElement element)
{
var type = element.GetRequiredProperty<string>("type");
return type switch
{
"tilelayer" => ReadTileLayer(element),
// "objectgroup" => ReadObjectGroup(element),
// "imagelayer" => ReadImageLayer(element),
// "group" => ReadGroup(element),
_ => throw new JsonException($"Unsupported layer type '{type}'.")
};
}
internal static TileLayer ReadTileLayer(JsonElement element)
{
var compression = element.GetOptionalPropertyParseable<DataCompression?>("compression", s => s switch
{
"zlib" => DataCompression.ZLib,
"gzip" => DataCompression.GZip,
"" => null,
_ => throw new JsonException($"Unsupported compression '{s}'.")
}, null);
var encoding = element.GetOptionalPropertyParseable<DataEncoding>("encoding", s => s switch
{
"csv" => DataEncoding.Csv,
"base64" => DataEncoding.Base64,
_ => throw new JsonException($"Unsupported encoding '{s}'.")
}, DataEncoding.Csv);
var chunks = element.GetOptionalPropertyCustom<Data?>("chunks", e => ReadDataAsChunks(e, compression, encoding), null);
var @class = element.GetOptionalProperty<string>("class", "");
var data = element.GetOptionalPropertyCustom<Data?>("data", e => ReadDataWithoutChunks(e, compression, encoding), null);
var height = element.GetRequiredProperty<uint>("height");
var id = element.GetRequiredProperty<uint>("id");
var name = element.GetRequiredProperty<string>("name");
var offsetX = element.GetOptionalProperty<float>("offsetx", 0.0f);
var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f);
var opacity = element.GetOptionalProperty<float>("opacity", 1.0f);
var parallaxx = element.GetOptionalProperty<float>("parallaxx", 1.0f);
var parallaxy = element.GetOptionalProperty<float>("parallaxy", 1.0f);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e), null);
var repeatX = element.GetOptionalProperty<bool>("repeatx", false);
var repeatY = element.GetOptionalProperty<bool>("repeaty", false);
var startX = element.GetOptionalProperty<int>("startx", 0);
var startY = element.GetOptionalProperty<int>("starty", 0);
var tintColor = element.GetOptionalPropertyParseable<Color?>("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
var transparentColor = element.GetOptionalPropertyParseable<Color?>("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
var visible = element.GetOptionalProperty<bool>("visible", true);
var width = element.GetRequiredProperty<uint>("width");
var x = element.GetRequiredProperty<uint>("x");
var y = element.GetRequiredProperty<uint>("y");
if ((data ?? chunks) is null)
throw new JsonException("Tile layer does not contain data.");
return new TileLayer
{
ID = id,
Name = name,
Class = @class,
Opacity = opacity,
Visible = visible,
TintColor = tintColor,
OffsetX = offsetX,
OffsetY = offsetY,
ParallaxX = parallaxx,
ParallaxY = parallaxy,
Properties = properties,
X = x,
Y = y,
Width = width,
Height = height,
Data = data ?? chunks
};
}
}

View file

@ -1,13 +1,15 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq;
using System.Text.Json; using System.Text.Json;
namespace DotTiled; namespace DotTiled;
internal partial class Tmj internal partial class Tmj
{ {
internal static Map ReadMap(JsonElement element) internal static Map ReadMap(JsonElement element, Func<string, Tileset>? externalTilesetResolver, Func<string, Template> externalTemplateResolver)
{ {
var version = element.GetRequiredProperty<string>("version"); var version = element.GetRequiredProperty<string>("version");
var tiledVersion = element.GetRequiredProperty<string>("tiledversion"); var tiledVersion = element.GetRequiredProperty<string>("tiledversion");
@ -51,14 +53,12 @@ internal partial class Tmj
var backgroundColor = element.GetOptionalPropertyParseable<Color>("backgroundcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), Color.Parse("#00000000", CultureInfo.InvariantCulture)); var backgroundColor = element.GetOptionalPropertyParseable<Color>("backgroundcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), Color.Parse("#00000000", CultureInfo.InvariantCulture));
var nextLayerID = element.GetRequiredProperty<uint>("nextlayerid"); var nextLayerID = element.GetRequiredProperty<uint>("nextlayerid");
var nextObjectID = element.GetRequiredProperty<uint>("nextobjectid"); var nextObjectID = element.GetRequiredProperty<uint>("nextobjectid");
var infinite = element.GetOptionalProperty<int>("infinite", 0) == 1; var infinite = element.GetOptionalProperty<bool>("infinite", false);
// At most one of var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", ReadProperties, null);
Dictionary<string, IProperty>? properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>>("properties", ReadProperties, null);
// Any number of List<BaseLayer> layers = element.GetOptionalPropertyCustom<List<BaseLayer>>("layers", e => e.GetValueAsList<BaseLayer>(ReadLayer), []);
List<BaseLayer> layers = []; List<Tileset> tilesets = element.GetOptionalPropertyCustom<List<Tileset>>("tilesets", e => e.GetValueAsList<Tileset>(el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver)), []);
List<Tileset> tilesets = [];
return new Map return new Map
{ {
@ -86,176 +86,4 @@ internal partial class Tmj
Layers = layers Layers = layers
}; };
} }
internal static Dictionary<string, IProperty> ReadProperties(JsonElement element)
{
var properties = new Dictionary<string, IProperty>();
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),
_ => throw new JsonException("Invalid property type")
};
return property!;
}).ForEach(p => properties.Add(p.Name, p));
return properties;
}
internal static ClassProperty ReadClassProperty(JsonElement element)
{
var name = element.GetRequiredProperty<string>("name");
var propertyType = element.GetRequiredProperty<string>("propertytype");
var properties = element.GetRequiredPropertyCustom<Dictionary<string, IProperty>>("properties", ReadProperties);
return new ClassProperty { Name = name, PropertyType = propertyType, Properties = properties };
}
// internal static List<Tileset> ReadTilesets(ref Utf8JsonReader reader)
// {
// var tilesets = new List<Tileset>();
// reader.ProcessJsonArray((ref Utf8JsonReader reader) =>
// {
// var tileset = ReadTileset(ref reader);
// tilesets.Add(tileset);
// });
// return tilesets;
// }
// internal static Tileset ReadTileset(ref Utf8JsonReader reader)
// {
// string? version = null;
// string? tiledVersion = null;
// uint? firstGID = null;
// string? source = null;
// string? name = null;
// string @class = "";
// uint? tileWidth = null;
// uint? tileHeight = null;
// uint? spacing = null;
// uint? margin = null;
// uint? tileCount = null;
// uint? columns = null;
// ObjectAlignment objectAlignment = ObjectAlignment.Unspecified;
// FillMode fillMode = FillMode.Stretch;
// string? image = null;
// uint? imageWidth = null;
// uint? imageHeight = null;
// Dictionary<string, IProperty>? properties = null;
// reader.ProcessJsonObject([
// new OptionalProperty("version", (ref Utf8JsonReader reader) => version = reader.Progress(reader.GetString())),
// new OptionalProperty("tiledversion", (ref Utf8JsonReader reader) => tiledVersion = reader.Progress(reader.GetString())),
// new OptionalProperty("firstgid", (ref Utf8JsonReader reader) => firstGID = reader.Progress(reader.GetUInt32())),
// new OptionalProperty("source", (ref Utf8JsonReader reader) => source = reader.Progress(reader.GetString())),
// new OptionalProperty("name", (ref Utf8JsonReader reader) => name = reader.Progress(reader.GetString())),
// new OptionalProperty("class", (ref Utf8JsonReader reader) => @class = reader.Progress(reader.GetString() ?? ""), allowNull: true),
// new OptionalProperty("tilewidth", (ref Utf8JsonReader reader) => tileWidth = reader.Progress(reader.GetUInt32())),
// new OptionalProperty("tileheight", (ref Utf8JsonReader reader) => tileHeight = reader.Progress(reader.GetUInt32())),
// new OptionalProperty("spacing", (ref Utf8JsonReader reader) => spacing = reader.Progress(reader.GetUInt32())),
// new OptionalProperty("margin", (ref Utf8JsonReader reader) => margin = reader.Progress(reader.GetUInt32())),
// new OptionalProperty("tilecount", (ref Utf8JsonReader reader) => tileCount = reader.Progress(reader.GetUInt32())),
// new OptionalProperty("columns", (ref Utf8JsonReader reader) => columns = reader.Progress(reader.GetUInt32())),
// new OptionalProperty("objectalignment", (ref Utf8JsonReader reader) => objectAlignment = reader.Progress(reader.GetString()) switch
// {
// "unspecified" => ObjectAlignment.Unspecified,
// "topleft" => ObjectAlignment.TopLeft,
// "top" => ObjectAlignment.Top,
// "topright" => ObjectAlignment.TopRight,
// "left" => ObjectAlignment.Left,
// "center" => ObjectAlignment.Center,
// "right" => ObjectAlignment.Right,
// "bottomleft" => ObjectAlignment.BottomLeft,
// "bottom" => ObjectAlignment.Bottom,
// "bottomright" => ObjectAlignment.BottomRight,
// _ => throw new JsonException("Invalid object alignment.")
// }),
// new OptionalProperty("fillmode", (ref Utf8JsonReader reader) => fillMode = reader.Progress(reader.GetString()) switch
// {
// "stretch" => FillMode.Stretch,
// "preserve-aspect-fit" => FillMode.PreserveAspectFit,
// _ => throw new JsonException("Invalid fill mode.")
// }),
// new OptionalProperty("image", (ref Utf8JsonReader reader) => image = reader.Progress(reader.GetString())),
// new OptionalProperty("imagewidth", (ref Utf8JsonReader reader) => imageWidth = reader.Progress(reader.GetUInt32())),
// new OptionalProperty("imageheight", (ref Utf8JsonReader reader) => imageHeight = reader.Progress(reader.GetUInt32())),
// new OptionalProperty("properties", (ref Utf8JsonReader reader) => properties = ReadProperties(ref reader))
// ], "tileset");
// Image? imageInstance = image is not null ? new Image
// {
// Format = ParseImageFormatFromSource(image),
// Width = imageWidth,
// Height = imageHeight,
// Source = image
// } : null;
// return new Tileset
// {
// Version = version,
// TiledVersion = tiledVersion,
// FirstGID = firstGID,
// Source = source,
// Name = name,
// Class = @class,
// TileWidth = tileWidth,
// TileHeight = tileHeight,
// Spacing = spacing,
// Margin = margin,
// TileCount = tileCount,
// Columns = columns,
// ObjectAlignment = objectAlignment,
// FillMode = fillMode,
// Image = imageInstance,
// Properties = properties
// };
// }
// private static ImageFormat ParseImageFormatFromSource(string? source)
// {
// if (source is null)
// throw new JsonException("Image source is required to determine image format.");
// var extension = Path.GetExtension(source);
// return extension switch
// {
// ".png" => ImageFormat.Png,
// ".jpg" => ImageFormat.Jpg,
// ".jpeg" => ImageFormat.Jpg,
// ".gif" => ImageFormat.Gif,
// ".bmp" => ImageFormat.Bmp,
// _ => throw new JsonException("Invalid image format.")
// };
// }
} }

View file

@ -0,0 +1,53 @@
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json;
namespace DotTiled;
internal partial class Tmj
{
internal static Dictionary<string, 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);
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),
_ => throw new JsonException("Invalid property type")
};
return property!;
}).ToDictionary(p => p.Name);
internal static ClassProperty ReadClassProperty(JsonElement element)
{
var name = element.GetRequiredProperty<string>("name");
var propertyType = element.GetRequiredProperty<string>("propertytype");
var properties = element.GetRequiredPropertyCustom<Dictionary<string, IProperty>>("properties", ReadProperties);
return new ClassProperty { Name = name, PropertyType = propertyType, Properties = properties };
}
}

View file

@ -0,0 +1,197 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text.Json;
namespace DotTiled;
internal partial class Tmj
{
internal static Tileset ReadTileset(JsonElement element, Func<string, Tileset>? externalTilesetResolver, Func<string, Template> externalTemplateResolver)
{
var backgroundColor = element.GetOptionalPropertyParseable<Color?>("backgroundcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
var @class = element.GetOptionalProperty<string>("class", "");
var columns = element.GetOptionalProperty<uint?>("columns", null);
var fillMode = element.GetOptionalPropertyParseable<FillMode>("fillmode", s => s switch
{
"stretch" => FillMode.Stretch,
"preserve-aspect-fit" => FillMode.PreserveAspectFit,
_ => throw new JsonException($"Unknown fill mode '{s}'")
}, FillMode.Stretch);
var firstGID = element.GetOptionalProperty<uint?>("firstgid", null);
var grid = element.GetOptionalPropertyCustom<Grid?>("grid", ReadGrid, null);
var image = element.GetOptionalProperty<string?>("image", null);
var imageHeight = element.GetOptionalProperty<uint?>("imageheight", null);
var imageWidth = element.GetOptionalProperty<uint?>("imagewidth", null);
var margin = element.GetOptionalProperty<uint?>("margin", null);
var name = element.GetOptionalProperty<string?>("name", null);
var objectAlignment = element.GetOptionalPropertyParseable<ObjectAlignment>("objectalignment", s => s switch
{
"unspecified" => ObjectAlignment.Unspecified,
"topleft" => ObjectAlignment.TopLeft,
"top" => ObjectAlignment.Top,
"topright" => ObjectAlignment.TopRight,
"left" => ObjectAlignment.Left,
"center" => ObjectAlignment.Center,
"right" => ObjectAlignment.Right,
"bottomleft" => ObjectAlignment.BottomLeft,
"bottom" => ObjectAlignment.Bottom,
"bottomright" => ObjectAlignment.BottomRight,
_ => throw new JsonException($"Unknown object alignment '{s}'")
}, ObjectAlignment.Unspecified);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", ReadProperties, null);
var source = element.GetOptionalProperty<string?>("source", null);
var spacing = element.GetOptionalProperty<uint?>("spacing", null);
var tileCount = element.GetOptionalProperty<uint?>("tilecount", null);
var tiledVersion = element.GetOptionalProperty<string?>("tiledversion", null);
var tileHeight = element.GetOptionalProperty<uint?>("tileheight", null);
var tileOffset = element.GetOptionalPropertyCustom<TileOffset?>("tileoffset", ReadTileOffset, null);
var tileRenderSize = element.GetOptionalPropertyParseable<TileRenderSize>("tilerendersize", s => s switch
{
"tile" => TileRenderSize.Tile,
"grid" => TileRenderSize.Grid,
_ => throw new JsonException($"Unknown tile render size '{s}'")
}, TileRenderSize.Tile);
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 wangsets = element.GetOptionalPropertyCustom<List<Wangset>?>("wangsets", ReadWangSets, 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);
resolvedTileset.FirstGID = firstGID;
resolvedTileset.Source = source;
return resolvedTileset;
}
var imageModel = new Image
{
Format = ParseImageFormatFromSource(image!),
Source = image,
Height = imageHeight,
Width = imageWidth,
TransparentColor = transparentColor
};
return new Tileset
{
Class = @class,
Columns = columns,
FillMode = fillMode,
FirstGID = firstGID,
Grid = grid,
Image = imageModel,
Margin = margin,
Name = name,
ObjectAlignment = objectAlignment,
Properties = properties,
Source = source,
Spacing = spacing,
TileCount = tileCount,
TiledVersion = tiledVersion,
TileHeight = tileHeight,
TileOffset = tileOffset,
RenderSize = tileRenderSize,
Tiles = tiles,
TileWidth = tileWidth,
Version = version,
//Wangsets = wangsets
};
}
private static ImageFormat ParseImageFormatFromSource(string source)
{
var extension = Path.GetExtension(source).ToLowerInvariant();
return extension switch
{
".png" => ImageFormat.Png,
".gif" => ImageFormat.Gif,
".jpg" => ImageFormat.Jpg,
".jpeg" => ImageFormat.Jpg,
".bmp" => ImageFormat.Bmp,
_ => throw new JsonException($"Unsupported image format '{extension}'")
};
}
internal static Grid ReadGrid(JsonElement element)
{
var orientation = element.GetOptionalPropertyParseable<GridOrientation>("orientation", s => s switch
{
"orthogonal" => GridOrientation.Orthogonal,
"isometric" => GridOrientation.Isometric,
_ => throw new JsonException($"Unknown grid orientation '{s}'")
}, GridOrientation.Orthogonal);
var height = element.GetRequiredProperty<uint>("height");
var width = element.GetRequiredProperty<uint>("width");
return new Grid
{
Orientation = orientation,
Height = height,
Width = width
};
}
internal static TileOffset ReadTileOffset(JsonElement element)
{
var x = element.GetRequiredProperty<int>("x");
var y = element.GetRequiredProperty<int>("y");
return new TileOffset
{
X = x,
Y = y
};
}
internal static List<Tile> ReadTiles(JsonElement element) =>
element.GetValueAsList<Tile>(e =>
{
//var animation = e.GetOptionalPropertyCustom<List<Frame>>("animation", ReadFrames, null);
var id = e.GetRequiredProperty<uint>("id");
var image = e.GetOptionalProperty<string?>("image", null);
var imageHeight = e.GetOptionalProperty<uint?>("imageheight", null);
var imageWidth = e.GetOptionalProperty<uint?>("imagewidth", null);
var x = e.GetOptionalProperty<uint>("x", 0);
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", ReadObjectLayer, null);
var probability = e.GetOptionalProperty<float>("probability", 1.0f);
var properties = e.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", ReadProperties, null);
// var terrain, replaced by wangsets
var type = e.GetOptionalProperty<string>("type", "");
var imageModel = image != null ? new Image
{
Format = ParseImageFormatFromSource(image),
Source = image,
Height = imageHeight ?? 0,
Width = imageWidth ?? 0
} : null;
return new Tile
{
//Animation = animation,
ID = id,
Image = imageModel,
X = x,
Y = y,
Width = width,
Height = height,
//ObjectLayer = objectGroup,
Probability = probability,
Properties = properties,
Type = type
};
});
}

View file

@ -7,19 +7,25 @@ namespace DotTiled;
public class TmjMapReader : IMapReader public class TmjMapReader : IMapReader
{ {
// External resolvers
private readonly Func<string, Tileset> _externalTilesetResolver;
private readonly Func<string, Template> _externalTemplateResolver;
private string _jsonString; private string _jsonString;
private bool disposedValue; private bool disposedValue;
public TmjMapReader(string jsonString) public TmjMapReader(string jsonString, Func<string, Tileset> externalTilesetResolver, Func<string, Template> externalTemplateResolver)
{ {
_jsonString = jsonString ?? throw new ArgumentNullException(nameof(jsonString)); _jsonString = jsonString ?? throw new ArgumentNullException(nameof(jsonString));
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
} }
public Map ReadMap() public Map ReadMap()
{ {
var jsonDoc = JsonDocument.Parse(_jsonString); var jsonDoc = JsonDocument.Parse(_jsonString);
var rootElement = jsonDoc.RootElement; var rootElement = jsonDoc.RootElement;
return Tmj.ReadMap(rootElement); return Tmj.ReadMap(rootElement, _externalTilesetResolver, _externalTemplateResolver);
} }
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Xml; using System.Xml;
@ -18,8 +19,8 @@ internal partial class Tmx
var @class = reader.GetOptionalAttribute("class") ?? ""; var @class = reader.GetOptionalAttribute("class") ?? "";
var tileWidth = reader.GetOptionalAttributeParseable<uint>("tilewidth"); var tileWidth = reader.GetOptionalAttributeParseable<uint>("tilewidth");
var tileHeight = reader.GetOptionalAttributeParseable<uint>("tileheight"); var tileHeight = reader.GetOptionalAttributeParseable<uint>("tileheight");
var spacing = reader.GetOptionalAttributeParseable<uint>("spacing"); var spacing = reader.GetOptionalAttributeParseable<uint>("spacing") ?? 0;
var margin = reader.GetOptionalAttributeParseable<uint>("margin"); var margin = reader.GetOptionalAttributeParseable<uint>("margin") ?? 0;
var tileCount = reader.GetOptionalAttributeParseable<uint>("tilecount"); var tileCount = reader.GetOptionalAttributeParseable<uint>("tilecount");
var columns = reader.GetOptionalAttributeParseable<uint>("columns"); var columns = reader.GetOptionalAttributeParseable<uint>("columns");
var objectAlignment = reader.GetOptionalAttributeEnum<ObjectAlignment>("objectalignment", s => s switch var objectAlignment = reader.GetOptionalAttributeEnum<ObjectAlignment>("objectalignment", s => s switch
@ -131,6 +132,9 @@ internal partial class Tmx
_ => r.Skip _ => r.Skip
}); });
if (format is null && source is not null)
format = ParseImageFormatFromSource(source);
return new Image return new Image
{ {
Format = format, Format = format,
@ -141,6 +145,21 @@ internal partial class Tmx
}; };
} }
private static ImageFormat ParseImageFormatFromSource(string source)
{
var extension = Path.GetExtension(source).ToLowerInvariant();
return extension switch
{
".png" => ImageFormat.Png,
".gif" => ImageFormat.Gif,
".jpg" => ImageFormat.Jpg,
".jpeg" => ImageFormat.Jpg,
".bmp" => ImageFormat.Bmp,
_ => throw new XmlException($"Unsupported image format '{extension}'")
};
}
internal static TileOffset ReadTileOffset(XmlReader reader) internal static TileOffset ReadTileOffset(XmlReader reader)
{ {
// Attributes // Attributes

View file

@ -0,0 +1,9 @@
test:
dotnet test
.PHONY: benchmark
benchmark: DotTiled.Benchmark/BenchmarkDotNet.Artifacts/results/MyBenchmarks.MapLoading-report-github.md
BENCHMARK_SOURCES = DotTiled.Benchmark/Program.cs
DotTiled.Benchmark/BenchmarkDotNet.Artifacts/results/MyBenchmarks.MapLoading-report-github.md: $(BENCHMARK_SOURCES)
dotnet run --project DotTiled.Benchmark/DotTiled.Benchmark.csproj -c Release