From 1168917c23da9b3d8af657b61d757db1de5208f7 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Fri, 9 Aug 2024 23:12:45 +0200 Subject: [PATCH] Getting further with the json support --- DotTiled.Tests/Assert/AssertMap.cs | 4 +- DotTiled.Tests/DotTiled.Tests.csproj | 4 +- .../{Tmx/TestData => }/TestData.cs | 6 +- .../TestData/Map/empty-map-base64-gzip.tmj | 30 +++ .../TestData/Map/empty-map-base64-gzip.tmx | 0 .../TestData/Map/empty-map-base64-zlib.tmj | 30 +++ .../TestData/Map/empty-map-base64-zlib.tmx | 0 .../TestData/Map/empty-map-base64.tmj | 30 +++ .../TestData/Map/empty-map-base64.tmx | 0 .../TestData/Map/empty-map-csv.tmj | 32 +++ .../{Tmx => }/TestData/Map/empty-map-csv.tmx | 0 .../TestData/Map/empty-map-properties.cs | 4 +- .../TestData/Map/empty-map-properties.tmj | 69 ++++++ .../TestData/Map/empty-map-properties.tmx | 0 .../{Tmx => }/TestData/Map/empty-map.cs | 4 +- .../{Tmx => }/TestData/Map/map-with-group.cs | 4 +- .../TestData/Map/map-with-group.tmj | 80 +++++++ .../{Tmx => }/TestData/Map/map-with-group.tmx | 0 .../TestData/Map/map-with-object-template.cs | 4 +- .../TestData/Map/map-with-object-template.tmj | 104 +++++++++ .../TestData/Map/map-with-object-template.tmx | 0 .../TestData/Map/simple-tileset-embed.cs | 5 +- .../TestData/Map/simple-tileset-embed.tmj | 45 ++++ .../TestData/Map/simple-tileset-embed.tmx | 0 .../Template/map-with-object-template.tj | 28 +++ .../Template/map-with-object-template.tx | 0 .../Serialization/Tmj/TmjMapReaderTests.cs | 113 +++++----- .../Serialization/Tmx/TmxMapReaderTests.cs | 28 +-- DotTiled/Model/Tileset/Tileset.cs | 4 +- .../Tmj/ExtensionsJsonElement.cs | 15 +- DotTiled/Serialization/Tmj/Tmj.Data.cs | 132 ++++++++++++ DotTiled/Serialization/Tmj/Tmj.Layer.cs | 88 ++++++++ DotTiled/Serialization/Tmj/Tmj.Map.cs | 186 +---------------- DotTiled/Serialization/Tmj/Tmj.Properties.cs | 53 +++++ DotTiled/Serialization/Tmj/Tmj.Tileset.cs | 197 ++++++++++++++++++ DotTiled/Serialization/Tmj/TmjMapReader.cs | 10 +- DotTiled/Serialization/Tmx/Tmx.Tileset.cs | 23 +- Makefile | 9 + 38 files changed, 1072 insertions(+), 269 deletions(-) rename DotTiled.Tests/Serialization/{Tmx/TestData => }/TestData.cs (75%) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-gzip.tmj rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/empty-map-base64-gzip.tmx (100%) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-zlib.tmj rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/empty-map-base64-zlib.tmx (100%) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/empty-map-base64.tmj rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/empty-map-base64.tmx (100%) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/empty-map-csv.tmj rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/empty-map-csv.tmx (100%) rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/empty-map-properties.cs (95%) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.tmj rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/empty-map-properties.tmx (100%) rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/empty-map.cs (89%) rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/map-with-group.cs (97%) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-group.tmj rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/map-with-group.tmx (100%) rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/map-with-object-template.cs (97%) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.tmj rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/map-with-object-template.tmx (100%) rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/simple-tileset-embed.cs (92%) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.tmj rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/simple-tileset-embed.tmx (100%) create mode 100644 DotTiled.Tests/Serialization/TestData/Template/map-with-object-template.tj rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Template/map-with-object-template.tx (100%) create mode 100644 DotTiled/Serialization/Tmj/Tmj.Data.cs create mode 100644 DotTiled/Serialization/Tmj/Tmj.Layer.cs create mode 100644 DotTiled/Serialization/Tmj/Tmj.Properties.cs create mode 100644 DotTiled/Serialization/Tmj/Tmj.Tileset.cs diff --git a/DotTiled.Tests/Assert/AssertMap.cs b/DotTiled.Tests/Assert/AssertMap.cs index 167d0ad..e831063 100644 --- a/DotTiled.Tests/Assert/AssertMap.cs +++ b/DotTiled.Tests/Assert/AssertMap.cs @@ -30,11 +30,11 @@ public static partial class DotTiledAssert Assert.NotNull(actual.Tilesets); Assert.Equal(expected.Tilesets.Count, actual.Tilesets.Count); 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.Equal(expected.Layers.Count, actual.Layers.Count); for (var i = 0; i < expected.Layers.Count; i++) - AssertLayer(actual.Layers[i], expected.Layers[i]); + AssertLayer(expected.Layers[i], actual.Layers[i]); } } diff --git a/DotTiled.Tests/DotTiled.Tests.csproj b/DotTiled.Tests/DotTiled.Tests.csproj index 5e36559..faa22d4 100644 --- a/DotTiled.Tests/DotTiled.Tests.csproj +++ b/DotTiled.Tests/DotTiled.Tests.csproj @@ -25,8 +25,8 @@ - - + + diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/TestData.cs b/DotTiled.Tests/Serialization/TestData.cs similarity index 75% rename from DotTiled.Tests/Serialization/Tmx/TestData/TestData.cs rename to DotTiled.Tests/Serialization/TestData.cs index 667fec0..d31956f 100644 --- a/DotTiled.Tests/Serialization/Tmx/TestData/TestData.cs +++ b/DotTiled.Tests/Serialization/TestData.cs @@ -2,12 +2,12 @@ using System.Xml; namespace DotTiled.Tests; -public static class TmxMapReaderTestData +public static partial class TestData { public static XmlReader GetXmlReaderFor(string 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"); using var stringReader = new StreamReader(stream); @@ -19,7 +19,7 @@ public static class TmxMapReaderTestData public static string GetRawStringFor(string 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"); using var stringReader = new StreamReader(stream); diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-gzip.tmj b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-gzip.tmj new file mode 100644 index 0000000..de94421 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-gzip.tmj @@ -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 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-base64-gzip.tmx b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-gzip.tmx similarity index 100% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-base64-gzip.tmx rename to DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-gzip.tmx diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-zlib.tmj b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-zlib.tmj new file mode 100644 index 0000000..4cf9e84 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-zlib.tmj @@ -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 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-base64-zlib.tmx b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-zlib.tmx similarity index 100% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-base64-zlib.tmx rename to DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-zlib.tmx diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64.tmj b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64.tmj new file mode 100644 index 0000000..b13707c --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64.tmj @@ -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 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-base64.tmx b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64.tmx similarity index 100% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-base64.tmx rename to DotTiled.Tests/Serialization/TestData/Map/empty-map-base64.tmx diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map-csv.tmj b/DotTiled.Tests/Serialization/TestData/Map/empty-map-csv.tmj new file mode 100644 index 0000000..896df6c --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/empty-map-csv.tmj @@ -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 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-csv.tmx b/DotTiled.Tests/Serialization/TestData/Map/empty-map-csv.tmx similarity index 100% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-csv.tmx rename to DotTiled.Tests/Serialization/TestData/Map/empty-map-csv.tmx diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-properties.cs b/DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.cs similarity index 95% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-properties.cs rename to DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.cs index 795b920..79df5a5 100644 --- a/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-properties.cs +++ b/DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.cs @@ -1,8 +1,8 @@ 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", TiledVersion = "1.11.0", diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.tmj b/DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.tmj new file mode 100644 index 0000000..237546d --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.tmj @@ -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 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-properties.tmx b/DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.tmx similarity index 100% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-properties.tmx rename to DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.tmx diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map.cs b/DotTiled.Tests/Serialization/TestData/Map/empty-map.cs similarity index 89% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map.cs rename to DotTiled.Tests/Serialization/TestData/Map/empty-map.cs index 12cfc00..b51aeba 100644 --- a/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map.cs +++ b/DotTiled.Tests/Serialization/TestData/Map/empty-map.cs @@ -1,8 +1,8 @@ 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", TiledVersion = "1.11.0", diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-group.cs b/DotTiled.Tests/Serialization/TestData/Map/map-with-group.cs similarity index 97% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-group.cs rename to DotTiled.Tests/Serialization/TestData/Map/map-with-group.cs index 515312e..14a2c8c 100644 --- a/DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-group.cs +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-group.cs @@ -1,8 +1,8 @@ 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", TiledVersion = "1.11.0", diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-group.tmj b/DotTiled.Tests/Serialization/TestData/Map/map-with-group.tmj new file mode 100644 index 0000000..6bce8c8 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-group.tmj @@ -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 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-group.tmx b/DotTiled.Tests/Serialization/TestData/Map/map-with-group.tmx similarity index 100% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-group.tmx rename to DotTiled.Tests/Serialization/TestData/Map/map-with-group.tmx diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-object-template.cs b/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.cs similarity index 97% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-object-template.cs rename to DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.cs index 8f4459e..26ddde8 100644 --- a/DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-object-template.cs +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.cs @@ -1,8 +1,8 @@ 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", TiledVersion = "1.11.0", diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.tmj b/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.tmj new file mode 100644 index 0000000..398403b --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.tmj @@ -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 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-object-template.tmx b/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.tmx similarity index 100% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-object-template.tmx rename to DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.tmx diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/simple-tileset-embed.cs b/DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.cs similarity index 92% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/simple-tileset-embed.cs rename to DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.cs index 7dfb740..d6a5f10 100644 --- a/DotTiled.Tests/Serialization/Tmx/TestData/Map/simple-tileset-embed.cs +++ b/DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.cs @@ -1,8 +1,8 @@ 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", TiledVersion = "1.11.0", @@ -26,6 +26,7 @@ public partial class TmxMapReaderTests Columns = 4, Image = new Image { + Format = ImageFormat.Png, Source = "tiles.png", Width = 128, Height = 64 diff --git a/DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.tmj b/DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.tmj new file mode 100644 index 0000000..fa5a4ef --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.tmj @@ -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 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/simple-tileset-embed.tmx b/DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.tmx similarity index 100% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/simple-tileset-embed.tmx rename to DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.tmx diff --git a/DotTiled.Tests/Serialization/TestData/Template/map-with-object-template.tj b/DotTiled.Tests/Serialization/TestData/Template/map-with-object-template.tj new file mode 100644 index 0000000..ec2b065 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Template/map-with-object-template.tj @@ -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" +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Template/map-with-object-template.tx b/DotTiled.Tests/Serialization/TestData/Template/map-with-object-template.tx similarity index 100% rename from DotTiled.Tests/Serialization/Tmx/TestData/Template/map-with-object-template.tx rename to DotTiled.Tests/Serialization/TestData/Template/map-with-object-template.tx diff --git a/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs b/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs index 7e15bac..605cda9 100644 --- a/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs +++ b/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs @@ -2,64 +2,75 @@ namespace DotTiled.Tests; public partial class TmjMapReaderTests { - [Fact] - public void Test1() + public static IEnumerable DeserializeMap_ValidTmjNoExternalTilesets_ReturnsMapWithoutThrowing_Data => + [ + ["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 - var jsonString = - """ + var json = TestData.GetRawStringFor(testDataFile); + static Template ResolveTemplate(string source) { - "backgroundcolor":"#656667", - "height":4, - "nextobjectid":1, - "nextlayerid":1, - "orientation":"orthogonal", - "properties": [ - { - "name":"mapProperty1", - "type":"string", - "value":"one" - }, - { - "name":"mapProperty3", - "type":"string", - "value":"twoeee" - } - ], - "renderorder":"right-down", - "tileheight":32, - "tilewidth":32, - "version":"1", - "tiledversion":"1.0.3", - "width":4, - "tilesets": [ - { - "columns":19, - "firstgid":1, - "image":"image/fishbaddie_parts.png", - "imageheight":480, - "imagewidth":640, - "margin":3, - "name":"", - "properties":[ - { - "name":"myProperty1", - "type":"string", - "value":"myProperty1_value" - }], - "spacing":1, - "tilecount":266, - "tileheight":32, - "tilewidth":32 - } - ] + 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 - using var tmjMapReader = new TmjMapReader(jsonString); + var map = mapReader.ReadMap(); // Assert - var map = tmjMapReader.ReadMap(); + Assert.NotNull(map); + DotTiledAssert.AssertMap(expectedMap, map); + } + + public static IEnumerable 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); } } diff --git a/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs b/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs index 1924ac2..7a1fc66 100644 --- a/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs +++ b/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs @@ -78,12 +78,12 @@ public partial class TmxMapReaderTests public static IEnumerable DeserializeMap_ValidXmlNoExternalTilesets_ReturnsMapWithoutThrowing_Data => [ - ["Serialization.Tmx.TestData.Map.empty-map-csv.tmx", EmptyMapWithEncodingAndCompression(DataEncoding.Csv, null)], - ["Serialization.Tmx.TestData.Map.empty-map-base64.tmx", EmptyMapWithEncodingAndCompression(DataEncoding.Base64, null)], - ["Serialization.Tmx.TestData.Map.empty-map-base64-gzip.tmx", EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.GZip)], - ["Serialization.Tmx.TestData.Map.empty-map-base64-zlib.tmx", EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.ZLib)], - ["Serialization.Tmx.TestData.Map.simple-tileset-embed.tmx", SimpleMapWithEmbeddedTileset()], - ["Serialization.Tmx.TestData.Map.empty-map-properties.tmx", EmptyMapWithProperties()], + ["Serialization.TestData.Map.empty-map-csv.tmx", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Csv, null)], + ["Serialization.TestData.Map.empty-map-base64.tmx", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Base64, null)], + ["Serialization.TestData.Map.empty-map-base64-gzip.tmx", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.GZip)], + ["Serialization.TestData.Map.empty-map-base64-zlib.tmx", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.ZLib)], + ["Serialization.TestData.Map.simple-tileset-embed.tmx", TestData.SimpleMapWithEmbeddedTileset()], + ["Serialization.TestData.Map.empty-map-properties.tmx", TestData.EmptyMapWithProperties()], ]; [Theory] @@ -91,16 +91,16 @@ public partial class TmxMapReaderTests public void TmxMapReaderReadMap_ValidXmlNoExternalTilesets_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) { // Arrange - using var reader = TmxMapReaderTestData.GetXmlReaderFor(testDataFile); + using var reader = TestData.GetXmlReaderFor(testDataFile); 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); return templateReader.ReadTemplate(); } 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); return tilesetReader.ReadTileset(); } @@ -116,8 +116,8 @@ public partial class TmxMapReaderTests public static IEnumerable DeserializeMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data => [ - ["Serialization.Tmx.TestData.Map.map-with-object-template.tmx", MapWithObjectTemplate()], - ["Serialization.Tmx.TestData.Map.map-with-group.tmx", MapWithGroup()], + ["Serialization.TestData.Map.map-with-object-template.tmx", TestData.MapWithObjectTemplate()], + ["Serialization.TestData.Map.map-with-group.tmx", TestData.MapWithGroup()], ]; [Theory] @@ -125,16 +125,16 @@ public partial class TmxMapReaderTests public void TmxMapReaderReadMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) { // Arrange - using var reader = TmxMapReaderTestData.GetXmlReaderFor(testDataFile); + using var reader = TestData.GetXmlReaderFor(testDataFile); 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); return templateReader.ReadTemplate(); } 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); return tilesetReader.ReadTileset(); } diff --git a/DotTiled/Model/Tileset/Tileset.cs b/DotTiled/Model/Tileset/Tileset.cs index ac1da0d..7b1a982 100644 --- a/DotTiled/Model/Tileset/Tileset.cs +++ b/DotTiled/Model/Tileset/Tileset.cs @@ -39,8 +39,8 @@ public class Tileset public string Class { get; set; } = ""; public uint? TileWidth { get; set; } public uint? TileHeight { get; set; } - public float? Spacing { get; set; } - public float? Margin { get; set; } + public float? Spacing { get; set; } = 0f; + public float? Margin { get; set; } = 0f; public uint? TileCount { get; set; } public uint? Columns { get; set; } public ObjectAlignment ObjectAlignment { get; set; } = ObjectAlignment.Unspecified; diff --git a/DotTiled/Serialization/Tmj/ExtensionsJsonElement.cs b/DotTiled/Serialization/Tmj/ExtensionsJsonElement.cs index d7620e4..7462c56 100644 --- a/DotTiled/Serialization/Tmj/ExtensionsJsonElement.cs +++ b/DotTiled/Serialization/Tmj/ExtensionsJsonElement.cs @@ -20,21 +20,32 @@ internal static class ExtensionsJsonElement if (!element.TryGetProperty(propertyName, out var property)) return defaultValue; + if (property.ValueKind == JsonValueKind.Null) + return defaultValue; + return property.GetValueAs(); } internal static T GetValueAs(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(int) => element.GetInt32().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(bool) => element.GetBoolean().ToString(CultureInfo.InvariantCulture), _ => 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(this JsonElement element, string propertyName) where T : IParsable diff --git a/DotTiled/Serialization/Tmj/Tmj.Data.cs b/DotTiled/Serialization/Tmj/Tmj.Data.cs new file mode 100644 index 0000000..27cd2bc --- /dev/null +++ b/DotTiled/Serialization/Tmj/Tmj.Data.cs @@ -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(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("x"); + var y = element.GetRequiredProperty("y"); + var width = element.GetRequiredProperty("width"); + var height = element.GetRequiredProperty("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(e => e.GetValueAs()).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(); + 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); + } +} diff --git a/DotTiled/Serialization/Tmj/Tmj.Layer.cs b/DotTiled/Serialization/Tmj/Tmj.Layer.cs new file mode 100644 index 0000000..7e8685e --- /dev/null +++ b/DotTiled/Serialization/Tmj/Tmj.Layer.cs @@ -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("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("compression", s => s switch + { + "zlib" => DataCompression.ZLib, + "gzip" => DataCompression.GZip, + "" => null, + _ => throw new JsonException($"Unsupported compression '{s}'.") + }, null); + var encoding = element.GetOptionalPropertyParseable("encoding", s => s switch + { + "csv" => DataEncoding.Csv, + "base64" => DataEncoding.Base64, + _ => throw new JsonException($"Unsupported encoding '{s}'.") + }, DataEncoding.Csv); + var chunks = element.GetOptionalPropertyCustom("chunks", e => ReadDataAsChunks(e, compression, encoding), null); + var @class = element.GetOptionalProperty("class", ""); + var data = element.GetOptionalPropertyCustom("data", e => ReadDataWithoutChunks(e, compression, encoding), null); + var height = element.GetRequiredProperty("height"); + var id = element.GetRequiredProperty("id"); + var name = element.GetRequiredProperty("name"); + var offsetX = element.GetOptionalProperty("offsetx", 0.0f); + var offsetY = element.GetOptionalProperty("offsety", 0.0f); + var opacity = element.GetOptionalProperty("opacity", 1.0f); + var parallaxx = element.GetOptionalProperty("parallaxx", 1.0f); + var parallaxy = element.GetOptionalProperty("parallaxy", 1.0f); + var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e), null); + var repeatX = element.GetOptionalProperty("repeatx", false); + var repeatY = element.GetOptionalProperty("repeaty", false); + var startX = element.GetOptionalProperty("startx", 0); + var startY = element.GetOptionalProperty("starty", 0); + var tintColor = element.GetOptionalPropertyParseable("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var transparentColor = element.GetOptionalPropertyParseable("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var visible = element.GetOptionalProperty("visible", true); + var width = element.GetRequiredProperty("width"); + var x = element.GetRequiredProperty("x"); + var y = element.GetRequiredProperty("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 + }; + } +} diff --git a/DotTiled/Serialization/Tmj/Tmj.Map.cs b/DotTiled/Serialization/Tmj/Tmj.Map.cs index d4ea6f1..0906b70 100644 --- a/DotTiled/Serialization/Tmj/Tmj.Map.cs +++ b/DotTiled/Serialization/Tmj/Tmj.Map.cs @@ -1,13 +1,15 @@ +using System; 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 Map ReadMap(JsonElement element) + internal static Map ReadMap(JsonElement element, Func? externalTilesetResolver, Func externalTemplateResolver) { var version = element.GetRequiredProperty("version"); var tiledVersion = element.GetRequiredProperty("tiledversion"); @@ -51,14 +53,12 @@ internal partial class Tmj var backgroundColor = element.GetOptionalPropertyParseable("backgroundcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), Color.Parse("#00000000", CultureInfo.InvariantCulture)); var nextLayerID = element.GetRequiredProperty("nextlayerid"); var nextObjectID = element.GetRequiredProperty("nextobjectid"); - var infinite = element.GetOptionalProperty("infinite", 0) == 1; + var infinite = element.GetOptionalProperty("infinite", false); - // At most one of - Dictionary? properties = element.GetOptionalPropertyCustom>("properties", ReadProperties, null); + var properties = element.GetOptionalPropertyCustom?>("properties", ReadProperties, null); - // Any number of - List layers = []; - List tilesets = []; + List layers = element.GetOptionalPropertyCustom>("layers", e => e.GetValueAsList(ReadLayer), []); + List tilesets = element.GetOptionalPropertyCustom>("tilesets", e => e.GetValueAsList(el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver)), []); return new Map { @@ -86,176 +86,4 @@ internal partial class Tmj Layers = layers }; } - - internal static Dictionary ReadProperties(JsonElement element) - { - var properties = new Dictionary(); - - element.GetValueAsList(e => - { - var name = e.GetRequiredProperty("name"); - var type = e.GetOptionalPropertyParseable("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("value") }, - PropertyType.Int => new IntProperty { Name = name, Value = e.GetRequiredProperty("value") }, - PropertyType.Float => new FloatProperty { Name = name, Value = e.GetRequiredProperty("value") }, - PropertyType.Bool => new BoolProperty { Name = name, Value = e.GetRequiredProperty("value") }, - PropertyType.Color => new ColorProperty { Name = name, Value = e.GetRequiredPropertyParseable("value") }, - PropertyType.File => new FileProperty { Name = name, Value = e.GetRequiredProperty("value") }, - PropertyType.Object => new ObjectProperty { Name = name, Value = e.GetRequiredProperty("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("name"); - var propertyType = element.GetRequiredProperty("propertytype"); - - var properties = element.GetRequiredPropertyCustom>("properties", ReadProperties); - - return new ClassProperty { Name = name, PropertyType = propertyType, Properties = properties }; - } - - // internal static List ReadTilesets(ref Utf8JsonReader reader) - // { - // var tilesets = new List(); - - // 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? 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.") - // }; - // } } diff --git a/DotTiled/Serialization/Tmj/Tmj.Properties.cs b/DotTiled/Serialization/Tmj/Tmj.Properties.cs new file mode 100644 index 0000000..37b7dd1 --- /dev/null +++ b/DotTiled/Serialization/Tmj/Tmj.Properties.cs @@ -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 ReadProperties(JsonElement element) => + element.GetValueAsList(e => + { + var name = e.GetRequiredProperty("name"); + var type = e.GetOptionalPropertyParseable("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("value") }, + PropertyType.Int => new IntProperty { Name = name, Value = e.GetRequiredProperty("value") }, + PropertyType.Float => new FloatProperty { Name = name, Value = e.GetRequiredProperty("value") }, + PropertyType.Bool => new BoolProperty { Name = name, Value = e.GetRequiredProperty("value") }, + PropertyType.Color => new ColorProperty { Name = name, Value = e.GetRequiredPropertyParseable("value") }, + PropertyType.File => new FileProperty { Name = name, Value = e.GetRequiredProperty("value") }, + PropertyType.Object => new ObjectProperty { Name = name, Value = e.GetRequiredProperty("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("name"); + var propertyType = element.GetRequiredProperty("propertytype"); + + var properties = element.GetRequiredPropertyCustom>("properties", ReadProperties); + + return new ClassProperty { Name = name, PropertyType = propertyType, Properties = properties }; + } +} diff --git a/DotTiled/Serialization/Tmj/Tmj.Tileset.cs b/DotTiled/Serialization/Tmj/Tmj.Tileset.cs new file mode 100644 index 0000000..66d42b1 --- /dev/null +++ b/DotTiled/Serialization/Tmj/Tmj.Tileset.cs @@ -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? externalTilesetResolver, Func externalTemplateResolver) + { + var backgroundColor = element.GetOptionalPropertyParseable("backgroundcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var @class = element.GetOptionalProperty("class", ""); + var columns = element.GetOptionalProperty("columns", null); + var fillMode = element.GetOptionalPropertyParseable("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("firstgid", null); + var grid = element.GetOptionalPropertyCustom("grid", ReadGrid, null); + var image = element.GetOptionalProperty("image", null); + var imageHeight = element.GetOptionalProperty("imageheight", null); + var imageWidth = element.GetOptionalProperty("imagewidth", null); + var margin = element.GetOptionalProperty("margin", null); + var name = element.GetOptionalProperty("name", null); + var objectAlignment = element.GetOptionalPropertyParseable("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?>("properties", ReadProperties, null); + var source = element.GetOptionalProperty("source", null); + var spacing = element.GetOptionalProperty("spacing", null); + var tileCount = element.GetOptionalProperty("tilecount", null); + var tiledVersion = element.GetOptionalProperty("tiledversion", null); + var tileHeight = element.GetOptionalProperty("tileheight", null); + var tileOffset = element.GetOptionalPropertyCustom("tileoffset", ReadTileOffset, null); + var tileRenderSize = element.GetOptionalPropertyParseable("tilerendersize", s => s switch + { + "tile" => TileRenderSize.Tile, + "grid" => TileRenderSize.Grid, + _ => throw new JsonException($"Unknown tile render size '{s}'") + }, TileRenderSize.Tile); + var tiles = element.GetOptionalPropertyCustom>("tiles", ReadTiles, []); + var tileWidth = element.GetOptionalProperty("tilewidth", null); + var transparentColor = element.GetOptionalPropertyParseable("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var type = element.GetOptionalProperty("type", null); + var version = element.GetOptionalProperty("version", null); + //var wangsets = element.GetOptionalPropertyCustom?>("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("orientation", s => s switch + { + "orthogonal" => GridOrientation.Orthogonal, + "isometric" => GridOrientation.Isometric, + _ => throw new JsonException($"Unknown grid orientation '{s}'") + }, GridOrientation.Orthogonal); + var height = element.GetRequiredProperty("height"); + var width = element.GetRequiredProperty("width"); + + return new Grid + { + Orientation = orientation, + Height = height, + Width = width + }; + } + + internal static TileOffset ReadTileOffset(JsonElement element) + { + var x = element.GetRequiredProperty("x"); + var y = element.GetRequiredProperty("y"); + + return new TileOffset + { + X = x, + Y = y + }; + } + + internal static List ReadTiles(JsonElement element) => + element.GetValueAsList(e => + { + //var animation = e.GetOptionalPropertyCustom>("animation", ReadFrames, null); + var id = e.GetRequiredProperty("id"); + var image = e.GetOptionalProperty("image", null); + var imageHeight = e.GetOptionalProperty("imageheight", null); + var imageWidth = e.GetOptionalProperty("imagewidth", null); + var x = e.GetOptionalProperty("x", 0); + var y = e.GetOptionalProperty("y", 0); + var width = e.GetOptionalProperty("width", imageWidth ?? 0); + var height = e.GetOptionalProperty("height", imageHeight ?? 0); + //var objectGroup = e.GetOptionalPropertyCustom("objectgroup", ReadObjectLayer, null); + var probability = e.GetOptionalProperty("probability", 1.0f); + var properties = e.GetOptionalPropertyCustom?>("properties", ReadProperties, null); + // var terrain, replaced by wangsets + var type = e.GetOptionalProperty("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 + }; + }); +} diff --git a/DotTiled/Serialization/Tmj/TmjMapReader.cs b/DotTiled/Serialization/Tmj/TmjMapReader.cs index 4a113b1..9aa9675 100644 --- a/DotTiled/Serialization/Tmj/TmjMapReader.cs +++ b/DotTiled/Serialization/Tmj/TmjMapReader.cs @@ -7,19 +7,25 @@ namespace DotTiled; public class TmjMapReader : IMapReader { + // External resolvers + private readonly Func _externalTilesetResolver; + private readonly Func _externalTemplateResolver; + private string _jsonString; private bool disposedValue; - public TmjMapReader(string jsonString) + public TmjMapReader(string jsonString, Func externalTilesetResolver, Func externalTemplateResolver) { _jsonString = jsonString ?? throw new ArgumentNullException(nameof(jsonString)); + _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); + _externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver)); } public Map ReadMap() { var jsonDoc = JsonDocument.Parse(_jsonString); var rootElement = jsonDoc.RootElement; - return Tmj.ReadMap(rootElement); + return Tmj.ReadMap(rootElement, _externalTilesetResolver, _externalTemplateResolver); } protected virtual void Dispose(bool disposing) diff --git a/DotTiled/Serialization/Tmx/Tmx.Tileset.cs b/DotTiled/Serialization/Tmx/Tmx.Tileset.cs index 1996bc2..3c2a3a8 100644 --- a/DotTiled/Serialization/Tmx/Tmx.Tileset.cs +++ b/DotTiled/Serialization/Tmx/Tmx.Tileset.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Xml; @@ -18,8 +19,8 @@ internal partial class Tmx var @class = reader.GetOptionalAttribute("class") ?? ""; var tileWidth = reader.GetOptionalAttributeParseable("tilewidth"); var tileHeight = reader.GetOptionalAttributeParseable("tileheight"); - var spacing = reader.GetOptionalAttributeParseable("spacing"); - var margin = reader.GetOptionalAttributeParseable("margin"); + var spacing = reader.GetOptionalAttributeParseable("spacing") ?? 0; + var margin = reader.GetOptionalAttributeParseable("margin") ?? 0; var tileCount = reader.GetOptionalAttributeParseable("tilecount"); var columns = reader.GetOptionalAttributeParseable("columns"); var objectAlignment = reader.GetOptionalAttributeEnum("objectalignment", s => s switch @@ -131,6 +132,9 @@ internal partial class Tmx _ => r.Skip }); + if (format is null && source is not null) + format = ParseImageFormatFromSource(source); + return new Image { 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) { // Attributes diff --git a/Makefile b/Makefile index e69de29..148b225 100644 --- a/Makefile +++ b/Makefile @@ -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 \ No newline at end of file