From 7407edccb3c32bc39eeae0ef4aa749d1f030ae06 Mon Sep 17 00:00:00 2001 From: krnlexception Date: Tue, 10 Sep 2024 00:56:24 +0200 Subject: [PATCH 01/33] Example project --- src/DotTiled.Example/DotTiled.Example.csproj | 29 +++++++ src/DotTiled.Example/Program.cs | 78 +++++++++++++++++++ src/DotTiled.Example/tilemap.tmx | 12 +++ src/DotTiled.Example/tileset.png | Bin 0 -> 149 bytes src/DotTiled.Example/tileset.tsx | 4 + src/DotTiled.sln | 6 ++ 6 files changed, 129 insertions(+) create mode 100644 src/DotTiled.Example/DotTiled.Example.csproj create mode 100644 src/DotTiled.Example/Program.cs create mode 100644 src/DotTiled.Example/tilemap.tmx create mode 100644 src/DotTiled.Example/tileset.png create mode 100644 src/DotTiled.Example/tileset.tsx diff --git a/src/DotTiled.Example/DotTiled.Example.csproj b/src/DotTiled.Example/DotTiled.Example.csproj new file mode 100644 index 0000000..89e9d09 --- /dev/null +++ b/src/DotTiled.Example/DotTiled.Example.csproj @@ -0,0 +1,29 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + Always + + + Always + + + Always + + + Always + + + + diff --git a/src/DotTiled.Example/Program.cs b/src/DotTiled.Example/Program.cs new file mode 100644 index 0000000..0f0f806 --- /dev/null +++ b/src/DotTiled.Example/Program.cs @@ -0,0 +1,78 @@ +using DotTiled.Serialization; + +namespace DotTiled.Example; + +class Program +{ + static void Main(string[] args) + { + Quick(); + Manual(); + } + + // QUICK START + // Automatic and easy way to load tilemaps. + static void Quick() + { + var loader = Loader.Default(); + var map = loader.LoadMap("tilemap.tmx"); + + // You can do stuff with it like... + Console.WriteLine($"Tile width and height: {map.TileWidth}x{map.TileHeight}"); + TileLayer layer0 = (TileLayer)map.Layers[0]; // Get a layer + Console.WriteLine($"Tile in layer 0 at 0, 0: {layer0.Data.Value.GlobalTileIDs.Value[0]}"); + } + + // MANUAL + // Manually load a map, if you need to load from a custom source + static void Manual() + { + using var mapFileReader = new StreamReader("tilemap.tmx"); + var mapString = mapFileReader.ReadToEnd(); + using var mapReader = new MapReader(mapString, ResolveTileset, ResolveTemplate, ResolveCustomType); + var map = mapReader.ReadMap(); + + // Now do some other stuff with it... + StringProperty hello = (StringProperty)map.Properties.FirstOrDefault(property => property.Name == "hello"); + Console.WriteLine($"Layer 1 name: {map.Layers[0].Name}"); + Console.WriteLine($"Property 'hello': {hello.Value}"); + + // Now with tileset + Tileset tileset = map.Tilesets[0]; + Console.WriteLine($"Tileset 0 source: {tileset.Source.Value}"); + Console.WriteLine($"Tileset 0 image 0 source: {tileset.Image.Value.Source.Value}"); + } + + /* This function is responsible for loading all tilesets required by a tilemap, if you + want to use a custom source. */ + static Tileset ResolveTileset(string source) + { + string tilesetPath = Path.Combine(Directory.GetCurrentDirectory(), source); // Resolve path to a tileset. + using var tilesetFileReader = new StreamReader(tilesetPath); // Read tileset file itself. + var tilesetString = tilesetFileReader.ReadToEnd(); // You can replace this with any custom function + // to load data from any source, eg. .zip file. + using var tilesetReader = new TilesetReader(tilesetString, ResolveTileset, ResolveTemplate, ResolveCustomType); // Parse loaded tileset. + + return tilesetReader.ReadTileset(); // Return loaded tileset + } + + // This is pretty similar to above, but instead it loads templates, not tilesets. + static Template ResolveTemplate(string source) + { + string templatePath = Path.Combine(Directory.GetCurrentDirectory(), source); + using var templateFileReader = new StreamReader(templatePath); + var templateString = templateFileReader.ReadToEnd(); + using var templateReader = new TemplateReader(templateString, ResolveTileset, ResolveTemplate, ResolveCustomType); + return templateReader.ReadTemplate(); + } + + static ICustomTypeDefinition ResolveCustomType(string name) + { + CustomClassDefinition[] allDefinedTypes = + [ + new CustomClassDefinition() { Name = "a" }, + ]; + return allDefinedTypes.FirstOrDefault(type => type.Name == name); + } + +} diff --git a/src/DotTiled.Example/tilemap.tmx b/src/DotTiled.Example/tilemap.tmx new file mode 100644 index 0000000..7a9a294 --- /dev/null +++ b/src/DotTiled.Example/tilemap.tmx @@ -0,0 +1,12 @@ + + + + + + + + + AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAA== + + + diff --git a/src/DotTiled.Example/tileset.png b/src/DotTiled.Example/tileset.png new file mode 100644 index 0000000000000000000000000000000000000000..67b9eeea3c0801c3a7092d76b229e2560ee5d7c0 GIT binary patch literal 149 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucK`l=g#}Etu})QSZQ qC$3zWaLH~Bhp(&*Z!NEp7Q<%lsS3|M&zb^tF?hQAxvX + + + diff --git a/src/DotTiled.sln b/src/DotTiled.sln index 421e996..3eec160 100644 --- a/src/DotTiled.sln +++ b/src/DotTiled.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Tests", "DotTiled. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Benchmark", "DotTiled.Benchmark\DotTiled.Benchmark.csproj", "{510F3077-8EA4-47D1-8D01-E2D538F1B899}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Example", "DotTiled.Example\DotTiled.Example.csproj", "{31D5D3EB-67E1-4C8F-BB59-5BC32817C0FF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -30,5 +32,9 @@ Global {510F3077-8EA4-47D1-8D01-E2D538F1B899}.Debug|Any CPU.Build.0 = Debug|Any CPU {510F3077-8EA4-47D1-8D01-E2D538F1B899}.Release|Any CPU.ActiveCfg = Release|Any CPU {510F3077-8EA4-47D1-8D01-E2D538F1B899}.Release|Any CPU.Build.0 = Release|Any CPU + {31D5D3EB-67E1-4C8F-BB59-5BC32817C0FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {31D5D3EB-67E1-4C8F-BB59-5BC32817C0FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {31D5D3EB-67E1-4C8F-BB59-5BC32817C0FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {31D5D3EB-67E1-4C8F-BB59-5BC32817C0FF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From eb22de169cf5d15bb07b2ac2c9484c16119f04bf Mon Sep 17 00:00:00 2001 From: krnlexception Date: Tue, 10 Sep 2024 00:57:23 +0200 Subject: [PATCH 02/33] JetBrains Rider .idea directory ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a2b8d83..1b33c6c 100644 --- a/.gitignore +++ b/.gitignore @@ -402,3 +402,4 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml +.idea From 44cbf5b90a3babc95a3eed28213e6b88bbf214df Mon Sep 17 00:00:00 2001 From: krnlException Date: Wed, 11 Sep 2024 17:11:24 +0200 Subject: [PATCH 03/33] Style changes, and usage of embedded resources --- src/DotTiled.Example/DotTiled.Example.csproj | 9 ++++ src/DotTiled.Example/Program.cs | 53 ++++++++++--------- src/DotTiled.Example/embedded-tilemap.tmx | 12 +++++ src/DotTiled.Example/embedded-tileset.png | Bin 0 -> 149 bytes src/DotTiled.Example/embedded-tileset.tsx | 4 ++ 5 files changed, 53 insertions(+), 25 deletions(-) create mode 100644 src/DotTiled.Example/embedded-tilemap.tmx create mode 100644 src/DotTiled.Example/embedded-tileset.png create mode 100644 src/DotTiled.Example/embedded-tileset.tsx diff --git a/src/DotTiled.Example/DotTiled.Example.csproj b/src/DotTiled.Example/DotTiled.Example.csproj index 89e9d09..b1e806e 100644 --- a/src/DotTiled.Example/DotTiled.Example.csproj +++ b/src/DotTiled.Example/DotTiled.Example.csproj @@ -26,4 +26,13 @@ + + + + + + + + + diff --git a/src/DotTiled.Example/Program.cs b/src/DotTiled.Example/Program.cs index 0f0f806..785369b 100644 --- a/src/DotTiled.Example/Program.cs +++ b/src/DotTiled.Example/Program.cs @@ -1,10 +1,11 @@ -using DotTiled.Serialization; +using System.Reflection; +using DotTiled.Serialization; namespace DotTiled.Example; class Program { - static void Main(string[] args) + private static void Main() { Quick(); Manual(); @@ -12,7 +13,7 @@ class Program // QUICK START // Automatic and easy way to load tilemaps. - static void Quick() + private static void Quick() { var loader = Loader.Default(); var map = loader.LoadMap("tilemap.tmx"); @@ -25,7 +26,7 @@ class Program // MANUAL // Manually load a map, if you need to load from a custom source - static void Manual() + private static void Manual() { using var mapFileReader = new StreamReader("tilemap.tmx"); var mapString = mapFileReader.ReadToEnd(); @@ -33,7 +34,7 @@ class Program var map = mapReader.ReadMap(); // Now do some other stuff with it... - StringProperty hello = (StringProperty)map.Properties.FirstOrDefault(property => property.Name == "hello"); + StringProperty hello = map.GetProperty("hello"); Console.WriteLine($"Layer 1 name: {map.Layers[0].Name}"); Console.WriteLine($"Property 'hello': {hello.Value}"); @@ -43,36 +44,38 @@ class Program Console.WriteLine($"Tileset 0 image 0 source: {tileset.Image.Value.Source.Value}"); } - /* This function is responsible for loading all tilesets required by a tilemap, if you - want to use a custom source. */ - static Tileset ResolveTileset(string source) + // This function is responsible for loading all tilesets required by a tilemap, if you + // want to use a custom source. + private static Tileset ResolveTileset(string source) { - string tilesetPath = Path.Combine(Directory.GetCurrentDirectory(), source); // Resolve path to a tileset. - using var tilesetFileReader = new StreamReader(tilesetPath); // Read tileset file itself. - var tilesetString = tilesetFileReader.ReadToEnd(); // You can replace this with any custom function - // to load data from any source, eg. .zip file. - using var tilesetReader = new TilesetReader(tilesetString, ResolveTileset, ResolveTemplate, ResolveCustomType); // Parse loaded tileset. + // Read a file from assembly + // You can use any other source for files, eg. compressed archive, or even file from internet. + using Stream? tilesetStream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"DotTiled.Example.embedded-{source}") + ?? throw new FileLoadException($"{source} not found in assembly."); + string tilesetString = new StreamReader(tilesetStream).ReadToEnd(); + + using TilesetReader tilesetReader = new TilesetReader(tilesetString, ResolveTileset, ResolveTemplate, ResolveCustomType); // Parse loaded tileset. return tilesetReader.ReadTileset(); // Return loaded tileset } // This is pretty similar to above, but instead it loads templates, not tilesets. - static Template ResolveTemplate(string source) + private static Template ResolveTemplate(string source) { - string templatePath = Path.Combine(Directory.GetCurrentDirectory(), source); - using var templateFileReader = new StreamReader(templatePath); - var templateString = templateFileReader.ReadToEnd(); - using var templateReader = new TemplateReader(templateString, ResolveTileset, ResolveTemplate, ResolveCustomType); + using Stream? templateStream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"DotTiled.Example.{source}") + ?? throw new FileLoadException($"{source} not found in assembly."); + string templateString = new StreamReader(templateStream).ReadToEnd(); + + using TemplateReader templateReader = new TemplateReader(templateString, ResolveTileset, ResolveTemplate, ResolveCustomType); return templateReader.ReadTemplate(); } - static ICustomTypeDefinition ResolveCustomType(string name) + private static ICustomTypeDefinition ResolveCustomType(string name) { - CustomClassDefinition[] allDefinedTypes = - [ - new CustomClassDefinition() { Name = "a" }, - ]; - return allDefinedTypes.FirstOrDefault(type => type.Name == name); + ICustomTypeDefinition[] allDefinedTypes = + [ + new CustomClassDefinition() { Name = "a" }, + ]; + return allDefinedTypes.FirstOrDefault(type => type.Name == name) ?? throw new InvalidOperationException(); } - } diff --git a/src/DotTiled.Example/embedded-tilemap.tmx b/src/DotTiled.Example/embedded-tilemap.tmx new file mode 100644 index 0000000..7a9a294 --- /dev/null +++ b/src/DotTiled.Example/embedded-tilemap.tmx @@ -0,0 +1,12 @@ + + + + + + + + + AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAA== + + + diff --git a/src/DotTiled.Example/embedded-tileset.png b/src/DotTiled.Example/embedded-tileset.png new file mode 100644 index 0000000000000000000000000000000000000000..67b9eeea3c0801c3a7092d76b229e2560ee5d7c0 GIT binary patch literal 149 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucK`l=g#}Etu})QSZQ qC$3zWaLH~Bhp(&*Z!NEp7Q<%lsS3|M&zb^tF?hQAxvX + + + From 3d649fab95a6e4cbd210214b06b36eeb91ba61a4 Mon Sep 17 00:00:00 2001 From: krnlException Date: Thu, 12 Sep 2024 20:23:43 +0200 Subject: [PATCH 04/33] Program class to public and top-level warning disabled --- .editorconfig | 1 + src/DotTiled.Example/Program.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index 8e747e9..a784f75 100644 --- a/.editorconfig +++ b/.editorconfig @@ -237,6 +237,7 @@ dotnet_diagnostic.IDE0008.severity = silent dotnet_diagnostic.IDE0055.severity = silent dotnet_diagnostic.IDE0058.severity = silent dotnet_diagnostic.IDE0160.severity = none +dotnet_diagnostic.IDE0210.severity = none dotnet_diagnostic.CA1707.severity = silent dotnet_diagnostic.CA1852.severity = none dotnet_diagnostic.CA1805.severity = none diff --git a/src/DotTiled.Example/Program.cs b/src/DotTiled.Example/Program.cs index 785369b..234012a 100644 --- a/src/DotTiled.Example/Program.cs +++ b/src/DotTiled.Example/Program.cs @@ -3,7 +3,7 @@ using DotTiled.Serialization; namespace DotTiled.Example; -class Program +public class Program { private static void Main() { From 0515ba3256dd517618f18735ccbf432e3a1c0072 Mon Sep 17 00:00:00 2001 From: krnlexception Date: Fri, 13 Sep 2024 19:32:24 +0200 Subject: [PATCH 05/33] Godot example --- .../DotTiled.Example.Console.csproj} | 2 +- .../DotTiled.Example.Console}/Program.cs | 0 .../embedded-tilemap.tmx | 0 .../embedded-tileset.png | Bin .../embedded-tileset.tsx | 0 .../DotTiled.Example.Console}/tilemap.tmx | 0 .../DotTiled.Example.Console}/tileset.png | Bin .../DotTiled.Example.Console}/tileset.tsx | 0 .../DotTiled.Example.Godot/.gitattributes | 2 + .../DotTiled.Example.Godot/.gitignore | 2 + .../DotTiled.Example.Godot.csproj | 11 +++ .../DotTiled.Example.Godot.csproj.old | 11 +++ .../DotTiled.Example.Godot.sln | 19 +++++ .../DotTiled.Example.Godot/MapParser.cs | 68 ++++++++++++++++++ .../DotTiled.Example.Godot/blocks/1.tscn | 9 +++ .../DotTiled.Example.Godot/icon.svg | 1 + .../DotTiled.Example.Godot/icon.svg.import | 37 ++++++++++ .../DotTiled.Example.Godot/main.tscn | 6 ++ .../DotTiled.Example.Godot/project.godot | 20 ++++++ .../DotTiled.Example.Godot/tilemap.tmx | 12 ++++ .../DotTiled.Example.Godot/tileset.png | Bin 0 -> 149 bytes .../DotTiled.Example.Godot/tileset.png.import | 34 +++++++++ .../DotTiled.Example.Godot/tileset.tsx | 4 ++ src/DotTiled.sln | 22 ++++-- 24 files changed, 254 insertions(+), 6 deletions(-) rename src/{DotTiled.Example/DotTiled.Example.csproj => DotTiled.Examples/DotTiled.Example.Console/DotTiled.Example.Console.csproj} (94%) rename src/{DotTiled.Example => DotTiled.Examples/DotTiled.Example.Console}/Program.cs (100%) rename src/{DotTiled.Example => DotTiled.Examples/DotTiled.Example.Console}/embedded-tilemap.tmx (100%) rename src/{DotTiled.Example => DotTiled.Examples/DotTiled.Example.Console}/embedded-tileset.png (100%) rename src/{DotTiled.Example => DotTiled.Examples/DotTiled.Example.Console}/embedded-tileset.tsx (100%) rename src/{DotTiled.Example => DotTiled.Examples/DotTiled.Example.Console}/tilemap.tmx (100%) rename src/{DotTiled.Example => DotTiled.Examples/DotTiled.Example.Console}/tileset.png (100%) rename src/{DotTiled.Example => DotTiled.Examples/DotTiled.Example.Console}/tileset.tsx (100%) create mode 100644 src/DotTiled.Examples/DotTiled.Example.Godot/.gitattributes create mode 100644 src/DotTiled.Examples/DotTiled.Example.Godot/.gitignore create mode 100644 src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.csproj create mode 100644 src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.csproj.old create mode 100644 src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.sln create mode 100644 src/DotTiled.Examples/DotTiled.Example.Godot/MapParser.cs create mode 100644 src/DotTiled.Examples/DotTiled.Example.Godot/blocks/1.tscn create mode 100644 src/DotTiled.Examples/DotTiled.Example.Godot/icon.svg create mode 100644 src/DotTiled.Examples/DotTiled.Example.Godot/icon.svg.import create mode 100644 src/DotTiled.Examples/DotTiled.Example.Godot/main.tscn create mode 100644 src/DotTiled.Examples/DotTiled.Example.Godot/project.godot create mode 100644 src/DotTiled.Examples/DotTiled.Example.Godot/tilemap.tmx create mode 100644 src/DotTiled.Examples/DotTiled.Example.Godot/tileset.png create mode 100644 src/DotTiled.Examples/DotTiled.Example.Godot/tileset.png.import create mode 100644 src/DotTiled.Examples/DotTiled.Example.Godot/tileset.tsx diff --git a/src/DotTiled.Example/DotTiled.Example.csproj b/src/DotTiled.Examples/DotTiled.Example.Console/DotTiled.Example.Console.csproj similarity index 94% rename from src/DotTiled.Example/DotTiled.Example.csproj rename to src/DotTiled.Examples/DotTiled.Example.Console/DotTiled.Example.Console.csproj index b1e806e..c9bf1a8 100644 --- a/src/DotTiled.Example/DotTiled.Example.csproj +++ b/src/DotTiled.Examples/DotTiled.Example.Console/DotTiled.Example.Console.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/DotTiled.Example/Program.cs b/src/DotTiled.Examples/DotTiled.Example.Console/Program.cs similarity index 100% rename from src/DotTiled.Example/Program.cs rename to src/DotTiled.Examples/DotTiled.Example.Console/Program.cs diff --git a/src/DotTiled.Example/embedded-tilemap.tmx b/src/DotTiled.Examples/DotTiled.Example.Console/embedded-tilemap.tmx similarity index 100% rename from src/DotTiled.Example/embedded-tilemap.tmx rename to src/DotTiled.Examples/DotTiled.Example.Console/embedded-tilemap.tmx diff --git a/src/DotTiled.Example/embedded-tileset.png b/src/DotTiled.Examples/DotTiled.Example.Console/embedded-tileset.png similarity index 100% rename from src/DotTiled.Example/embedded-tileset.png rename to src/DotTiled.Examples/DotTiled.Example.Console/embedded-tileset.png diff --git a/src/DotTiled.Example/embedded-tileset.tsx b/src/DotTiled.Examples/DotTiled.Example.Console/embedded-tileset.tsx similarity index 100% rename from src/DotTiled.Example/embedded-tileset.tsx rename to src/DotTiled.Examples/DotTiled.Example.Console/embedded-tileset.tsx diff --git a/src/DotTiled.Example/tilemap.tmx b/src/DotTiled.Examples/DotTiled.Example.Console/tilemap.tmx similarity index 100% rename from src/DotTiled.Example/tilemap.tmx rename to src/DotTiled.Examples/DotTiled.Example.Console/tilemap.tmx diff --git a/src/DotTiled.Example/tileset.png b/src/DotTiled.Examples/DotTiled.Example.Console/tileset.png similarity index 100% rename from src/DotTiled.Example/tileset.png rename to src/DotTiled.Examples/DotTiled.Example.Console/tileset.png diff --git a/src/DotTiled.Example/tileset.tsx b/src/DotTiled.Examples/DotTiled.Example.Console/tileset.tsx similarity index 100% rename from src/DotTiled.Example/tileset.tsx rename to src/DotTiled.Examples/DotTiled.Example.Console/tileset.tsx diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/.gitattributes b/src/DotTiled.Examples/DotTiled.Example.Godot/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/src/DotTiled.Examples/DotTiled.Example.Godot/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/.gitignore b/src/DotTiled.Examples/DotTiled.Example.Godot/.gitignore new file mode 100644 index 0000000..4709183 --- /dev/null +++ b/src/DotTiled.Examples/DotTiled.Example.Godot/.gitignore @@ -0,0 +1,2 @@ +# Godot 4+ specific ignores +.godot/ diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.csproj b/src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.csproj new file mode 100644 index 0000000..99e4b77 --- /dev/null +++ b/src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.csproj @@ -0,0 +1,11 @@ + + + net8.0 + net7.0 + net8.0 + true + + + + + \ No newline at end of file diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.csproj.old b/src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.csproj.old new file mode 100644 index 0000000..a7de93f --- /dev/null +++ b/src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.csproj.old @@ -0,0 +1,11 @@ + + + net8.0 + net7.0 + net8.0 + true + + + + + diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.sln b/src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.sln new file mode 100644 index 0000000..c707fa2 --- /dev/null +++ b/src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.sln @@ -0,0 +1,19 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Example.Godot", "DotTiled.Example.Godot.csproj", "{61468FCF-ACC1-4E3B-B4B4-270279E45BF5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + ExportDebug|Any CPU = ExportDebug|Any CPU + ExportRelease|Any CPU = ExportRelease|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {61468FCF-ACC1-4E3B-B4B4-270279E45BF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {61468FCF-ACC1-4E3B-B4B4-270279E45BF5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {61468FCF-ACC1-4E3B-B4B4-270279E45BF5}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU + {61468FCF-ACC1-4E3B-B4B4-270279E45BF5}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU + {61468FCF-ACC1-4E3B-B4B4-270279E45BF5}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU + {61468FCF-ACC1-4E3B-B4B4-270279E45BF5}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU + EndGlobalSection +EndGlobal diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/MapParser.cs b/src/DotTiled.Examples/DotTiled.Example.Godot/MapParser.cs new file mode 100644 index 0000000..5ad960b --- /dev/null +++ b/src/DotTiled.Examples/DotTiled.Example.Godot/MapParser.cs @@ -0,0 +1,68 @@ +using System; +using System.Globalization; +using System.Linq; +using DotTiled.Serialization; +using Godot; + +namespace DotTiled.Example.Godot; +public partial class MapParser : Node2D +{ + public override void _Ready() + { + // Load map + var mapString = FileAccess.Open("res://tilemap.tmx", FileAccess.ModeFlags.Read).GetAsText(); //Get file from Godot filesystem + using var mapReader = new MapReader(mapString, ResolveTileset, ResolveTemplate, ResolveCustomType); + var map = mapReader.ReadMap(); + + TileLayer layer0 = (TileLayer)map.Layers[0]; + + for (int y = 0; y < layer0.Height; y++) + { + for (int x = 0; x < layer0.Width; x++) + { + uint tile = layer0.Data.Value.GlobalTileIDs.Value[(y * layer0.Width) + x]; + if (tile == 0) continue; // If block is 0, i.e. air, then continue + + // Load actual block from Godot resources + Node2D block = (Node2D)GD.Load($"res://blocks/{tile}.tscn").Instantiate(); + + // Calculate where block should be + Vector2I scale = (Vector2I)block.GetNode(tile.ToString(CultureInfo.CurrentCulture)).Scale; + int blockX = (block.GetNode(tile.ToString(CultureInfo.CurrentCulture)).Texture.GetWidth() * scale.X / 2) + + (x * block.GetNode(tile.ToString(CultureInfo.CurrentCulture)).Texture.GetWidth() * scale.X); + int blockY = (block.GetNode(tile.ToString(CultureInfo.CurrentCulture)).Texture.GetHeight() * scale.Y / 2) + + (y * block.GetNode(tile.ToString(CultureInfo.CurrentCulture)).Texture.GetHeight() * scale.Y); + block.Position = new Vector2(blockX, blockY); + + // Add block to current scene + AddChild(block); + GD.Print($"{blockX}, {blockY}: {tile}"); + } + } + } + + private Tileset ResolveTileset(string source) + { + string tilesetString = FileAccess.Open($"res://{source}", FileAccess.ModeFlags.Read).GetAsText(); + using TilesetReader tilesetReader = + new TilesetReader(tilesetString, ResolveTileset, ResolveTemplate, ResolveCustomType); + return tilesetReader.ReadTileset(); + } + + private Template ResolveTemplate(string source) + { + string templateString = FileAccess.Open($"res://{source}", FileAccess.ModeFlags.Read).GetAsText(); + using TemplateReader templateReader = + new TemplateReader(templateString, ResolveTileset, ResolveTemplate, ResolveCustomType); + return templateReader.ReadTemplate(); + } + + private static ICustomTypeDefinition ResolveCustomType(string name) + { + ICustomTypeDefinition[] allDefinedTypes = + [ + new CustomClassDefinition() { Name = "a" }, + ]; + return allDefinedTypes.FirstOrDefault(type => type.Name == name) ?? throw new InvalidOperationException(); + } +} diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/blocks/1.tscn b/src/DotTiled.Examples/DotTiled.Example.Godot/blocks/1.tscn new file mode 100644 index 0000000..8c10a13 --- /dev/null +++ b/src/DotTiled.Examples/DotTiled.Example.Godot/blocks/1.tscn @@ -0,0 +1,9 @@ +[gd_scene load_steps=2 format=3 uid="uid://ce10iald4cb3f"] + +[ext_resource type="Texture2D" uid="uid://da08vay832u8c" path="res://tileset.png" id="1_c5fs4"] + +[node name="1" type="Node2D"] + +[node name="1" type="Sprite2D" parent="."] +scale = Vector2(2, 2) +texture = ExtResource("1_c5fs4") diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/icon.svg b/src/DotTiled.Examples/DotTiled.Example.Godot/icon.svg new file mode 100644 index 0000000..3fe4f4a --- /dev/null +++ b/src/DotTiled.Examples/DotTiled.Example.Godot/icon.svg @@ -0,0 +1 @@ + diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/icon.svg.import b/src/DotTiled.Examples/DotTiled.Example.Godot/icon.svg.import new file mode 100644 index 0000000..164e9de --- /dev/null +++ b/src/DotTiled.Examples/DotTiled.Example.Godot/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://0kywmrvvqqyr" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/main.tscn b/src/DotTiled.Examples/DotTiled.Example.Godot/main.tscn new file mode 100644 index 0000000..4fdfc60 --- /dev/null +++ b/src/DotTiled.Examples/DotTiled.Example.Godot/main.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://p4rpwsvyslew"] + +[ext_resource type="Script" path="res://MapParser.cs" id="1_xjmxv"] + +[node name="Node2D" type="Node2D"] +script = ExtResource("1_xjmxv") diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/project.godot b/src/DotTiled.Examples/DotTiled.Example.Godot/project.godot new file mode 100644 index 0000000..539b623 --- /dev/null +++ b/src/DotTiled.Examples/DotTiled.Example.Godot/project.godot @@ -0,0 +1,20 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="DotTiled.Example.Godot" +run/main_scene="res://main.tscn" +config/features=PackedStringArray("4.3", "C#", "Forward Plus") +config/icon="res://icon.svg" + +[dotnet] + +project/assembly_name="DotTiled.Example.Godot" diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/tilemap.tmx b/src/DotTiled.Examples/DotTiled.Example.Godot/tilemap.tmx new file mode 100644 index 0000000..7a9a294 --- /dev/null +++ b/src/DotTiled.Examples/DotTiled.Example.Godot/tilemap.tmx @@ -0,0 +1,12 @@ + + + + + + + + + AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAA== + + + diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/tileset.png b/src/DotTiled.Examples/DotTiled.Example.Godot/tileset.png new file mode 100644 index 0000000000000000000000000000000000000000..67b9eeea3c0801c3a7092d76b229e2560ee5d7c0 GIT binary patch literal 149 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucK`l=g#}Etu})QSZQ qC$3zWaLH~Bhp(&*Z!NEp7Q<%lsS3|M&zb^tF?hQAxvX + + + diff --git a/src/DotTiled.sln b/src/DotTiled.sln index 3eec160..7208481 100644 --- a/src/DotTiled.sln +++ b/src/DotTiled.sln @@ -9,7 +9,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Tests", "DotTiled. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Benchmark", "DotTiled.Benchmark\DotTiled.Benchmark.csproj", "{510F3077-8EA4-47D1-8D01-E2D538F1B899}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Example", "DotTiled.Example\DotTiled.Example.csproj", "{31D5D3EB-67E1-4C8F-BB59-5BC32817C0FF}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{8C54542E-3C2C-486C-9BEF-4C510391AFDA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Example.Console", "DotTiled.Examples\DotTiled.Example.Console\DotTiled.Example.Console.csproj", "{F9892295-6C2C-4ABD-9D6F-2AC81D2C6E67}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Example.Godot", "DotTiled.Examples\DotTiled.Example.Godot\DotTiled.Example.Godot.csproj", "{7541A9B3-43A5-45A7-939E-6F542319D990}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -32,9 +36,17 @@ Global {510F3077-8EA4-47D1-8D01-E2D538F1B899}.Debug|Any CPU.Build.0 = Debug|Any CPU {510F3077-8EA4-47D1-8D01-E2D538F1B899}.Release|Any CPU.ActiveCfg = Release|Any CPU {510F3077-8EA4-47D1-8D01-E2D538F1B899}.Release|Any CPU.Build.0 = Release|Any CPU - {31D5D3EB-67E1-4C8F-BB59-5BC32817C0FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {31D5D3EB-67E1-4C8F-BB59-5BC32817C0FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {31D5D3EB-67E1-4C8F-BB59-5BC32817C0FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {31D5D3EB-67E1-4C8F-BB59-5BC32817C0FF}.Release|Any CPU.Build.0 = Release|Any CPU + {F9892295-6C2C-4ABD-9D6F-2AC81D2C6E67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F9892295-6C2C-4ABD-9D6F-2AC81D2C6E67}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F9892295-6C2C-4ABD-9D6F-2AC81D2C6E67}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F9892295-6C2C-4ABD-9D6F-2AC81D2C6E67}.Release|Any CPU.Build.0 = Release|Any CPU + {7541A9B3-43A5-45A7-939E-6F542319D990}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7541A9B3-43A5-45A7-939E-6F542319D990}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7541A9B3-43A5-45A7-939E-6F542319D990}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {7541A9B3-43A5-45A7-939E-6F542319D990}.Release|Any CPU.Build.0 = Debug|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {F9892295-6C2C-4ABD-9D6F-2AC81D2C6E67} = {8C54542E-3C2C-486C-9BEF-4C510391AFDA} + {7541A9B3-43A5-45A7-939E-6F542319D990} = {8C54542E-3C2C-486C-9BEF-4C510391AFDA} EndGlobalSection EndGlobal From f72cfd397bdabbaaa4886eda9797085693fee490 Mon Sep 17 00:00:00 2001 From: krnlException Date: Mon, 16 Sep 2024 16:33:07 +0200 Subject: [PATCH 06/33] Removed backup file --- .../DotTiled.Example.Godot.csproj.old | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.csproj.old diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.csproj.old b/src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.csproj.old deleted file mode 100644 index a7de93f..0000000 --- a/src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.csproj.old +++ /dev/null @@ -1,11 +0,0 @@ - - - net8.0 - net7.0 - net8.0 - true - - - - - From 50c14011bc0fc97835511cee26f496613b43201a Mon Sep 17 00:00:00 2001 From: dcronqvist Date: Sat, 28 Sep 2024 19:52:28 +0200 Subject: [PATCH 07/33] Remove text about MonoGame support --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6d9de13..fa181c4 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Other similar libraries exist, and you may want to consider them for your projec > [!NOTE] > *Both benchmark time and memory ratios are relative to DotTiled. Lower is better. Benchmark (time) refers to the execution time of loading the same map from an in-memory string that contains XML data in the `.tmx` format. Benchmark (memory) refers to the memory allocated during that loading process. For further details on the benchmark results, see the collapsible section below. -[MonoGame](https://www.monogame.net) users may also want to consider using [MonoGame.Extended](https://github.com/craftworkgames/MonoGame.Extended) for loading Tiled maps and tilesets. Like MonoGame.Extended, DotTiled also provides a way to properly import Tiled maps and tilesets with the MonoGame content pipeline (with the DotTiled.MonoGame.Pipeline NuGet). However, unlike MonoGame.Extended, DotTiled does *not* include any kind of rendering capabilities, and it is up to you as a developer to implement any kind of rendering for your maps when using DotTiled. The feature coverage by MonoGame.Extended is less than that of DotTiled, so you may want to consider using DotTiled if you need access to more Tiled features and flexibility. +[MonoGame](https://www.monogame.net) users may also want to consider using [MonoGame.Extended](https://github.com/craftworkgames/MonoGame.Extended) for loading Tiled maps and tilesets. The feature coverage by MonoGame.Extended is less than that of DotTiled, so you may want to consider using DotTiled if you need access to more Tiled features and flexibility.
From e8dc677341f684bf38ef4b3b8ed047d196c7de0e Mon Sep 17 00:00:00 2001 From: dcronqvist Date: Sat, 28 Sep 2024 19:55:06 +0200 Subject: [PATCH 08/33] Update README that is published with NuGet to reflect MonoGame support --- src/DotTiled/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DotTiled/README.md b/src/DotTiled/README.md index cd16fe2..692a997 100644 --- a/src/DotTiled/README.md +++ b/src/DotTiled/README.md @@ -23,7 +23,7 @@ Other similar libraries exist, and you may want to consider them for your projec > *Both benchmark time and memory ratios are relative to DotTiled. Lower is better. Benchmark (time) refers to the execution time of loading the same map from an in-memory string that contains XML data in the `.tmx` format. Benchmark (memory) refers to the memory allocated during that loading process. -[MonoGame](https://www.monogame.net) users may also want to consider using [MonoGame.Extended](https://github.com/craftworkgames/MonoGame.Extended) for loading Tiled maps and tilesets. Like MonoGame.Extended, DotTiled also provides a way to properly import Tiled maps and tilesets with the MonoGame content pipeline (with the DotTiled.MonoGame.Pipeline NuGet). However, unlike MonoGame.Extended, DotTiled does *not* include any kind of rendering capabilities, and it is up to you as a developer to implement any kind of rendering for your maps when using DotTiled. The feature coverage by MonoGame.Extended is less than that of DotTiled, so you may want to consider using DotTiled if you need access to more Tiled features and flexibility. +[MonoGame](https://www.monogame.net) users may also want to consider using [MonoGame.Extended](https://github.com/craftworkgames/MonoGame.Extended) for loading Tiled maps and tilesets. The feature coverage by MonoGame.Extended is less than that of DotTiled, so you may want to consider using DotTiled if you need access to more Tiled features and flexibility. # Feature coverage comparison From 782e771a4146533e464ceda1f353dc63f40484d2 Mon Sep 17 00:00:00 2001 From: Anders Kehlet Date: Thu, 3 Oct 2024 22:26:47 +0200 Subject: [PATCH 09/33] Fix type of visible attribute in tileset object group. --- src/DotTiled/Serialization/Tmx/TmxReaderBase.ObjectLayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DotTiled/Serialization/Tmx/TmxReaderBase.ObjectLayer.cs b/src/DotTiled/Serialization/Tmx/TmxReaderBase.ObjectLayer.cs index 85c669c..46626e6 100644 --- a/src/DotTiled/Serialization/Tmx/TmxReaderBase.ObjectLayer.cs +++ b/src/DotTiled/Serialization/Tmx/TmxReaderBase.ObjectLayer.cs @@ -97,7 +97,7 @@ public abstract partial class TmxReaderBase var height = _reader.GetOptionalAttributeParseable("height").GetValueOr(heightDefault); var rotation = _reader.GetOptionalAttributeParseable("rotation").GetValueOr(rotationDefault); var gid = _reader.GetOptionalAttributeParseable("gid").GetValueOrOptional(gidDefault); - var visible = _reader.GetOptionalAttributeParseable("visible").GetValueOr(visibleDefault); + var visible = _reader.GetOptionalAttributeParseable("visible").GetValueOr(visibleDefault ? 1u : 0u) == 1; // Elements DotTiled.Object foundObject = null; From e411d515732aa81a2105962dc2182c1af34215d1 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Fri, 4 Oct 2024 21:16:00 +0200 Subject: [PATCH 10/33] Make sure to run console example application as part of test suite --- Makefile | 1 + .../DotTiled.Example.Console.csproj | 24 +++--------------- .../DotTiled.Example.Console/Program.cs | 21 ++++++++------- .../embedded-tilemap.tmx | 12 --------- .../embedded-tileset.png | Bin 149 -> 0 bytes .../embedded-tileset.tsx | 4 --- 6 files changed, 16 insertions(+), 46 deletions(-) delete mode 100644 src/DotTiled.Examples/DotTiled.Example.Console/embedded-tilemap.tmx delete mode 100644 src/DotTiled.Examples/DotTiled.Example.Console/embedded-tileset.png delete mode 100644 src/DotTiled.Examples/DotTiled.Example.Console/embedded-tileset.tsx diff --git a/Makefile b/Makefile index f7382f5..30ddf7e 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ test: dotnet build src/DotTiled.sln dotnet test src/DotTiled.sln + dotnet run --project src/DotTiled.Examples/DotTiled.Example.Console/DotTiled.Example.Console.csproj -- src/DotTiled.Examples/DotTiled.Example.Console docs-serve: docs/index.md docfx docs/docfx.json --serve diff --git a/src/DotTiled.Examples/DotTiled.Example.Console/DotTiled.Example.Console.csproj b/src/DotTiled.Examples/DotTiled.Example.Console/DotTiled.Example.Console.csproj index c9bf1a8..0dbaba2 100644 --- a/src/DotTiled.Examples/DotTiled.Example.Console/DotTiled.Example.Console.csproj +++ b/src/DotTiled.Examples/DotTiled.Example.Console/DotTiled.Example.Console.csproj @@ -12,27 +12,9 @@ - - Always - - - Always - - - Always - - - Always - - - - - - - - - - + + + diff --git a/src/DotTiled.Examples/DotTiled.Example.Console/Program.cs b/src/DotTiled.Examples/DotTiled.Example.Console/Program.cs index 234012a..9dba3df 100644 --- a/src/DotTiled.Examples/DotTiled.Example.Console/Program.cs +++ b/src/DotTiled.Examples/DotTiled.Example.Console/Program.cs @@ -5,18 +5,20 @@ namespace DotTiled.Example; public class Program { - private static void Main() + private static void Main(string[] args) { - Quick(); + Quick(args[0]); Manual(); } // QUICK START // Automatic and easy way to load tilemaps. - private static void Quick() + private static void Quick(string basePath) { + var tilemapPath = Path.Combine(basePath, "tilemap.tmx"); + var loader = Loader.Default(); - var map = loader.LoadMap("tilemap.tmx"); + var map = loader.LoadMap(tilemapPath); // You can do stuff with it like... Console.WriteLine($"Tile width and height: {map.TileWidth}x{map.TileHeight}"); @@ -28,9 +30,10 @@ public class Program // Manually load a map, if you need to load from a custom source private static void Manual() { - using var mapFileReader = new StreamReader("tilemap.tmx"); - var mapString = mapFileReader.ReadToEnd(); - using var mapReader = new MapReader(mapString, ResolveTileset, ResolveTemplate, ResolveCustomType); + using Stream? tilemapStream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"DotTiled.Example.Console.tilemap.tmx") + ?? throw new FileLoadException($"DotTiled.Example.Console.tilemap.tmx not found in assembly."); + string tileMapString = new StreamReader(tilemapStream).ReadToEnd(); + using var mapReader = new MapReader(tileMapString, ResolveTileset, ResolveTemplate, ResolveCustomType); var map = mapReader.ReadMap(); // Now do some other stuff with it... @@ -50,7 +53,7 @@ public class Program { // Read a file from assembly // You can use any other source for files, eg. compressed archive, or even file from internet. - using Stream? tilesetStream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"DotTiled.Example.embedded-{source}") + using Stream? tilesetStream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"DotTiled.Example.Console.{source}") ?? throw new FileLoadException($"{source} not found in assembly."); string tilesetString = new StreamReader(tilesetStream).ReadToEnd(); @@ -62,7 +65,7 @@ public class Program // This is pretty similar to above, but instead it loads templates, not tilesets. private static Template ResolveTemplate(string source) { - using Stream? templateStream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"DotTiled.Example.{source}") + using Stream? templateStream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"DotTiled.Example.Console.{source}") ?? throw new FileLoadException($"{source} not found in assembly."); string templateString = new StreamReader(templateStream).ReadToEnd(); diff --git a/src/DotTiled.Examples/DotTiled.Example.Console/embedded-tilemap.tmx b/src/DotTiled.Examples/DotTiled.Example.Console/embedded-tilemap.tmx deleted file mode 100644 index 7a9a294..0000000 --- a/src/DotTiled.Examples/DotTiled.Example.Console/embedded-tilemap.tmx +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAA== - - - diff --git a/src/DotTiled.Examples/DotTiled.Example.Console/embedded-tileset.png b/src/DotTiled.Examples/DotTiled.Example.Console/embedded-tileset.png deleted file mode 100644 index 67b9eeea3c0801c3a7092d76b229e2560ee5d7c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 149 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucK`l=g#}Etu})QSZQ qC$3zWaLH~Bhp(&*Z!NEp7Q<%lsS3|M&zb^tF?hQAxvX - - - From 69f68d5853b294b03d95a7c389cad9f6f7a7ba35 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Fri, 4 Oct 2024 21:22:43 +0200 Subject: [PATCH 11/33] Update version compatibility matrix --- docs/docs/essentials/representation-model.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/essentials/representation-model.md b/docs/docs/essentials/representation-model.md index 88dbdff..9fef3e0 100644 --- a/docs/docs/essentials/representation-model.md +++ b/docs/docs/essentials/representation-model.md @@ -14,4 +14,4 @@ The representation model is designed to be compatible with the latest version of | Tiled version | Compatible DotTiled version(s) | |---------------|--------------------------------| -| 1.11 | 0.1.0, 0.2.0 | \ No newline at end of file +| 1.11 | 0.1.0, 0.2.0, 0.2.1 | \ No newline at end of file From d943a8d8b72038696cc65cd60d522c3b936f99c5 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Fri, 4 Oct 2024 21:25:36 +0200 Subject: [PATCH 12/33] Update version in .csproj --- src/DotTiled/DotTiled.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DotTiled/DotTiled.csproj b/src/DotTiled/DotTiled.csproj index acf3e99..a5d41c3 100644 --- a/src/DotTiled/DotTiled.csproj +++ b/src/DotTiled/DotTiled.csproj @@ -18,7 +18,7 @@ true Copyright © 2024 dcronqvist LICENSE - 0.2.0 + 0.2.1 From 50036075f56006a6b0da71e11c8942e3626f07f4 Mon Sep 17 00:00:00 2001 From: differenceclouds Date: Thu, 14 Nov 2024 09:26:32 -0500 Subject: [PATCH 13/33] "rendersize" -> "tilerendersize" fix to match the XML. --- src/DotTiled/Serialization/Tmx/TmxReaderBase.Tileset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DotTiled/Serialization/Tmx/TmxReaderBase.Tileset.cs b/src/DotTiled/Serialization/Tmx/TmxReaderBase.Tileset.cs index 2fc2540..bc7e0e8 100644 --- a/src/DotTiled/Serialization/Tmx/TmxReaderBase.Tileset.cs +++ b/src/DotTiled/Serialization/Tmx/TmxReaderBase.Tileset.cs @@ -51,7 +51,7 @@ public abstract partial class TmxReaderBase "bottomright" => ObjectAlignment.BottomRight, _ => throw new InvalidOperationException($"Unknown object alignment '{s}'") }).GetValueOr(ObjectAlignment.Unspecified); - var renderSize = _reader.GetOptionalAttributeEnum("rendersize", s => s switch + var renderSize = _reader.GetOptionalAttributeEnum("tilerendersize", s => s switch { "tile" => TileRenderSize.Tile, "grid" => TileRenderSize.Grid, From 35c6ba5002afaeabf22351da52c5ae2d0dabf75f Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 16 Nov 2024 18:35:21 +0100 Subject: [PATCH 14/33] Add storageType parameter to FromEnum, default value is consistent with Tiled --- .../Properties/CustomTypes/CustomEnumDefinition.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/DotTiled/Properties/CustomTypes/CustomEnumDefinition.cs b/src/DotTiled/Properties/CustomTypes/CustomEnumDefinition.cs index 72593d9..b0d6e88 100644 --- a/src/DotTiled/Properties/CustomTypes/CustomEnumDefinition.cs +++ b/src/DotTiled/Properties/CustomTypes/CustomEnumDefinition.cs @@ -51,8 +51,9 @@ public class CustomEnumDefinition : ICustomTypeDefinition /// Creates a custom enum definition from the specified enum type. /// /// + /// The storage type of the custom enum. Defaults to to be consistent with Tiled. /// - public static CustomEnumDefinition FromEnum() where T : Enum + public static CustomEnumDefinition FromEnum(CustomEnumStorageType storageType = CustomEnumStorageType.String) where T : Enum { var type = typeof(T); var isFlags = type.GetCustomAttributes(typeof(FlagsAttribute), false).Length != 0; @@ -60,7 +61,7 @@ public class CustomEnumDefinition : ICustomTypeDefinition return new CustomEnumDefinition { Name = type.Name, - StorageType = CustomEnumStorageType.Int, + StorageType = storageType, Values = Enum.GetNames(type).ToList(), ValueAsFlags = isFlags }; @@ -69,8 +70,10 @@ public class CustomEnumDefinition : ICustomTypeDefinition /// /// Creates a custom enum definition from the specified enum type. /// + /// The enum type to create a custom enum definition from. + /// The storage type of the custom enum. Defaults to to be consistent with Tiled. /// - public static CustomEnumDefinition FromEnum(Type type) + public static CustomEnumDefinition FromEnum(Type type, CustomEnumStorageType storageType = CustomEnumStorageType.String) { if (!type.IsEnum) throw new ArgumentException("Type must be an enum.", nameof(type)); @@ -80,7 +83,7 @@ public class CustomEnumDefinition : ICustomTypeDefinition return new CustomEnumDefinition { Name = type.Name, - StorageType = CustomEnumStorageType.Int, + StorageType = storageType, Values = Enum.GetNames(type).ToList(), ValueAsFlags = isFlags }; From 2e8eaa5a729101c6d9681c7fecab9ad9a03754a3 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 16 Nov 2024 18:35:59 +0100 Subject: [PATCH 15/33] Update FromEnum tests with storage type parameter --- .../CustomTypes/CustomEnumDefinitionTests.cs | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/DotTiled.Tests/UnitTests/Properties/CustomTypes/CustomEnumDefinitionTests.cs b/src/DotTiled.Tests/UnitTests/Properties/CustomTypes/CustomEnumDefinitionTests.cs index c342f77..02b16d2 100644 --- a/src/DotTiled.Tests/UnitTests/Properties/CustomTypes/CustomEnumDefinitionTests.cs +++ b/src/DotTiled.Tests/UnitTests/Properties/CustomTypes/CustomEnumDefinitionTests.cs @@ -14,8 +14,10 @@ public class CustomEnumDefinitionTests private enum TestEnum1 { Value1, Value2, Value3 } - [Fact] - public void FromEnum_Type_WhenTypeIsEnum_ReturnsCustomEnumDefinition() + [Theory] + [InlineData(CustomEnumStorageType.String)] + [InlineData(CustomEnumStorageType.Int)] + public void FromEnum_Type_WhenTypeIsEnum_ReturnsCustomEnumDefinition(CustomEnumStorageType storageType) { // Arrange var type = typeof(TestEnum1); @@ -23,13 +25,13 @@ public class CustomEnumDefinitionTests { ID = 0, Name = "TestEnum1", - StorageType = CustomEnumStorageType.Int, + StorageType = storageType, Values = ["Value1", "Value2", "Value3"], ValueAsFlags = false }; // Act - var result = CustomEnumDefinition.FromEnum(type); + var result = CustomEnumDefinition.FromEnum(type, storageType); // Assert DotTiledAssert.AssertCustomEnumDefinitionEqual(expected, result); @@ -38,8 +40,10 @@ public class CustomEnumDefinitionTests [Flags] private enum TestEnum2 { Value1, Value2, Value3 } - [Fact] - public void FromEnum_Type_WhenEnumIsFlags_ReturnsCustomEnumDefinition() + [Theory] + [InlineData(CustomEnumStorageType.String)] + [InlineData(CustomEnumStorageType.Int)] + public void FromEnum_Type_WhenEnumIsFlags_ReturnsCustomEnumDefinition(CustomEnumStorageType storageType) { // Arrange var type = typeof(TestEnum2); @@ -47,53 +51,57 @@ public class CustomEnumDefinitionTests { ID = 0, Name = "TestEnum2", - StorageType = CustomEnumStorageType.Int, + StorageType = storageType, Values = ["Value1", "Value2", "Value3"], ValueAsFlags = true }; // Act - var result = CustomEnumDefinition.FromEnum(type); + var result = CustomEnumDefinition.FromEnum(type, storageType); // Assert DotTiledAssert.AssertCustomEnumDefinitionEqual(expected, result); } - [Fact] - public void FromEnum_T_WhenTypeIsEnum_ReturnsCustomEnumDefinition() + [Theory] + [InlineData(CustomEnumStorageType.String)] + [InlineData(CustomEnumStorageType.Int)] + public void FromEnum_T_WhenTypeIsEnum_ReturnsCustomEnumDefinition(CustomEnumStorageType storageType) { // Arrange var expected = new CustomEnumDefinition { ID = 0, Name = "TestEnum1", - StorageType = CustomEnumStorageType.Int, + StorageType = storageType, Values = ["Value1", "Value2", "Value3"], ValueAsFlags = false }; // Act - var result = CustomEnumDefinition.FromEnum(); + var result = CustomEnumDefinition.FromEnum(storageType); // Assert DotTiledAssert.AssertCustomEnumDefinitionEqual(expected, result); } - [Fact] - public void FromEnum_T_WhenEnumIsFlags_ReturnsCustomEnumDefinition() + [Theory] + [InlineData(CustomEnumStorageType.String)] + [InlineData(CustomEnumStorageType.Int)] + public void FromEnum_T_WhenEnumIsFlags_ReturnsCustomEnumDefinition(CustomEnumStorageType storageType) { // Arrange var expected = new CustomEnumDefinition { ID = 0, Name = "TestEnum2", - StorageType = CustomEnumStorageType.Int, + StorageType = storageType, Values = ["Value1", "Value2", "Value3"], ValueAsFlags = true }; // Act - var result = CustomEnumDefinition.FromEnum(); + var result = CustomEnumDefinition.FromEnum(storageType); // Assert DotTiledAssert.AssertCustomEnumDefinitionEqual(expected, result); From 666a3433e315ba4d96f5aa986d017b83f8625215 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 16 Nov 2024 18:48:15 +0100 Subject: [PATCH 16/33] Update docs section on CustomEnumDefinitions with new storage type parameter info --- docs/docs/essentials/custom-properties.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/docs/essentials/custom-properties.md b/docs/docs/essentials/custom-properties.md index 9fc19aa..addffd3 100644 --- a/docs/docs/essentials/custom-properties.md +++ b/docs/docs/essentials/custom-properties.md @@ -138,7 +138,7 @@ The equivalent definition in DotTiled would look like the following: var entityTypeDefinition = new CustomEnumDefinition { Name = "EntityType", - StorageType = CustomEnumStorageType.Int, + StorageType = CustomEnumStorageType.String, ValueAsFlags = false, Values = [ "Bomb", @@ -149,7 +149,7 @@ var entityTypeDefinition = new CustomEnumDefinition }; ``` -Similarly to custom class definitions, you can also automatically generate custom enum definitions from C# enums. This is done by using the method, or one of its overloads. This method will generate a from a given C# enum, and you can then use this definition when loading your maps. +Similarly to custom class definitions, you can also automatically generate custom enum definitions from C# enums. This is done by using the method, or one of its overloads. This method will generate a from a given C# enum, and you can then use this definition when loading your maps. ```csharp enum EntityType @@ -171,6 +171,9 @@ The generated custom enum definition will be identical to the one defined manual For enum definitions, the can be used to indicate that the enum should be treated as a flags enum. This will make it so the enum definition will have `ValueAsFlags = true` and the enum values will be treated as flags when working with them in DotTiled. +> [!NOTE] +> Tiled supports enums which can store their values as either strings or integers, and depending on the storage type you have specified in Tiled, you must make sure to have the same storage type in your . This can be done by setting the `StorageType` property to either `CustomEnumStorageType.String` or `CustomEnumStorageType.Int` when creating the definition, or by passing the storage type as an argument to the method. To be consistent with Tiled, will default to `CustomEnumStorageType.String` for the storage type parameter. + ## Mapping properties to C# classes or enums So far, we have only discussed how to define custom property types in DotTiled, and why they are needed. However, the most important part is how you can map properties inside your maps to their corresponding C# classes or enums. From dcdceb8b78ef39cf444821daaa9850c3783bd20e Mon Sep 17 00:00:00 2001 From: dcronqvist Date: Sat, 16 Nov 2024 19:02:50 +0100 Subject: [PATCH 17/33] Fix directory name of pull request templates --- .../dev_template.md | 0 .../master_template.md | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename .github/{pull_request_template => PULL_REQUEST_TEMPLATE}/dev_template.md (100%) rename .github/{pull_request_template => PULL_REQUEST_TEMPLATE}/master_template.md (100%) diff --git a/.github/pull_request_template/dev_template.md b/.github/PULL_REQUEST_TEMPLATE/dev_template.md similarity index 100% rename from .github/pull_request_template/dev_template.md rename to .github/PULL_REQUEST_TEMPLATE/dev_template.md diff --git a/.github/pull_request_template/master_template.md b/.github/PULL_REQUEST_TEMPLATE/master_template.md similarity index 100% rename from .github/pull_request_template/master_template.md rename to .github/PULL_REQUEST_TEMPLATE/master_template.md From 837f58bf68053f8bfddd843290b19bb7fad48de0 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 16 Nov 2024 19:26:14 +0100 Subject: [PATCH 18/33] Remove claims about being able to save Tiled maps --- README.md | 2 +- src/DotTiled/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fa181c4..1e9dd15 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ -DotTiled is a simple and easy-to-use library for loading, saving, and managing [Tiled maps and tilesets](https://mapeditor.org) in your .NET projects. After [TiledCS](https://github.com/TheBoneJarmer/TiledCS) unfortunately became unmaintained (since 2022), I aimed to create a new library that could fill its shoes. DotTiled is the result of that effort. +DotTiled is a simple and easy-to-use library for loading [Tiled maps and tilesets](https://mapeditor.org) in your .NET projects. After [TiledCS](https://github.com/TheBoneJarmer/TiledCS) unfortunately became unmaintained (since 2022), I aimed to create a new library that could fill its shoes. DotTiled is the result of that effort. DotTiled is designed to be a lightweight and efficient library that provides a simple API for loading and managing Tiled maps and tilesets. It is built with performance in mind and aims to be as fast and memory-efficient as possible. diff --git a/src/DotTiled/README.md b/src/DotTiled/README.md index 692a997..924b9ee 100644 --- a/src/DotTiled/README.md +++ b/src/DotTiled/README.md @@ -1,6 +1,6 @@ # 📚 DotTiled -DotTiled is a simple and easy-to-use library for loading, saving, and managing [Tiled maps and tilesets](https://mapeditor.org) in your .NET projects. After [TiledCS](https://github.com/TheBoneJarmer/TiledCS) unfortunately became unmaintained (since 2022), I aimed to create a new library that could fill its shoes. DotTiled is the result of that effort. +DotTiled is a simple and easy-to-use library for loading [Tiled maps and tilesets](https://mapeditor.org) in your .NET projects. After [TiledCS](https://github.com/TheBoneJarmer/TiledCS) unfortunately became unmaintained (since 2022), I aimed to create a new library that could fill its shoes. DotTiled is the result of that effort. DotTiled is designed to be a lightweight and efficient library that provides a simple API for loading and managing Tiled maps and tilesets. It is built with performance in mind and aims to be as fast and memory-efficient as possible. From feb4375cd596f0645c86e6703f613baecfaeea4c Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 16 Nov 2024 19:26:44 +0100 Subject: [PATCH 19/33] Make sure docs index.md depends on README.md as it is copied --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 30ddf7e..c83c9fa 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ docs-serve: docs/index.md docs-build: docs/index.md docfx docs/docfx.json -docs/index.md: +docs/index.md: README.md cp README.md docs/index.md lint: From 8c9068cc971a90395bd3a8934afab74f3a0f3820 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 16 Nov 2024 21:14:23 +0100 Subject: [PATCH 20/33] Make custom types optional --- .../DotTiled.Example.Console/Program.cs | 4 +- .../DotTiled.Example.Godot/MapParser.cs | 5 +- .../UnitTests/Serialization/LoaderTests.cs | 9 ++- .../UnitTests/Serialization/MapReaderTests.cs | 9 ++- .../Serialization/Tmj/TmjMapReaderTests.cs | 9 ++- .../Serialization/Tmx/TmxMapReaderTests.cs | 9 ++- src/DotTiled/Serialization/Helpers.cs | 25 +++++- src/DotTiled/Serialization/Loader.cs | 10 ++- src/DotTiled/Serialization/MapReader.cs | 4 +- src/DotTiled/Serialization/TemplateReader.cs | 4 +- src/DotTiled/Serialization/TilesetReader.cs | 4 +- .../Serialization/Tmj/TjTemplateReader.cs | 2 +- .../Serialization/Tmj/TmjMapReader.cs | 2 +- .../Tmj/TmjReaderBase.Properties.cs | 81 ++++++++++++++++--- .../Serialization/Tmj/TmjReaderBase.cs | 4 +- .../Serialization/Tmj/TsjTilesetReader.cs | 2 +- .../Serialization/Tmx/TmxMapReader.cs | 2 +- .../Tmx/TmxReaderBase.Properties.cs | 55 ++++++++++--- .../Serialization/Tmx/TmxReaderBase.cs | 4 +- .../Serialization/Tmx/TsxTilesetReader.cs | 2 +- .../Serialization/Tmx/TxTemplateReader.cs | 2 +- 21 files changed, 189 insertions(+), 59 deletions(-) diff --git a/src/DotTiled.Examples/DotTiled.Example.Console/Program.cs b/src/DotTiled.Examples/DotTiled.Example.Console/Program.cs index 9dba3df..baf36ea 100644 --- a/src/DotTiled.Examples/DotTiled.Example.Console/Program.cs +++ b/src/DotTiled.Examples/DotTiled.Example.Console/Program.cs @@ -73,12 +73,12 @@ public class Program return templateReader.ReadTemplate(); } - private static ICustomTypeDefinition ResolveCustomType(string name) + private static Optional ResolveCustomType(string name) { ICustomTypeDefinition[] allDefinedTypes = [ new CustomClassDefinition() { Name = "a" }, ]; - return allDefinedTypes.FirstOrDefault(type => type.Name == name) ?? throw new InvalidOperationException(); + return allDefinedTypes.FirstOrDefault(ctd => ctd.Name == name) is ICustomTypeDefinition ctd ? new Optional(ctd) : Optional.Empty; } } diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/MapParser.cs b/src/DotTiled.Examples/DotTiled.Example.Godot/MapParser.cs index 5ad960b..8319d00 100644 --- a/src/DotTiled.Examples/DotTiled.Example.Godot/MapParser.cs +++ b/src/DotTiled.Examples/DotTiled.Example.Godot/MapParser.cs @@ -1,4 +1,3 @@ -using System; using System.Globalization; using System.Linq; using DotTiled.Serialization; @@ -57,12 +56,12 @@ public partial class MapParser : Node2D return templateReader.ReadTemplate(); } - private static ICustomTypeDefinition ResolveCustomType(string name) + private static Optional ResolveCustomType(string name) { ICustomTypeDefinition[] allDefinedTypes = [ new CustomClassDefinition() { Name = "a" }, ]; - return allDefinedTypes.FirstOrDefault(type => type.Name == name) ?? throw new InvalidOperationException(); + return allDefinedTypes.FirstOrDefault(ctd => ctd.Name == name) is ICustomTypeDefinition ctd ? new Optional(ctd) : Optional.Empty; } } diff --git a/src/DotTiled.Tests/UnitTests/Serialization/LoaderTests.cs b/src/DotTiled.Tests/UnitTests/Serialization/LoaderTests.cs index d54ab9f..7edff12 100644 --- a/src/DotTiled.Tests/UnitTests/Serialization/LoaderTests.cs +++ b/src/DotTiled.Tests/UnitTests/Serialization/LoaderTests.cs @@ -246,7 +246,7 @@ public class LoaderTests } [Fact] - public void LoadMap_MapHasClassAndLoaderHasNoCustomTypes_ThrowsException() + public void LoadMap_MapHasClassAndLoaderHasNoCustomTypes_ReturnsMapWithEmptyProperties() { // Arrange var resourceReader = Substitute.For(); @@ -270,8 +270,11 @@ public class LoaderTests var customTypeDefinitions = Enumerable.Empty(); var loader = new Loader(resourceReader, resourceCache, customTypeDefinitions); - // Act & Assert - Assert.Throws(() => loader.LoadMap("map.tmx")); + // Act + var result = loader.LoadMap("map.tmx"); + + // Assert + DotTiledAssert.AssertProperties([], result.Properties); } [Fact] diff --git a/src/DotTiled.Tests/UnitTests/Serialization/MapReaderTests.cs b/src/DotTiled.Tests/UnitTests/Serialization/MapReaderTests.cs index 885f57e..dd6bcca 100644 --- a/src/DotTiled.Tests/UnitTests/Serialization/MapReaderTests.cs +++ b/src/DotTiled.Tests/UnitTests/Serialization/MapReaderTests.cs @@ -32,9 +32,14 @@ public partial class MapReaderTests using var tilesetReader = new TilesetReader(tilesetString, ResolveTileset, ResolveTemplate, ResolveCustomType); return tilesetReader.ReadTileset(); } - ICustomTypeDefinition ResolveCustomType(string name) + Optional ResolveCustomType(string name) { - return customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name)!; + if (customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name) is ICustomTypeDefinition ctd) + { + return new Optional(ctd); + } + + return Optional.Empty; } using var mapReader = new MapReader(mapString, ResolveTileset, ResolveTemplate, ResolveCustomType); diff --git a/src/DotTiled.Tests/UnitTests/Serialization/Tmj/TmjMapReaderTests.cs b/src/DotTiled.Tests/UnitTests/Serialization/Tmj/TmjMapReaderTests.cs index a896a48..aa289f6 100644 --- a/src/DotTiled.Tests/UnitTests/Serialization/Tmj/TmjMapReaderTests.cs +++ b/src/DotTiled.Tests/UnitTests/Serialization/Tmj/TmjMapReaderTests.cs @@ -28,9 +28,14 @@ public partial class TmjMapReaderTests using var tilesetReader = new TsjTilesetReader(tilesetJson, ResolveTileset, ResolveTemplate, ResolveCustomType); return tilesetReader.ReadTileset(); } - ICustomTypeDefinition ResolveCustomType(string name) + Optional ResolveCustomType(string name) { - return customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name)!; + if (customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name) is ICustomTypeDefinition ctd) + { + return new Optional(ctd); + } + + return Optional.Empty; } using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate, ResolveCustomType); diff --git a/src/DotTiled.Tests/UnitTests/Serialization/Tmx/TmxMapReaderTests.cs b/src/DotTiled.Tests/UnitTests/Serialization/Tmx/TmxMapReaderTests.cs index b6e5813..82dba07 100644 --- a/src/DotTiled.Tests/UnitTests/Serialization/Tmx/TmxMapReaderTests.cs +++ b/src/DotTiled.Tests/UnitTests/Serialization/Tmx/TmxMapReaderTests.cs @@ -28,9 +28,14 @@ public partial class TmxMapReaderTests using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTileset, ResolveTemplate, ResolveCustomType); return tilesetReader.ReadTileset(); } - ICustomTypeDefinition ResolveCustomType(string name) + Optional ResolveCustomType(string name) { - return customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name)!; + if (customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name) is ICustomTypeDefinition ctd) + { + return new Optional(ctd); + } + + return Optional.Empty; } using var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate, ResolveCustomType); diff --git a/src/DotTiled/Serialization/Helpers.cs b/src/DotTiled/Serialization/Helpers.cs index e784b80..d716293 100644 --- a/src/DotTiled/Serialization/Helpers.cs +++ b/src/DotTiled/Serialization/Helpers.cs @@ -86,13 +86,16 @@ internal static partial class Helpers }; } - internal static List ResolveClassProperties(string className, Func customTypeResolver) + internal static List ResolveClassProperties(string className, Func> customTypeResolver) { if (string.IsNullOrWhiteSpace(className)) return null; var customType = customTypeResolver(className) ?? throw new InvalidOperationException($"Could not resolve custom type '{className}'."); - if (customType is not CustomClassDefinition ccd) + if (!customType.HasValue) + return null; + + if (customType.Value is not CustomClassDefinition ccd) throw new InvalidOperationException($"Custom type '{className}' is not a class."); return CreateInstanceOfCustomClass(ccd, customTypeResolver); @@ -100,17 +103,31 @@ internal static partial class Helpers internal static List CreateInstanceOfCustomClass( CustomClassDefinition customClassDefinition, - Func customTypeResolver) + Func> customTypeResolver) { return customClassDefinition.Members.Select(x => { if (x is ClassProperty cp) { + var resolvedType = customTypeResolver(cp.PropertyType); + if (!resolvedType.HasValue) + { + return new ClassProperty + { + Name = cp.Name, + PropertyType = cp.PropertyType, + Value = [] + }; + } + + if (resolvedType.Value is not CustomClassDefinition ccd) + throw new InvalidOperationException($"Custom type '{cp.PropertyType}' is not a class."); + return new ClassProperty { Name = cp.Name, PropertyType = cp.PropertyType, - Value = CreateInstanceOfCustomClass((CustomClassDefinition)customTypeResolver(cp.PropertyType), customTypeResolver) + Value = CreateInstanceOfCustomClass(ccd, customTypeResolver) }; } diff --git a/src/DotTiled/Serialization/Loader.cs b/src/DotTiled/Serialization/Loader.cs index 02e258c..dd4fc2a 100644 --- a/src/DotTiled/Serialization/Loader.cs +++ b/src/DotTiled/Serialization/Loader.cs @@ -12,7 +12,7 @@ public class Loader { private readonly IResourceReader _resourceReader; private readonly IResourceCache _resourceCache; - private readonly IDictionary _customTypeDefinitions; + private readonly Dictionary _customTypeDefinitions; /// /// Initializes a new instance of the class with the given , , and . @@ -114,5 +114,11 @@ public class Loader return templateReader.ReadTemplate(); }); - private ICustomTypeDefinition CustomTypeResolver(string name) => _customTypeDefinitions[name]; + private Optional CustomTypeResolver(string name) + { + if (_customTypeDefinitions.TryGetValue(name, out var customTypeDefinition)) + return new Optional(customTypeDefinition); + + return Optional.Empty; + } } diff --git a/src/DotTiled/Serialization/MapReader.cs b/src/DotTiled/Serialization/MapReader.cs index d202e8f..5a6f6a7 100644 --- a/src/DotTiled/Serialization/MapReader.cs +++ b/src/DotTiled/Serialization/MapReader.cs @@ -14,7 +14,7 @@ public class MapReader : IMapReader // External resolvers private readonly Func _externalTilesetResolver; private readonly Func _externalTemplateResolver; - private readonly Func _customTypeResolver; + private readonly Func> _customTypeResolver; private readonly StringReader _mapStringReader; private readonly XmlReader _xmlReader; @@ -33,7 +33,7 @@ public class MapReader : IMapReader string map, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) + Func> customTypeResolver) { _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); _externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver)); diff --git a/src/DotTiled/Serialization/TemplateReader.cs b/src/DotTiled/Serialization/TemplateReader.cs index bf210c0..d44da3e 100644 --- a/src/DotTiled/Serialization/TemplateReader.cs +++ b/src/DotTiled/Serialization/TemplateReader.cs @@ -14,7 +14,7 @@ public class TemplateReader : ITemplateReader // External resolvers private readonly Func _externalTilesetResolver; private readonly Func _externalTemplateResolver; - private readonly Func _customTypeResolver; + private readonly Func> _customTypeResolver; private readonly StringReader _templateStringReader; private readonly XmlReader _xmlReader; @@ -33,7 +33,7 @@ public class TemplateReader : ITemplateReader string template, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) + Func> customTypeResolver) { _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); _externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver)); diff --git a/src/DotTiled/Serialization/TilesetReader.cs b/src/DotTiled/Serialization/TilesetReader.cs index 180d269..3d7c83a 100644 --- a/src/DotTiled/Serialization/TilesetReader.cs +++ b/src/DotTiled/Serialization/TilesetReader.cs @@ -14,7 +14,7 @@ public class TilesetReader : ITilesetReader // External resolvers private readonly Func _externalTilesetResolver; private readonly Func _externalTemplateResolver; - private readonly Func _customTypeResolver; + private readonly Func> _customTypeResolver; private readonly StringReader _tilesetStringReader; private readonly XmlReader _xmlReader; @@ -33,7 +33,7 @@ public class TilesetReader : ITilesetReader string tileset, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) + Func> customTypeResolver) { _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); _externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver)); diff --git a/src/DotTiled/Serialization/Tmj/TjTemplateReader.cs b/src/DotTiled/Serialization/Tmj/TjTemplateReader.cs index 792bb73..ef80970 100644 --- a/src/DotTiled/Serialization/Tmj/TjTemplateReader.cs +++ b/src/DotTiled/Serialization/Tmj/TjTemplateReader.cs @@ -19,7 +19,7 @@ public class TjTemplateReader : TmjReaderBase, ITemplateReader string jsonString, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) : base( + Func> customTypeResolver) : base( jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver) { } diff --git a/src/DotTiled/Serialization/Tmj/TmjMapReader.cs b/src/DotTiled/Serialization/Tmj/TmjMapReader.cs index 8ceb211..cffa036 100644 --- a/src/DotTiled/Serialization/Tmj/TmjMapReader.cs +++ b/src/DotTiled/Serialization/Tmj/TmjMapReader.cs @@ -19,7 +19,7 @@ public class TmjMapReader : TmjReaderBase, IMapReader string jsonString, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) : base( + Func> customTypeResolver) : base( jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver) { } diff --git a/src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs b/src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs index b877382..1ffff1c 100644 --- a/src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs +++ b/src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -63,21 +64,38 @@ public abstract partial class TmjReaderBase var propertyType = element.GetRequiredProperty("propertytype"); var customTypeDef = _customTypeResolver(propertyType); - if (customTypeDef is CustomClassDefinition ccd) + // If the custom class definition is not found, + // we assume an empty class definition. + if (!customTypeDef.HasValue) { - var propsInType = Helpers.CreateInstanceOfCustomClass(ccd, _customTypeResolver); - var props = element.GetOptionalPropertyCustom>("value", e => ReadPropertiesInsideClass(e, ccd)).GetValueOr([]); - var mergedProps = Helpers.MergeProperties(propsInType, props); + if (!element.TryGetProperty("value", out var valueElement)) + return new ClassProperty { Name = name, PropertyType = propertyType, Value = [] }; return new ClassProperty { Name = name, PropertyType = propertyType, - Value = mergedProps + Value = ReadPropertiesInsideClass(valueElement, new CustomClassDefinition + { + Name = propertyType, + Members = [] + }) }; } - throw new JsonException($"Unknown custom class '{propertyType}'."); + if (customTypeDef.Value is not CustomClassDefinition ccd) + throw new JsonException($"Custom type {propertyType} is not a class."); + + var propsInType = Helpers.CreateInstanceOfCustomClass(ccd, _customTypeResolver); + var props = element.GetOptionalPropertyCustom>("value", e => ReadPropertiesInsideClass(e, ccd)).GetValueOr([]); + var mergedProps = Helpers.MergeProperties(propsInType, props); + + return new ClassProperty + { + Name = name, + PropertyType = propertyType, + Value = mergedProps + }; } internal List ReadPropertiesInsideClass( @@ -91,6 +109,33 @@ public abstract partial class TmjReaderBase if (!element.TryGetProperty(prop.Name, out var propElement)) continue; + if (prop is ClassProperty classProp) + { + var resolvedCustomType = _customTypeResolver(classProp.PropertyType); + if (!resolvedCustomType.HasValue) + { + resultingProps.Add(new ClassProperty + { + Name = classProp.Name, + PropertyType = classProp.PropertyType, + Value = [] + }); + continue; + } + + if (resolvedCustomType.Value is not CustomClassDefinition ccd) + throw new JsonException($"Custom type '{classProp.PropertyType}' is not a class."); + + var readProps = ReadPropertiesInsideClass(propElement, ccd); + resultingProps.Add(new ClassProperty + { + Name = classProp.Name, + PropertyType = classProp.PropertyType, + Value = readProps + }); + continue; + } + IProperty property = prop.Type switch { PropertyType.String => new StringProperty { Name = prop.Name, Value = propElement.GetValueAs() }, @@ -100,8 +145,8 @@ public abstract partial class TmjReaderBase PropertyType.Color => new ColorProperty { Name = prop.Name, Value = Color.Parse(propElement.GetValueAs(), CultureInfo.InvariantCulture) }, PropertyType.File => new FileProperty { Name = prop.Name, Value = propElement.GetValueAs() }, PropertyType.Object => new ObjectProperty { Name = prop.Name, Value = propElement.GetValueAs() }, - PropertyType.Class => new ClassProperty { Name = prop.Name, PropertyType = ((ClassProperty)prop).PropertyType, Value = ReadPropertiesInsideClass(propElement, (CustomClassDefinition)_customTypeResolver(((ClassProperty)prop).PropertyType)) }, PropertyType.Enum => ReadEnumProperty(propElement), + PropertyType.Class => throw new NotImplementedException("Class properties should be handled elsewhere"), _ => throw new JsonException("Invalid property type") }; @@ -115,7 +160,7 @@ public abstract partial class TmjReaderBase { var name = element.GetRequiredProperty("name"); var propertyType = element.GetRequiredProperty("propertytype"); - var typeInXml = element.GetOptionalPropertyParseable("type", (s) => s switch + var typeInJson = element.GetOptionalPropertyParseable("type", (s) => s switch { "string" => PropertyType.String, "int" => PropertyType.Int, @@ -123,8 +168,24 @@ public abstract partial class TmjReaderBase }).GetValueOr(PropertyType.String); var customTypeDef = _customTypeResolver(propertyType); - if (customTypeDef is not CustomEnumDefinition ced) - throw new JsonException($"Unknown custom enum '{propertyType}'. Enums must be defined"); + if (!customTypeDef.HasValue) + { + if (typeInJson == PropertyType.String) + { + var value = element.GetRequiredProperty("value"); + var values = value.Split(',').Select(v => v.Trim()).ToHashSet(); + return new EnumProperty { Name = name, PropertyType = propertyType, Value = values }; + } + else + { + var value = element.GetRequiredProperty("value"); + var values = new HashSet { value.ToString(CultureInfo.InvariantCulture) }; + return new EnumProperty { Name = name, PropertyType = propertyType, Value = values }; + } + } + + if (customTypeDef.Value is not CustomEnumDefinition ced) + throw new JsonException($"Custom type '{propertyType}' is not an enum."); if (ced.StorageType == CustomEnumStorageType.String) { diff --git a/src/DotTiled/Serialization/Tmj/TmjReaderBase.cs b/src/DotTiled/Serialization/Tmj/TmjReaderBase.cs index f32100a..c63b913 100644 --- a/src/DotTiled/Serialization/Tmj/TmjReaderBase.cs +++ b/src/DotTiled/Serialization/Tmj/TmjReaderBase.cs @@ -13,7 +13,7 @@ public abstract partial class TmjReaderBase : IDisposable // External resolvers private readonly Func _externalTilesetResolver; private readonly Func _externalTemplateResolver; - private readonly Func _customTypeResolver; + private readonly Func> _customTypeResolver; /// /// The root element of the JSON document being read. @@ -34,7 +34,7 @@ public abstract partial class TmjReaderBase : IDisposable string jsonString, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) + Func> customTypeResolver) { RootElement = JsonDocument.Parse(jsonString ?? throw new ArgumentNullException(nameof(jsonString))).RootElement; _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); diff --git a/src/DotTiled/Serialization/Tmj/TsjTilesetReader.cs b/src/DotTiled/Serialization/Tmj/TsjTilesetReader.cs index 5816c8a..87f4b59 100644 --- a/src/DotTiled/Serialization/Tmj/TsjTilesetReader.cs +++ b/src/DotTiled/Serialization/Tmj/TsjTilesetReader.cs @@ -19,7 +19,7 @@ public class TsjTilesetReader : TmjReaderBase, ITilesetReader string jsonString, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) : base( + Func> customTypeResolver) : base( jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver) { } diff --git a/src/DotTiled/Serialization/Tmx/TmxMapReader.cs b/src/DotTiled/Serialization/Tmx/TmxMapReader.cs index 6029481..e993a4c 100644 --- a/src/DotTiled/Serialization/Tmx/TmxMapReader.cs +++ b/src/DotTiled/Serialization/Tmx/TmxMapReader.cs @@ -16,7 +16,7 @@ public class TmxMapReader : TmxReaderBase, IMapReader XmlReader reader, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) : base( + Func> customTypeResolver) : base( reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver) { } diff --git a/src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs b/src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs index bfdb93d..4aec221 100644 --- a/src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs +++ b/src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Xml; @@ -66,25 +67,35 @@ public abstract partial class TmxReaderBase var propertyType = _reader.GetRequiredAttribute("propertytype"); var customTypeDef = _customTypeResolver(propertyType); - if (customTypeDef is CustomClassDefinition ccd) + // If the custom class definition is not found, + // we assume an empty class definition. + if (!customTypeDef.HasValue) { if (!_reader.IsEmptyElement) { _reader.ReadStartElement("property"); - var propsInType = Helpers.CreateInstanceOfCustomClass(ccd, _customTypeResolver); var props = ReadProperties(); - var mergedProps = Helpers.MergeProperties(propsInType, props); _reader.ReadEndElement(); - return new ClassProperty { Name = name, PropertyType = propertyType, Value = mergedProps }; - } - else - { - var propsInType = Helpers.CreateInstanceOfCustomClass(ccd, _customTypeResolver); - return new ClassProperty { Name = name, PropertyType = propertyType, Value = propsInType }; + return new ClassProperty { Name = name, PropertyType = propertyType, Value = props }; } + + return new ClassProperty { Name = name, PropertyType = propertyType, Value = [] }; } - throw new XmlException($"Unkonwn custom class definition: {propertyType}"); + if (customTypeDef.Value is not CustomClassDefinition ccd) + throw new XmlException($"Custom type {propertyType} is not a class."); + + var propsInType = Helpers.CreateInstanceOfCustomClass(ccd, _customTypeResolver); + if (!_reader.IsEmptyElement) + { + _reader.ReadStartElement("property"); + var props = ReadProperties(); + var mergedProps = Helpers.MergeProperties(propsInType, props); + _reader.ReadEndElement(); + return new ClassProperty { Name = name, PropertyType = propertyType, Value = mergedProps }; + } + + return new ClassProperty { Name = name, PropertyType = propertyType, Value = propsInType }; } internal EnumProperty ReadEnumProperty() @@ -99,8 +110,26 @@ public abstract partial class TmxReaderBase }) ?? PropertyType.String; var customTypeDef = _customTypeResolver(propertyType); - if (customTypeDef is not CustomEnumDefinition ced) - throw new XmlException($"Unknown custom enum definition: {propertyType}. Enums must be defined"); + // If the custom enum definition is not found, + // we assume an empty enum definition. + if (!customTypeDef.HasValue) + { + if (typeInXml == PropertyType.String) + { + var value = _reader.GetRequiredAttribute("value"); + var values = value.Split(',').Select(v => v.Trim()).ToHashSet(); + return new EnumProperty { Name = name, PropertyType = propertyType, Value = values }; + } + else + { + var value = _reader.GetRequiredAttributeParseable("value"); + var values = new HashSet { value.ToString(CultureInfo.InvariantCulture) }; + return new EnumProperty { Name = name, PropertyType = propertyType, Value = values }; + } + } + + if (customTypeDef.Value is not CustomEnumDefinition ced) + throw new XmlException($"Custom defined type {propertyType} is not an enum."); if (ced.StorageType == CustomEnumStorageType.String) { @@ -144,6 +173,6 @@ public abstract partial class TmxReaderBase } } - throw new XmlException($"Unknown custom enum storage type: {ced.StorageType}"); + throw new XmlException($"Unable to read enum property {name} with type {propertyType}"); } } diff --git a/src/DotTiled/Serialization/Tmx/TmxReaderBase.cs b/src/DotTiled/Serialization/Tmx/TmxReaderBase.cs index e30af82..e3fe2d0 100644 --- a/src/DotTiled/Serialization/Tmx/TmxReaderBase.cs +++ b/src/DotTiled/Serialization/Tmx/TmxReaderBase.cs @@ -11,7 +11,7 @@ public abstract partial class TmxReaderBase : IDisposable // External resolvers private readonly Func _externalTilesetResolver; private readonly Func _externalTemplateResolver; - private readonly Func _customTypeResolver; + private readonly Func> _customTypeResolver; private readonly XmlReader _reader; private bool disposedValue; @@ -28,7 +28,7 @@ public abstract partial class TmxReaderBase : IDisposable XmlReader reader, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) + Func> customTypeResolver) { _reader = reader ?? throw new ArgumentNullException(nameof(reader)); _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); diff --git a/src/DotTiled/Serialization/Tmx/TsxTilesetReader.cs b/src/DotTiled/Serialization/Tmx/TsxTilesetReader.cs index f0dbcc9..195f767 100644 --- a/src/DotTiled/Serialization/Tmx/TsxTilesetReader.cs +++ b/src/DotTiled/Serialization/Tmx/TsxTilesetReader.cs @@ -16,7 +16,7 @@ public class TsxTilesetReader : TmxReaderBase, ITilesetReader XmlReader reader, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) : base( + Func> customTypeResolver) : base( reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver) { } diff --git a/src/DotTiled/Serialization/Tmx/TxTemplateReader.cs b/src/DotTiled/Serialization/Tmx/TxTemplateReader.cs index 72da9dc..8a9d3b1 100644 --- a/src/DotTiled/Serialization/Tmx/TxTemplateReader.cs +++ b/src/DotTiled/Serialization/Tmx/TxTemplateReader.cs @@ -16,7 +16,7 @@ public class TxTemplateReader : TmxReaderBase, ITemplateReader XmlReader reader, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) : base( + Func> customTypeResolver) : base( reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver) { } From e553c8e05a8234eb3c60b8e0c477103f0d2f4bf2 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 16 Nov 2024 21:33:06 +0100 Subject: [PATCH 21/33] Update custom type documentation with optionality disclaimer --- docs/docs/essentials/custom-properties.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/docs/essentials/custom-properties.md b/docs/docs/essentials/custom-properties.md index addffd3..e89b3d5 100644 --- a/docs/docs/essentials/custom-properties.md +++ b/docs/docs/essentials/custom-properties.md @@ -73,7 +73,10 @@ In addition to these primitive property types, [Tiled also supports more complex Tiled allows you to define custom property types that can be used in your maps. These custom property types can be of type `class` or `enum`. DotTiled supports custom property types by allowing you to define the equivalent in C#. This section will guide you through how to define custom property types in DotTiled and how to map properties in loaded maps to C# classes or enums. > [!NOTE] -> In the future, DotTiled could provide a way to configure the use of custom property types such that they aren't necessary to be defined, given that you have set the `Resolve object types and properties` setting in Tiled. +> While custom types are powerful, they will incur a bit of overhead as you attempt to sync them between Tiled and DotTiled. Defining custom types is recommended, but not necessary for simple use cases as Tiled supports arbitrary strings as classes. + +> [!IMPORTANT] +> If you choose to use custom types in your maps, but don't define them properly in DotTiled, you may get inconsistencies between the map in Tiled and the loaded map with DotTiled. If you still want to use custom types in Tiled without having to define them in DotTiled, it is recommended to set the `Resolve object types and properties` setting in Tiled to `true`. This will make Tiled resolve the custom types for you, but it will still require you to define the custom types in DotTiled if you want to access the properties in a type-safe manner. ### Class properties From 54bc13215400199ddd52a8701cef2fc02724916c Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sun, 17 Nov 2024 08:59:02 +0100 Subject: [PATCH 22/33] Add flipping flags parsing/clearing to tile objects --- .../map-with-flippingflags.cs | 31 ++++++++++++++- .../map-with-flippingflags.tmj | 39 ++++++++++++++++++- .../map-with-flippingflags.tmx | 6 ++- src/DotTiled/Layers/Objects/TileObject.cs | 5 +++ .../Tmj/TmjReaderBase.ObjectLayer.cs | 6 ++- .../Tmx/TmxReaderBase.ObjectLayer.cs | 7 +++- 6 files changed, 87 insertions(+), 7 deletions(-) diff --git a/src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/map-with-flippingflags.cs b/src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/map-with-flippingflags.cs index 0777e3f..aef25d3 100644 --- a/src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/map-with-flippingflags.cs +++ b/src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/map-with-flippingflags.cs @@ -20,8 +20,8 @@ public partial class TestData BackgroundColor = Color.Parse("#00000000", CultureInfo.InvariantCulture), Version = "1.10", TiledVersion = "1.11.0", - NextLayerID = 2, - NextObjectID = 1, + NextLayerID = 3, + NextObjectID = 3, Tilesets = [ new Tileset { @@ -68,6 +68,33 @@ public partial class TestData FlippingFlags.FlippedHorizontally, FlippingFlags.FlippedHorizontally, FlippingFlags.FlippedHorizontally, FlippingFlags.FlippedHorizontally, FlippingFlags.None ]) } + }, + new ObjectLayer + { + ID = 2, + Name = "Object Layer 1", + Objects = [ + new TileObject + { + ID = 1, + GID = 21, + X = 80.0555f, + Y = 48.3887f, + Width = 32, + Height = 32, + FlippingFlags = FlippingFlags.FlippedHorizontally + }, + new TileObject + { + ID = 2, + GID = 21, + X = 15.833f, + Y = 112.056f, + Width = 32, + Height = 32, + FlippingFlags = FlippingFlags.FlippedHorizontally | FlippingFlags.FlippedVertically + } + ] } ] }; diff --git a/src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/map-with-flippingflags.tmj b/src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/map-with-flippingflags.tmj index 3b74128..43a18fe 100644 --- a/src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/map-with-flippingflags.tmj +++ b/src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/map-with-flippingflags.tmj @@ -17,9 +17,44 @@ "width":5, "x":0, "y":0 + }, + { + "draworder":"topdown", + "id":2, + "name":"Object Layer 1", + "objects":[ + { + "gid":2147483669, + "height":32, + "id":1, + "name":"", + "rotation":0, + "type":"", + "visible":true, + "width":32, + "x":80.0555234239445, + "y":48.3886639676113 + }, + { + "gid":1073741845, + "height":32, + "id":2, + "name":"", + "rotation":0, + "type":"", + "visible":true, + "width":32, + "x":15.8334297281666, + "y":112.055523423944 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 }], - "nextlayerid":2, - "nextobjectid":1, + "nextlayerid":3, + "nextobjectid":3, "orientation":"orthogonal", "renderorder":"right-down", "tiledversion":"1.11.0", diff --git a/src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/map-with-flippingflags.tmx b/src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/map-with-flippingflags.tmx index a72cd1a..ca0130d 100644 --- a/src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/map-with-flippingflags.tmx +++ b/src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/map-with-flippingflags.tmx @@ -1,5 +1,5 @@ - + @@ -10,4 +10,8 @@ 2147483669,2147483669,2147483669,2147483669,1 + + + + diff --git a/src/DotTiled/Layers/Objects/TileObject.cs b/src/DotTiled/Layers/Objects/TileObject.cs index ab00628..ea23d70 100644 --- a/src/DotTiled/Layers/Objects/TileObject.cs +++ b/src/DotTiled/Layers/Objects/TileObject.cs @@ -9,4 +9,9 @@ public class TileObject : Object /// A reference to a tile. /// public uint GID { get; set; } + + /// + /// The flipping flags for the tile. + /// + public FlippingFlags FlippingFlags { get; set; } } diff --git a/src/DotTiled/Serialization/Tmj/TmjReaderBase.ObjectLayer.cs b/src/DotTiled/Serialization/Tmj/TmjReaderBase.ObjectLayer.cs index f90d986..ced3f64 100644 --- a/src/DotTiled/Serialization/Tmj/TmjReaderBase.ObjectLayer.cs +++ b/src/DotTiled/Serialization/Tmj/TmjReaderBase.ObjectLayer.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Numerics; using System.Text.Json; @@ -117,6 +118,8 @@ public abstract partial class TmjReaderBase if (gid.HasValue) { + var (clearedGIDs, flippingFlags) = Helpers.ReadAndClearFlippingFlagsFromGIDs([gid.Value]); + return new TileObject { ID = id, @@ -130,7 +133,8 @@ public abstract partial class TmjReaderBase Visible = visible, Template = template, Properties = properties, - GID = gid.Value + GID = clearedGIDs.Single(), + FlippingFlags = flippingFlags.Single() }; } diff --git a/src/DotTiled/Serialization/Tmx/TmxReaderBase.ObjectLayer.cs b/src/DotTiled/Serialization/Tmx/TmxReaderBase.ObjectLayer.cs index 46626e6..7553d21 100644 --- a/src/DotTiled/Serialization/Tmx/TmxReaderBase.ObjectLayer.cs +++ b/src/DotTiled/Serialization/Tmx/TmxReaderBase.ObjectLayer.cs @@ -118,9 +118,14 @@ public abstract partial class TmxReaderBase if (foundObject is null) { if (gid.HasValue) - foundObject = new TileObject { ID = id, GID = gid.Value }; + { + var (clearedGIDs, flippingFlags) = Helpers.ReadAndClearFlippingFlagsFromGIDs([gid.Value]); + foundObject = new TileObject { ID = id, GID = clearedGIDs.Single(), FlippingFlags = flippingFlags.Single() }; + } else + { foundObject = new RectangleObject { ID = id }; + } } foundObject.ID = id; From 90a57b125d761ff63fd36fc848f7479470440c6a Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sun, 17 Nov 2024 09:12:59 +0100 Subject: [PATCH 23/33] Fix multiline string property value parsing --- .../map-with-multiline-string-prop.cs | 65 +++++++++++++++ .../map-with-multiline-string-prop.tmj | 80 +++++++++++++++++++ .../map-with-multiline-string-prop.tmx | 26 ++++++ .../UnitTests/Serialization/TestData.cs | 1 + .../Tmx/TmxReaderBase.Properties.cs | 27 ++++++- 5 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 src/DotTiled.Tests/TestData/Maps/map-with-multiline-string-prop/map-with-multiline-string-prop.cs create mode 100644 src/DotTiled.Tests/TestData/Maps/map-with-multiline-string-prop/map-with-multiline-string-prop.tmj create mode 100644 src/DotTiled.Tests/TestData/Maps/map-with-multiline-string-prop/map-with-multiline-string-prop.tmx diff --git a/src/DotTiled.Tests/TestData/Maps/map-with-multiline-string-prop/map-with-multiline-string-prop.cs b/src/DotTiled.Tests/TestData/Maps/map-with-multiline-string-prop/map-with-multiline-string-prop.cs new file mode 100644 index 0000000..06fc07f --- /dev/null +++ b/src/DotTiled.Tests/TestData/Maps/map-with-multiline-string-prop/map-with-multiline-string-prop.cs @@ -0,0 +1,65 @@ +using System.Globalization; + +namespace DotTiled.Tests; + +public partial class TestData +{ + public static Map MapWithMultilineStringProp() => new Map + { + Class = "", + Orientation = MapOrientation.Isometric, + Width = 5, + Height = 5, + TileWidth = 32, + TileHeight = 16, + Infinite = false, + ParallaxOriginX = 0, + ParallaxOriginY = 0, + RenderOrder = RenderOrder.RightDown, + CompressionLevel = -1, + BackgroundColor = Color.Parse("#00ff00", CultureInfo.InvariantCulture), + Version = "1.10", + TiledVersion = "1.11.0", + NextLayerID = 2, + NextObjectID = 1, + Layers = [ + new TileLayer + { + ID = 1, + Name = "Tile Layer 1", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + GlobalTileIDs = new Optional([ + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + ]), + FlippingFlags = new Optional([ + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None + ]) + } + } + ], + Properties = + [ + new BoolProperty { Name = "boolprop", Value = true }, + new ColorProperty { Name = "colorprop", Value = Color.Parse("#ff55ffff", CultureInfo.InvariantCulture) }, + new FileProperty { Name = "fileprop", Value = "file.txt" }, + new FloatProperty { Name = "floatprop", Value = 4.2f }, + new IntProperty { Name = "intprop", Value = 8 }, + new ObjectProperty { Name = "objectprop", Value = 5 }, + new StringProperty { Name = "stringmultiline", Value = "hello there\n\ni am a multiline\nstring property" }, + new StringProperty { Name = "stringprop", Value = "This is a string, hello world!" }, + new StringProperty { Name = "unsetstringprop", Value = "" } + ] + }; +} diff --git a/src/DotTiled.Tests/TestData/Maps/map-with-multiline-string-prop/map-with-multiline-string-prop.tmj b/src/DotTiled.Tests/TestData/Maps/map-with-multiline-string-prop/map-with-multiline-string-prop.tmj new file mode 100644 index 0000000..44465f2 --- /dev/null +++ b/src/DotTiled.Tests/TestData/Maps/map-with-multiline-string-prop/map-with-multiline-string-prop.tmj @@ -0,0 +1,80 @@ +{ "backgroundcolor":"#00ff00", + "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":"isometric", + "properties":[ + { + "name":"boolprop", + "type":"bool", + "value":true + }, + { + "name":"colorprop", + "type":"color", + "value":"#ff55ffff" + }, + { + "name":"fileprop", + "type":"file", + "value":"file.txt" + }, + { + "name":"floatprop", + "type":"float", + "value":4.2 + }, + { + "name":"intprop", + "type":"int", + "value":8 + }, + + { + "name":"objectprop", + "type":"object", + "value":5 + }, + { + "name":"stringmultiline", + "type":"string", + "value":"hello there\n\ni am a multiline\nstring property" + }, + { + "name":"stringprop", + "type":"string", + "value":"This is a string, hello world!" + }, + { + "name":"unsetstringprop", + "type":"string", + "value":"" + }], + "renderorder":"right-down", + "tiledversion":"1.11.0", + "tileheight":16, + "tilesets":[], + "tilewidth":32, + "type":"map", + "version":"1.10", + "width":5 +} \ No newline at end of file diff --git a/src/DotTiled.Tests/TestData/Maps/map-with-multiline-string-prop/map-with-multiline-string-prop.tmx b/src/DotTiled.Tests/TestData/Maps/map-with-multiline-string-prop/map-with-multiline-string-prop.tmx new file mode 100644 index 0000000..de55ee1 --- /dev/null +++ b/src/DotTiled.Tests/TestData/Maps/map-with-multiline-string-prop/map-with-multiline-string-prop.tmx @@ -0,0 +1,26 @@ + + + + + + + + + + hello there + +i am a multiline +string property + + + + + +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 + + + diff --git a/src/DotTiled.Tests/UnitTests/Serialization/TestData.cs b/src/DotTiled.Tests/UnitTests/Serialization/TestData.cs index eb13942..b59622e 100644 --- a/src/DotTiled.Tests/UnitTests/Serialization/TestData.cs +++ b/src/DotTiled.Tests/UnitTests/Serialization/TestData.cs @@ -42,6 +42,7 @@ public static partial class TestData [GetMapPath("map-external-tileset-multi"), (string f) => MapExternalTilesetMulti(f), Array.Empty()], [GetMapPath("map-external-tileset-wangset"), (string f) => MapExternalTilesetWangset(f), Array.Empty()], [GetMapPath("map-with-many-layers"), (string f) => MapWithManyLayers(f), Array.Empty()], + [GetMapPath("map-with-multiline-string-prop"), (string f) => MapWithMultilineStringProp(), Array.Empty()], [GetMapPath("map-with-deep-props"), (string f) => MapWithDeepProps(), MapWithDeepPropsCustomTypeDefinitions()], [GetMapPath("map-with-class"), (string f) => MapWithClass(), MapWithClassCustomTypeDefinitions()], [GetMapPath("map-with-class-and-props"), (string f) => MapWithClassAndProps(), MapWithClassAndPropsCustomTypeDefinitions()], diff --git a/src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs b/src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs index bfdb93d..32b6a5c 100644 --- a/src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs +++ b/src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Xml; @@ -32,9 +33,14 @@ public abstract partial class TmxReaderBase return ReadPropertyWithCustomType(); } + if (type == PropertyType.String) + { + return ReadStringProperty(name); + } + IProperty property = type switch { - PropertyType.String => new StringProperty { Name = name, Value = r.GetRequiredAttribute("value") }, + PropertyType.String => throw new InvalidOperationException("String properties should be handled elsewhere."), PropertyType.Int => new IntProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, PropertyType.Float => new FloatProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, PropertyType.Bool => new BoolProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, @@ -49,6 +55,25 @@ public abstract partial class TmxReaderBase }); } + internal StringProperty ReadStringProperty(string name) + { + var valueAttrib = _reader.GetOptionalAttribute("value"); + if (valueAttrib.HasValue) + { + return new StringProperty { Name = name, Value = valueAttrib.Value }; + } + + if (!_reader.IsEmptyElement) + { + _reader.ReadStartElement("property"); + var value = _reader.ReadContentAsString(); + _reader.ReadEndElement(); + return new StringProperty { Name = name, Value = value }; + } + + return new StringProperty { Name = name, Value = string.Empty }; + } + internal IProperty ReadPropertyWithCustomType() { var isClass = _reader.GetOptionalAttribute("type") == "class"; From c9e85c9fd6145ce06a3ba60e3805f837078d3996 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sun, 17 Nov 2024 15:52:20 +0100 Subject: [PATCH 24/33] Add object override for rectangle objects --- .../map-override-object-bug.cs | 242 ++++++++++++++++++ .../map-override-object-bug.tmj | 171 +++++++++++++ .../map-override-object-bug.tmx | 50 ++++ .../Maps/map-override-object-bug/poly.tj | 31 +++ .../Maps/map-override-object-bug/poly.tx | 9 + .../Maps/map-override-object-bug/random.tj | 12 + .../Maps/map-override-object-bug/random.tx | 4 + .../Maps/map-override-object-bug/tileset.png | Bin 0 -> 11908 bytes .../Maps/map-override-object-bug/tileset.tsj | 14 + .../Maps/map-override-object-bug/tileset.tsx | 4 + .../UnitTests/Serialization/TestData.cs | 1 + .../Tmx/TmxReaderBase.ObjectLayer.cs | 30 ++- 12 files changed, 557 insertions(+), 11 deletions(-) create mode 100644 src/DotTiled.Tests/TestData/Maps/map-override-object-bug/map-override-object-bug.cs create mode 100644 src/DotTiled.Tests/TestData/Maps/map-override-object-bug/map-override-object-bug.tmj create mode 100644 src/DotTiled.Tests/TestData/Maps/map-override-object-bug/map-override-object-bug.tmx create mode 100644 src/DotTiled.Tests/TestData/Maps/map-override-object-bug/poly.tj create mode 100644 src/DotTiled.Tests/TestData/Maps/map-override-object-bug/poly.tx create mode 100644 src/DotTiled.Tests/TestData/Maps/map-override-object-bug/random.tj create mode 100644 src/DotTiled.Tests/TestData/Maps/map-override-object-bug/random.tx create mode 100644 src/DotTiled.Tests/TestData/Maps/map-override-object-bug/tileset.png create mode 100644 src/DotTiled.Tests/TestData/Maps/map-override-object-bug/tileset.tsj create mode 100644 src/DotTiled.Tests/TestData/Maps/map-override-object-bug/tileset.tsx diff --git a/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/map-override-object-bug.cs b/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/map-override-object-bug.cs new file mode 100644 index 0000000..82dfd29 --- /dev/null +++ b/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/map-override-object-bug.cs @@ -0,0 +1,242 @@ +using System.Numerics; + +namespace DotTiled.Tests; + +public partial class TestData +{ + public static Map MapOverrideObjectBug(string fileExt) => new Map + { + Class = "", + Orientation = MapOrientation.Orthogonal, + Width = 5, + Height = 5, + TileWidth = 32, + TileHeight = 32, + Infinite = false, + ParallaxOriginX = 0, + ParallaxOriginY = 0, + RenderOrder = RenderOrder.RightDown, + CompressionLevel = -1, + BackgroundColor = new Color { R = 0, G = 0, B = 0, A = 0 }, + Version = "1.10", + TiledVersion = "1.11.0", + NextLayerID = 8, + NextObjectID = 8, + Tilesets = [ + new Tileset + { + Version = "1.10", + TiledVersion = "1.11.0", + FirstGID = 1, + Name = "tileset", + TileWidth = 32, + TileHeight = 32, + TileCount = 24, + Columns = 8, + Source = $"tileset.{(fileExt == "tmx" ? "tsx" : "tsj")}", + Image = new Image + { + Format = ImageFormat.Png, + Source = "tileset.png", + Width = 256, + Height = 96, + } + } + ], + Layers = [ + new Group + { + ID = 2, + Name = "Root", + Layers = [ + new ObjectLayer + { + ID = 3, + Name = "Objects", + Objects = [ + new RectangleObject + { + ID = 1, + Name = "Object 1", + X = 25.6667f, + Y = 28.6667f, + Width = 31.3333f, + Height = 31.3333f + }, + new PointObject + { + ID = 3, + Name = "P1", + X = 117.667f, + Y = 48.6667f + }, + new EllipseObject + { + ID = 4, + Name = "Circle1", + X = 77f, + Y = 72.3333f, + Width = 34.6667f, + Height = 34.6667f + }, + new PolygonObject + { + ID = 5, + Name = "Poly", + X = 20.6667f, + Y = 114.667f, + Points = [ + new Vector2(0, 0), + new Vector2(104,20), + new Vector2(35.6667f, 32.3333f) + ], + Template = fileExt == "tmx" ? "poly.tx" : "poly.tj", + Properties = [ + new StringProperty { Name = "templateprop", Value = "helo there" } + ] + }, + new TileObject + { + ID = 6, + Name = "TileObj", + GID = 7, + X = -35, + Y = 110.333f, + Width = 64, + Height = 146 + }, + new RectangleObject + { + ID = 7, + Name = "", + Template = fileExt == "tmx" ? "random.tx" : "random.tj", + Type = "randomclass", + X = 134.552f, + Y = 113.638f + } + ] + }, + new Group + { + ID = 5, + Name = "Sub", + Layers = [ + new TileLayer + { + ID = 7, + Name = "Tile 3", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + GlobalTileIDs = new Optional([ + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + ]), + FlippingFlags = new Optional([ + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None + ]) + } + }, + new TileLayer + { + ID = 6, + Name = "Tile 2", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + GlobalTileIDs = new Optional([ + 0, 15, 15, 0, 0, + 0, 15, 15, 0, 0, + 0, 15, 15, 15, 0, + 15, 15, 15, 0, 0, + 0, 0, 0, 0, 0 + ]), + FlippingFlags = new Optional([ + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None + ]) + } + } + ] + }, + new ImageLayer + { + ID = 4, + Name = "ImageLayer", + Image = new Image + { + Format = ImageFormat.Png, + Source = "tileset.png", + Width = fileExt == "tmx" ? 256u : 0, // Currently, json format does not + Height = fileExt == "tmx" ? 96u : 0 // include image dimensions in image layer https://github.com/mapeditor/tiled/issues/4028 + }, + RepeatX = true + }, + new TileLayer + { + ID = 1, + Name = "Tile Layer 1", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + GlobalTileIDs = new Optional([ + 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + ]), + FlippingFlags = new Optional([ + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None + ]) + } + } + ] + } + ] + }; + + public static IReadOnlyCollection MapOverrideObjectBugCustomTypeDefinitions() => [ + new CustomClassDefinition + { + Name = "TestClass", + UseAs = CustomClassUseAs.Map, + Members = [ + new BoolProperty + { + Name = "classbool", + Value = true + }, + new StringProperty + { + Name = "classstring", + Value = "Hello there default value" + } + ] + }, + new CustomClassDefinition + { + Name = "randomclass" + } + ]; +} diff --git a/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/map-override-object-bug.tmj b/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/map-override-object-bug.tmj new file mode 100644 index 0000000..ba67083 --- /dev/null +++ b/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/map-override-object-bug.tmj @@ -0,0 +1,171 @@ +{ "compressionlevel":-1, + "height":5, + "infinite":false, + "layers":[ + { + "id":2, + "layers":[ + { + "draworder":"topdown", + "id":3, + "name":"Objects", + "objects":[ + { + "height":31.3333, + "id":1, + "name":"Object 1", + "rotation":0, + "type":"", + "visible":true, + "width":31.3333, + "x":25.6667, + "y":28.6667 + }, + { + "height":0, + "id":3, + "name":"P1", + "point":true, + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":117.667, + "y":48.6667 + }, + { + "ellipse":true, + "height":34.6667, + "id":4, + "name":"Circle1", + "rotation":0, + "type":"", + "visible":true, + "width":34.6667, + "x":77, + "y":72.3333 + }, + { + "id":5, + "template":"poly.tj", + "x":20.6667, + "y":114.667 + }, + { + "gid":7, + "height":146, + "id":6, + "name":"TileObj", + "rotation":0, + "type":"", + "visible":true, + "width":64, + "x":-35, + "y":110.333 + }, + + { + "id":7, + "template":"random.tj", + "type":"randomclass", + "x":134.551764025448, + "y":113.637941006362 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }, + { + "id":5, + "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":7, + "name":"Tile 3", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":5, + "x":0, + "y":0 + }, + { + "data":[0, 15, 15, 0, 0, + 0, 15, 15, 0, 0, + 0, 15, 15, 15, 0, + 15, 15, 15, 0, 0, + 0, 0, 0, 0, 0], + "height":5, + "id":6, + "name":"Tile 2", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":5, + "x":0, + "y":0 + }], + "name":"Sub", + "opacity":1, + "type":"group", + "visible":true, + "x":0, + "y":0 + }, + { + "id":4, + "image":"tileset.png", + "name":"ImageLayer", + "opacity":1, + "repeatx":true, + "type":"imagelayer", + "visible":true, + "x":0, + "y":0 + }, + { + "data":[1, 1, 1, 1, 1, + 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 + }], + "name":"Root", + "opacity":1, + "type":"group", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":8, + "nextobjectid":8, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.11.0", + "tileheight":32, + "tilesets":[ + { + "firstgid":1, + "source":"tileset.tsj" + }], + "tilewidth":32, + "type":"map", + "version":"1.10", + "width":5 +} \ No newline at end of file diff --git a/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/map-override-object-bug.tmx b/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/map-override-object-bug.tmx new file mode 100644 index 0000000..08f2657 --- /dev/null +++ b/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/map-override-object-bug.tmx @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + +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 + + + + +0,15,15,0,0, +0,15,15,0,0, +0,15,15,15,0, +15,15,15,0,0, +0,0,0,0,0 + + + + + + + + +1,1,1,1,1, +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0 + + + + diff --git a/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/poly.tj b/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/poly.tj new file mode 100644 index 0000000..f23c7d9 --- /dev/null +++ b/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/poly.tj @@ -0,0 +1,31 @@ +{ "object": + { + "height":0, + "id":5, + "name":"Poly", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":104, + "y":20 + }, + { + "x":35.6667, + "y":32.3333 + }], + "properties":[ + { + "name":"templateprop", + "type":"string", + "value":"helo there" + }], + "rotation":0, + "type":"", + "visible":true, + "width":0 + }, + "type":"template" +} \ No newline at end of file diff --git a/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/poly.tx b/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/poly.tx new file mode 100644 index 0000000..a0a2457 --- /dev/null +++ b/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/poly.tx @@ -0,0 +1,9 @@ + + diff --git a/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/random.tj b/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/random.tj new file mode 100644 index 0000000..09b8393 --- /dev/null +++ b/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/random.tj @@ -0,0 +1,12 @@ +{ "object": + { + "height":0, + "id":7, + "name":"", + "rotation":0, + "type":"randomclass", + "visible":true, + "width":0 + }, + "type":"template" +} \ No newline at end of file diff --git a/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/random.tx b/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/random.tx new file mode 100644 index 0000000..d6b44ee --- /dev/null +++ b/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/random.tx @@ -0,0 +1,4 @@ + + diff --git a/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/tileset.png b/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/tileset.png new file mode 100644 index 0000000000000000000000000000000000000000..97c1fb31b1fa4dcf1214b8fe9b2e2b8e287c72d0 GIT binary patch literal 11908 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 Pn-8F-q^(#jZyEZ3BaoJ| literal 0 HcmV?d00001 diff --git a/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/tileset.tsj b/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/tileset.tsj new file mode 100644 index 0000000..820e88f --- /dev/null +++ b/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/tileset.tsj @@ -0,0 +1,14 @@ +{ "columns":8, + "image":"tileset.png", + "imageheight":96, + "imagewidth":256, + "margin":0, + "name":"tileset", + "spacing":0, + "tilecount":24, + "tiledversion":"1.11.0", + "tileheight":32, + "tilewidth":32, + "type":"tileset", + "version":"1.10" +} \ No newline at end of file diff --git a/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/tileset.tsx b/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/tileset.tsx new file mode 100644 index 0000000..d730182 --- /dev/null +++ b/src/DotTiled.Tests/TestData/Maps/map-override-object-bug/tileset.tsx @@ -0,0 +1,4 @@ + + + + diff --git a/src/DotTiled.Tests/UnitTests/Serialization/TestData.cs b/src/DotTiled.Tests/UnitTests/Serialization/TestData.cs index b59622e..28358b3 100644 --- a/src/DotTiled.Tests/UnitTests/Serialization/TestData.cs +++ b/src/DotTiled.Tests/UnitTests/Serialization/TestData.cs @@ -46,5 +46,6 @@ public static partial class TestData [GetMapPath("map-with-deep-props"), (string f) => MapWithDeepProps(), MapWithDeepPropsCustomTypeDefinitions()], [GetMapPath("map-with-class"), (string f) => MapWithClass(), MapWithClassCustomTypeDefinitions()], [GetMapPath("map-with-class-and-props"), (string f) => MapWithClassAndProps(), MapWithClassAndPropsCustomTypeDefinitions()], + [GetMapPath("map-override-object-bug"), (string f) => MapOverrideObjectBug(f), MapOverrideObjectBugCustomTypeDefinitions()], ]; } diff --git a/src/DotTiled/Serialization/Tmx/TmxReaderBase.ObjectLayer.cs b/src/DotTiled/Serialization/Tmx/TmxReaderBase.ObjectLayer.cs index 7553d21..b2e0c56 100644 --- a/src/DotTiled/Serialization/Tmx/TmxReaderBase.ObjectLayer.cs +++ b/src/DotTiled/Serialization/Tmx/TmxReaderBase.ObjectLayer.cs @@ -148,19 +148,20 @@ public abstract partial class TmxReaderBase if (obj is null) return foundObject; + obj.ID = foundObject.ID; + obj.Name = foundObject.Name; + obj.Type = foundObject.Type; + obj.X = foundObject.X; + obj.Y = foundObject.Y; + obj.Width = foundObject.Width; + obj.Height = foundObject.Height; + obj.Rotation = foundObject.Rotation; + obj.Visible = foundObject.Visible; + obj.Properties = Helpers.MergeProperties(obj.Properties, foundObject.Properties).ToList(); + obj.Template = foundObject.Template; + if (obj.GetType() != foundObject.GetType()) { - obj.ID = foundObject.ID; - obj.Name = foundObject.Name; - obj.Type = foundObject.Type; - obj.X = foundObject.X; - obj.Y = foundObject.Y; - obj.Width = foundObject.Width; - obj.Height = foundObject.Height; - obj.Rotation = foundObject.Rotation; - obj.Visible = foundObject.Visible; - obj.Properties = Helpers.MergeProperties(obj.Properties, foundObject.Properties).ToList(); - obj.Template = foundObject.Template; return obj; } @@ -231,6 +232,13 @@ public abstract partial class TmxReaderBase return obj; } + internal static RectangleObject OverrideObject(RectangleObject obj, RectangleObject foundObject) + { + obj.Width = foundObject.Width; + obj.Height = foundObject.Height; + return obj; + } + internal TextObject ReadTextObject() { // Attributes From 1027b922fe6678962962f5871ed7c3fc540c91d2 Mon Sep 17 00:00:00 2001 From: Kat Date: Tue, 19 Nov 2024 03:08:04 -0800 Subject: [PATCH 25/33] Enum properties in CustomClassDefinition.FromClass --- .../CustomTypes/FromTypeUsedInLoaderTests.cs | 23 +++++++++++++++++-- .../CustomTypes/CustomClassDefinition.cs | 14 +++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/DotTiled.Tests/IntegrationTests/CustomTypes/FromTypeUsedInLoaderTests.cs b/src/DotTiled.Tests/IntegrationTests/CustomTypes/FromTypeUsedInLoaderTests.cs index dae9464..87c9948 100644 --- a/src/DotTiled.Tests/IntegrationTests/CustomTypes/FromTypeUsedInLoaderTests.cs +++ b/src/DotTiled.Tests/IntegrationTests/CustomTypes/FromTypeUsedInLoaderTests.cs @@ -5,10 +5,25 @@ namespace DotTiled.Tests; public class FromTypeUsedInLoaderTests { + private enum TestEnum + { + A, + B, + C + } + [Flags] + private enum TestFlags + { + A = 0b001, + B = 0b010, + C = 0b100 + } private sealed class TestClass { public string Name { get; set; } = "John Doe"; public int Age { get; set; } = 42; + public TestEnum Enum { get; set; } = TestEnum.A; + public TestFlags Flags { get; set; } = TestFlags.B | TestFlags.C; } [Fact] @@ -82,7 +97,9 @@ public class FromTypeUsedInLoaderTests ], Properties = [ new StringProperty { Name = "Name", Value = "John Doe" }, - new IntProperty { Name = "Age", Value = 42 } + new IntProperty { Name = "Age", Value = 42 }, + new EnumProperty { Name = "Enum", PropertyType = "TestEnum", Value = new HashSet { "A" } }, + new EnumProperty { Name = "Flags", PropertyType = "TestFlags", Value = new HashSet { "B", "C" } } ] }; @@ -167,7 +184,9 @@ public class FromTypeUsedInLoaderTests ], Properties = [ new StringProperty { Name = "Name", Value = "John Doe" }, - new IntProperty { Name = "Age", Value = 42 } + new IntProperty { Name = "Age", Value = 42 }, + new EnumProperty { Name = "Enum", PropertyType = "TestEnum", Value = new HashSet { "A" } }, + new EnumProperty { Name = "Flags", PropertyType = "TestFlags", Value = new HashSet { "B", "C" } } ] }; diff --git a/src/DotTiled/Properties/CustomTypes/CustomClassDefinition.cs b/src/DotTiled/Properties/CustomTypes/CustomClassDefinition.cs index 83e0fbe..b674f48 100644 --- a/src/DotTiled/Properties/CustomTypes/CustomClassDefinition.cs +++ b/src/DotTiled/Properties/CustomTypes/CustomClassDefinition.cs @@ -165,6 +165,20 @@ public class CustomClassDefinition : HasPropertiesBase, ICustomTypeDefinition return new IntProperty { Name = propertyInfo.Name, Value = (int)propertyInfo.GetValue(instance) }; case Type t when t.IsClass: return new ClassProperty { Name = propertyInfo.Name, PropertyType = t.Name, Value = GetNestedProperties(propertyInfo.PropertyType, propertyInfo.GetValue(instance)) }; + case Type t when t.IsEnum: + var isFlags = t.GetCustomAttributes(typeof(FlagsAttribute), false).Length != 0; + + if (isFlags) + { + ISet values = new HashSet(); + foreach (var value in t.GetEnumValues()) + { + if (((int)value & (int)propertyInfo.GetValue(instance)) != 0) values.Add(t.GetEnumName(value)); + } + return new EnumProperty { Name = propertyInfo.Name, PropertyType = t.Name, Value = values }; + } + + return new EnumProperty { Name = propertyInfo.Name, PropertyType = t.Name, Value = new HashSet { t.GetEnumName(propertyInfo.GetValue(instance)) } }; default: break; } From 7a7f360e2238ab381d6e375c2f9a5f91fa3eb50c Mon Sep 17 00:00:00 2001 From: Kat Date: Wed, 20 Nov 2024 05:11:45 -0800 Subject: [PATCH 26/33] Implement requested changes --- .../CustomTypes/FromTypeUsedInLoaderTests.cs | 115 ++++++++++++++---- .../CustomTypes/CustomClassDefinitionTests.cs | 31 +++++ .../CustomTypes/CustomClassDefinition.cs | 19 ++- 3 files changed, 133 insertions(+), 32 deletions(-) diff --git a/src/DotTiled.Tests/IntegrationTests/CustomTypes/FromTypeUsedInLoaderTests.cs b/src/DotTiled.Tests/IntegrationTests/CustomTypes/FromTypeUsedInLoaderTests.cs index 87c9948..c0d580f 100644 --- a/src/DotTiled.Tests/IntegrationTests/CustomTypes/FromTypeUsedInLoaderTests.cs +++ b/src/DotTiled.Tests/IntegrationTests/CustomTypes/FromTypeUsedInLoaderTests.cs @@ -5,25 +5,10 @@ namespace DotTiled.Tests; public class FromTypeUsedInLoaderTests { - private enum TestEnum - { - A, - B, - C - } - [Flags] - private enum TestFlags - { - A = 0b001, - B = 0b010, - C = 0b100 - } private sealed class TestClass { public string Name { get; set; } = "John Doe"; public int Age { get; set; } = 42; - public TestEnum Enum { get; set; } = TestEnum.A; - public TestFlags Flags { get; set; } = TestFlags.B | TestFlags.C; } [Fact] @@ -97,9 +82,7 @@ public class FromTypeUsedInLoaderTests ], Properties = [ new StringProperty { Name = "Name", Value = "John Doe" }, - new IntProperty { Name = "Age", Value = 42 }, - new EnumProperty { Name = "Enum", PropertyType = "TestEnum", Value = new HashSet { "A" } }, - new EnumProperty { Name = "Flags", PropertyType = "TestFlags", Value = new HashSet { "B", "C" } } + new IntProperty { Name = "Age", Value = 42 } ] }; @@ -184,9 +167,99 @@ public class FromTypeUsedInLoaderTests ], Properties = [ new StringProperty { Name = "Name", Value = "John Doe" }, - new IntProperty { Name = "Age", Value = 42 }, - new EnumProperty { Name = "Enum", PropertyType = "TestEnum", Value = new HashSet { "A" } }, - new EnumProperty { Name = "Flags", PropertyType = "TestFlags", Value = new HashSet { "B", "C" } } + new IntProperty { Name = "Age", Value = 42 } + ] + }; + + // Act + var result = loader.LoadMap("map.tmx"); + + // Assert + DotTiledAssert.AssertMap(expectedMap, result); + } + + private enum TestEnum + { + Value1, + Value2 + } + + private sealed class TestClassWithEnum + { + public TestEnum Enum { get; set; } = TestEnum.Value1; + } + + [Fact] + public void LoadMap_MapHasClassWithEnumAndClassIsDefined_ReturnsCorrectMap() + { + // Arrange + var resourceReader = Substitute.For(); + resourceReader.Read("map.tmx").Returns( + """ + + + + + 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 + + + + """); + var classDefinition = CustomClassDefinition.FromClass(); + var loader = Loader.DefaultWith( + resourceReader: resourceReader, + customTypeDefinitions: [classDefinition]); + var expectedMap = new Map + { + Class = "TestClassWithEnum", + Orientation = MapOrientation.Orthogonal, + Width = 5, + Height = 5, + TileWidth = 32, + TileHeight = 32, + Infinite = false, + ParallaxOriginX = 0, + ParallaxOriginY = 0, + RenderOrder = RenderOrder.RightDown, + CompressionLevel = -1, + BackgroundColor = new Color { R = 0, G = 0, B = 0, A = 0 }, + Version = "1.10", + TiledVersion = "1.11.0", + NextLayerID = 2, + NextObjectID = 1, + Layers = [ + new TileLayer + { + ID = 1, + Name = "Tile Layer 1", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + GlobalTileIDs = new Optional([ + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + ]), + FlippingFlags = new Optional([ + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None + ]) + } + } + ], + Properties = [ + new EnumProperty { Name = "Enum", PropertyType = "TestEnum", Value = new HashSet { "Value1" } } ] }; diff --git a/src/DotTiled.Tests/UnitTests/Properties/CustomTypes/CustomClassDefinitionTests.cs b/src/DotTiled.Tests/UnitTests/Properties/CustomTypes/CustomClassDefinitionTests.cs index 7e920e3..74e716c 100644 --- a/src/DotTiled.Tests/UnitTests/Properties/CustomTypes/CustomClassDefinitionTests.cs +++ b/src/DotTiled.Tests/UnitTests/Properties/CustomTypes/CustomClassDefinitionTests.cs @@ -70,11 +70,42 @@ public class CustomClassDefinitionTests ] }; + private enum TestEnum1 + { + Value1, + Value2 + } + + [Flags] + private enum TestFlags1 + { + Value1 = 0b001, + Value2 = 0b010, + Value3 = 0b100 + } + + private sealed class TestClass4WithEnums + { + public TestEnum1 Enum { get; set; } = TestEnum1.Value2; + public TestFlags1 Flags { get; set; } = TestFlags1.Value1 | TestFlags1.Value2; + } + + private static CustomClassDefinition ExpectedTestClass4WithEnumsDefinition => new CustomClassDefinition + { + Name = "TestClass4WithEnums", + UseAs = CustomClassUseAs.All, + Members = [ + new EnumProperty { Name = "Enum", PropertyType = "TestEnum1", Value = new HashSet { "Value2" } }, + new EnumProperty { Name = "Flags", PropertyType = "TestFlags1", Value = new HashSet { "Value1", "Value2" } } + ] + }; + private static IEnumerable<(Type, CustomClassDefinition)> GetCustomClassDefinitionTestData() { yield return (typeof(TestClass1), ExpectedTestClass1Definition); yield return (typeof(TestClass2WithNestedClass), ExpectedTestClass2WithNestedClassDefinition); yield return (typeof(TestClass3WithOverridenNestedClass), ExpectedTestClass3WithOverridenNestedClassDefinition); + yield return (typeof(TestClass4WithEnums), ExpectedTestClass4WithEnumsDefinition); } public static IEnumerable CustomClassDefinitionTestData => diff --git a/src/DotTiled/Properties/CustomTypes/CustomClassDefinition.cs b/src/DotTiled/Properties/CustomTypes/CustomClassDefinition.cs index b674f48..37b8c6e 100644 --- a/src/DotTiled/Properties/CustomTypes/CustomClassDefinition.cs +++ b/src/DotTiled/Properties/CustomTypes/CustomClassDefinition.cs @@ -166,19 +166,16 @@ public class CustomClassDefinition : HasPropertiesBase, ICustomTypeDefinition case Type t when t.IsClass: return new ClassProperty { Name = propertyInfo.Name, PropertyType = t.Name, Value = GetNestedProperties(propertyInfo.PropertyType, propertyInfo.GetValue(instance)) }; case Type t when t.IsEnum: - var isFlags = t.GetCustomAttributes(typeof(FlagsAttribute), false).Length != 0; + var enumDefinition = CustomEnumDefinition.FromEnum(t); - if (isFlags) - { - ISet values = new HashSet(); - foreach (var value in t.GetEnumValues()) - { - if (((int)value & (int)propertyInfo.GetValue(instance)) != 0) values.Add(t.GetEnumName(value)); - } - return new EnumProperty { Name = propertyInfo.Name, PropertyType = t.Name, Value = values }; - } + if (!enumDefinition.ValueAsFlags) + return new EnumProperty { Name = propertyInfo.Name, PropertyType = t.Name, Value = new HashSet { propertyInfo.GetValue(instance).ToString() } }; - return new EnumProperty { Name = propertyInfo.Name, PropertyType = t.Name, Value = new HashSet { t.GetEnumName(propertyInfo.GetValue(instance)) } }; + var flags = (Enum)propertyInfo.GetValue(instance); + var enumValues = Enum.GetValues(t).Cast(); + var enumNames = enumValues.Where(flags.HasFlag).Select(e => e.ToString()); + + return new EnumProperty { Name = propertyInfo.Name, PropertyType = t.Name, Value = enumNames.ToHashSet() }; default: break; } From f192a71c56ecbad50b7f53daa46615ae86ff9796 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Thu, 21 Nov 2024 17:52:54 +0100 Subject: [PATCH 27/33] Add disclaimer about FromClass with classes that contain enums --- docs/docs/essentials/custom-properties.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/docs/essentials/custom-properties.md b/docs/docs/essentials/custom-properties.md index addffd3..6ac6b38 100644 --- a/docs/docs/essentials/custom-properties.md +++ b/docs/docs/essentials/custom-properties.md @@ -203,6 +203,9 @@ var entityDataDef = CustomClassDefinition.FromClass(); The above gives us two custom type definitions that we can supply to our map loader. Given a map that looks like this: +> [!WARNING] +> For classes that you call `FromClass` on, which also contain enum properties (at some level of depth) that you want to map to a C# enum, you must also supply the custom enum definitions to the map loader. This is so that the map loader can resolve the enum values correctly. + ```xml From 94c1ac0f32ad28f90cc509c48c97e5521d8800c5 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Thu, 21 Nov 2024 20:55:51 +0100 Subject: [PATCH 28/33] Unset colors are now parsed correctly, color properties have optional colors --- .../Maps/map-with-common-props/map-with-common-props.cs | 3 ++- .../Maps/map-with-common-props/map-with-common-props.tmj | 5 +++++ .../Maps/map-with-common-props/map-with-common-props.tmx | 1 + src/DotTiled/Properties/ColorProperty.cs | 4 ++-- src/DotTiled/Properties/IHasProperties.cs | 4 +++- src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs | 2 +- src/DotTiled/Serialization/Tmx/ExtensionsXmlReader.cs | 2 +- src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs | 2 +- 8 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/DotTiled.Tests/TestData/Maps/map-with-common-props/map-with-common-props.cs b/src/DotTiled.Tests/TestData/Maps/map-with-common-props/map-with-common-props.cs index b2dbc7a..2437d0b 100644 --- a/src/DotTiled.Tests/TestData/Maps/map-with-common-props/map-with-common-props.cs +++ b/src/DotTiled.Tests/TestData/Maps/map-with-common-props/map-with-common-props.cs @@ -57,7 +57,8 @@ public partial class TestData new FloatProperty { Name = "floatprop", Value = 4.2f }, new IntProperty { Name = "intprop", Value = 8 }, new ObjectProperty { Name = "objectprop", Value = 5 }, - new StringProperty { Name = "stringprop", Value = "This is a string, hello world!" } + new StringProperty { Name = "stringprop", Value = "This is a string, hello world!" }, + new ColorProperty { Name = "unsetcolorprop", Value = Optional.Empty } ] }; } diff --git a/src/DotTiled.Tests/TestData/Maps/map-with-common-props/map-with-common-props.tmj b/src/DotTiled.Tests/TestData/Maps/map-with-common-props/map-with-common-props.tmj index c7182ef..4fbc980 100644 --- a/src/DotTiled.Tests/TestData/Maps/map-with-common-props/map-with-common-props.tmj +++ b/src/DotTiled.Tests/TestData/Maps/map-with-common-props/map-with-common-props.tmj @@ -58,6 +58,11 @@ "name":"stringprop", "type":"string", "value":"This is a string, hello world!" + }, + { + "name":"unsetcolorprop", + "type":"color", + "value":"" }], "renderorder":"right-down", "tiledversion":"1.11.0", diff --git a/src/DotTiled.Tests/TestData/Maps/map-with-common-props/map-with-common-props.tmx b/src/DotTiled.Tests/TestData/Maps/map-with-common-props/map-with-common-props.tmx index b4b36cd..1aeacd7 100644 --- a/src/DotTiled.Tests/TestData/Maps/map-with-common-props/map-with-common-props.tmx +++ b/src/DotTiled.Tests/TestData/Maps/map-with-common-props/map-with-common-props.tmx @@ -8,6 +8,7 @@ + diff --git a/src/DotTiled/Properties/ColorProperty.cs b/src/DotTiled/Properties/ColorProperty.cs index 0fff029..295096e 100644 --- a/src/DotTiled/Properties/ColorProperty.cs +++ b/src/DotTiled/Properties/ColorProperty.cs @@ -3,7 +3,7 @@ namespace DotTiled; /// /// Represents a color property. /// -public class ColorProperty : IProperty +public class ColorProperty : IProperty> { /// public required string Name { get; set; } @@ -14,7 +14,7 @@ public class ColorProperty : IProperty /// /// The color value of the property. /// - public required Color Value { get; set; } + public required Optional Value { get; set; } /// public IProperty Clone() => new ColorProperty diff --git a/src/DotTiled/Properties/IHasProperties.cs b/src/DotTiled/Properties/IHasProperties.cs index 03e610a..6dd1798 100644 --- a/src/DotTiled/Properties/IHasProperties.cs +++ b/src/DotTiled/Properties/IHasProperties.cs @@ -105,7 +105,9 @@ public abstract class HasPropertiesBase : IHasProperties type.GetProperty(prop.Name)?.SetValue(instance, boolProp.Value); break; case ColorProperty colorProp: - type.GetProperty(prop.Name)?.SetValue(instance, colorProp.Value); + if (!colorProp.Value.HasValue) + break; + type.GetProperty(prop.Name)?.SetValue(instance, colorProp.Value.Value); break; case FloatProperty floatProp: type.GetProperty(prop.Name)?.SetValue(instance, floatProp.Value); diff --git a/src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs b/src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs index 1ffff1c..acb63be 100644 --- a/src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs +++ b/src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs @@ -36,7 +36,7 @@ public abstract partial class TmjReaderBase 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.Color => new ColorProperty { Name = name, Value = e.GetRequiredPropertyParseable("value", s => s == "" ? default : Color.Parse(s, CultureInfo.InvariantCulture)) }, PropertyType.File => new FileProperty { Name = name, Value = e.GetRequiredProperty("value") }, PropertyType.Object => new ObjectProperty { Name = name, Value = e.GetRequiredProperty("value") }, PropertyType.Class => throw new JsonException("Class property must have a property type"), diff --git a/src/DotTiled/Serialization/Tmx/ExtensionsXmlReader.cs b/src/DotTiled/Serialization/Tmx/ExtensionsXmlReader.cs index 0d06ade..f1c2a5d 100644 --- a/src/DotTiled/Serialization/Tmx/ExtensionsXmlReader.cs +++ b/src/DotTiled/Serialization/Tmx/ExtensionsXmlReader.cs @@ -45,7 +45,7 @@ internal static class ExtensionsXmlReader return T.Parse(value, CultureInfo.InvariantCulture); } - internal static Optional GetOptionalAttributeParseable(this XmlReader reader, string attribute, Func parser) where T : struct + internal static Optional GetOptionalAttributeParseable(this XmlReader reader, string attribute, Func parser) { var value = reader.GetAttribute(attribute); if (value is null) diff --git a/src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs b/src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs index 03d3a74..13a99fc 100644 --- a/src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs +++ b/src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs @@ -45,7 +45,7 @@ public abstract partial class TmxReaderBase PropertyType.Int => new IntProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, PropertyType.Float => new FloatProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, PropertyType.Bool => new BoolProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, - PropertyType.Color => new ColorProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, + PropertyType.Color => new ColorProperty { Name = name, Value = r.GetRequiredAttributeParseable("value", s => s == "" ? default : Color.Parse(s, CultureInfo.InvariantCulture)) }, PropertyType.File => new FileProperty { Name = name, Value = r.GetRequiredAttribute("value") }, PropertyType.Object => new ObjectProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, PropertyType.Class => throw new XmlException("Class property must have a property type"), From ade3d8840a472cde5e21566510f828b8a45cc740 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Wed, 27 Nov 2024 22:15:24 +0100 Subject: [PATCH 29/33] Fix bug where enum properties were mistakenly parsed as uint when they were string --- .../Tmj/TmjReaderBase.Properties.cs | 21 ++++++++--------- .../Tmx/TmxReaderBase.Properties.cs | 23 ++++++++----------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs b/src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs index acb63be..6ea39e4 100644 --- a/src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs +++ b/src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs @@ -156,7 +156,7 @@ public abstract partial class TmjReaderBase return resultingProps; } - internal EnumProperty ReadEnumProperty(JsonElement element) + internal IProperty ReadEnumProperty(JsonElement element) { var name = element.GetRequiredProperty("name"); var propertyType = element.GetRequiredProperty("propertytype"); @@ -170,18 +170,15 @@ public abstract partial class TmjReaderBase if (!customTypeDef.HasValue) { - if (typeInJson == PropertyType.String) +#pragma warning disable CS8509 // The switch expression does not handle all possible values of its input type (it is not exhaustive). +#pragma warning disable IDE0072 // Add missing cases + return typeInJson switch { - var value = element.GetRequiredProperty("value"); - var values = value.Split(',').Select(v => v.Trim()).ToHashSet(); - return new EnumProperty { Name = name, PropertyType = propertyType, Value = values }; - } - else - { - var value = element.GetRequiredProperty("value"); - var values = new HashSet { value.ToString(CultureInfo.InvariantCulture) }; - return new EnumProperty { Name = name, PropertyType = propertyType, Value = values }; - } + PropertyType.String => new StringProperty { Name = name, Value = element.GetRequiredProperty("value") }, + PropertyType.Int => new IntProperty { Name = name, Value = element.GetRequiredProperty("value") }, + }; +#pragma warning restore IDE0072 // Add missing cases +#pragma warning restore CS8509 // The switch expression does not handle all possible values of its input type (it is not exhaustive). } if (customTypeDef.Value is not CustomEnumDefinition ced) diff --git a/src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs b/src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs index 13a99fc..5770056 100644 --- a/src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs +++ b/src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs @@ -123,7 +123,7 @@ public abstract partial class TmxReaderBase return new ClassProperty { Name = name, PropertyType = propertyType, Value = propsInType }; } - internal EnumProperty ReadEnumProperty() + internal IProperty ReadEnumProperty() { var name = _reader.GetRequiredAttribute("name"); var propertyType = _reader.GetRequiredAttribute("propertytype"); @@ -132,25 +132,22 @@ public abstract partial class TmxReaderBase "string" => PropertyType.String, "int" => PropertyType.Int, _ => throw new XmlException("Invalid property type") - }) ?? PropertyType.String; + }).GetValueOr(PropertyType.String); var customTypeDef = _customTypeResolver(propertyType); // If the custom enum definition is not found, // we assume an empty enum definition. if (!customTypeDef.HasValue) { - if (typeInXml == PropertyType.String) +#pragma warning disable CS8509 // The switch expression does not handle all possible values of its input type (it is not exhaustive). +#pragma warning disable IDE0072 // Add missing cases + return typeInXml switch { - var value = _reader.GetRequiredAttribute("value"); - var values = value.Split(',').Select(v => v.Trim()).ToHashSet(); - return new EnumProperty { Name = name, PropertyType = propertyType, Value = values }; - } - else - { - var value = _reader.GetRequiredAttributeParseable("value"); - var values = new HashSet { value.ToString(CultureInfo.InvariantCulture) }; - return new EnumProperty { Name = name, PropertyType = propertyType, Value = values }; - } + PropertyType.String => new StringProperty { Name = name, Value = _reader.GetRequiredAttribute("value") }, + PropertyType.Int => new IntProperty { Name = name, Value = _reader.GetRequiredAttributeParseable("value") }, + }; +#pragma warning restore IDE0072 // Add missing cases +#pragma warning restore CS8509 // The switch expression does not handle all possible values of its input type (it is not exhaustive). } if (customTypeDef.Value is not CustomEnumDefinition ced) From b978b8b50d755931042c40d03a5c2cf4c6d87b5a Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Wed, 27 Nov 2024 22:20:42 +0100 Subject: [PATCH 30/33] Add documentation about enum property behaviour --- docs/docs/essentials/custom-properties.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/docs/essentials/custom-properties.md b/docs/docs/essentials/custom-properties.md index 0ea066f..3fd1bcc 100644 --- a/docs/docs/essentials/custom-properties.md +++ b/docs/docs/essentials/custom-properties.md @@ -177,6 +177,9 @@ For enum definitions, the can be used to indicate t > [!NOTE] > Tiled supports enums which can store their values as either strings or integers, and depending on the storage type you have specified in Tiled, you must make sure to have the same storage type in your . This can be done by setting the `StorageType` property to either `CustomEnumStorageType.String` or `CustomEnumStorageType.Int` when creating the definition, or by passing the storage type as an argument to the method. To be consistent with Tiled, will default to `CustomEnumStorageType.String` for the storage type parameter. +> [!WARNING] +> If you have a custom enum type in Tiled, but do not define it in DotTiled, you must be aware that the type of the parsed property will be either or . It is not possible to determine the correct way to parse the enum property without the custom enum definition, which is why you will instead be given a property of type `string` or `int` when accessing the property in DotTiled. This can lead to inconsistencies between the map in Tiled and the loaded map with DotTiled. It is therefore recommended to define your custom enum types in DotTiled if you want to access the properties as instances. + ## Mapping properties to C# classes or enums So far, we have only discussed how to define custom property types in DotTiled, and why they are needed. However, the most important part is how you can map properties inside your maps to their corresponding C# classes or enums. From 88ceee46e5849d68f8295176bc6e59d6edea4555 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Wed, 27 Nov 2024 22:53:28 +0100 Subject: [PATCH 31/33] Add some test cases for the enum parsing bug and fix issue with .tmj format --- ...map-with-custom-type-props-without-defs.cs | 85 +++++++++++++++++++ ...ap-with-custom-type-props-without-defs.tmj | 68 +++++++++++++++ ...ap-with-custom-type-props-without-defs.tmx | 25 ++++++ .../UnitTests/Serialization/TestData.cs | 1 + .../Tmj/TmjReaderBase.Properties.cs | 31 +++++-- 5 files changed, 205 insertions(+), 5 deletions(-) create mode 100644 src/DotTiled.Tests/TestData/Maps/map-with-custom-type-props-without-defs/map-with-custom-type-props-without-defs.cs create mode 100644 src/DotTiled.Tests/TestData/Maps/map-with-custom-type-props-without-defs/map-with-custom-type-props-without-defs.tmj create mode 100644 src/DotTiled.Tests/TestData/Maps/map-with-custom-type-props-without-defs/map-with-custom-type-props-without-defs.tmx diff --git a/src/DotTiled.Tests/TestData/Maps/map-with-custom-type-props-without-defs/map-with-custom-type-props-without-defs.cs b/src/DotTiled.Tests/TestData/Maps/map-with-custom-type-props-without-defs/map-with-custom-type-props-without-defs.cs new file mode 100644 index 0000000..4134dac --- /dev/null +++ b/src/DotTiled.Tests/TestData/Maps/map-with-custom-type-props-without-defs/map-with-custom-type-props-without-defs.cs @@ -0,0 +1,85 @@ +using System.Globalization; + +namespace DotTiled.Tests; + +public partial class TestData +{ + public static Map MapWithCustomTypePropsWithoutDefs() => new Map + { + Class = "", + Orientation = MapOrientation.Orthogonal, + Width = 5, + Height = 5, + TileWidth = 32, + TileHeight = 32, + Infinite = false, + ParallaxOriginX = 0, + ParallaxOriginY = 0, + RenderOrder = RenderOrder.RightDown, + CompressionLevel = -1, + BackgroundColor = Color.Parse("#00000000", CultureInfo.InvariantCulture), + Version = "1.10", + TiledVersion = "1.11.0", + NextLayerID = 2, + NextObjectID = 1, + Layers = [ + new TileLayer + { + ID = 1, + Name = "Tile Layer 1", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + GlobalTileIDs = new Optional([ + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + ]), + FlippingFlags = new Optional([ + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None + ]) + } + } + ], + Properties = [ + new ClassProperty + { + Name = "customclassprop", + PropertyType = "CustomClass", + Value = [ + new BoolProperty { Name = "boolinclass", Value = true }, + new FloatProperty { Name = "floatinclass", Value = 13.37f }, + new StringProperty { Name = "stringinclass", Value = "This is a set string" } + ] + }, + new IntProperty + { + Name = "customenumintflagsprop", + Value = 6 + }, + new IntProperty + { + Name = "customenumintprop", + Value = 3 + }, + new StringProperty + { + Name = "customenumstringprop", + Value = "CustomEnumString_2" + }, + new StringProperty + { + Name = "customenumstringflagsprop", + Value = "CustomEnumStringFlags_1,CustomEnumStringFlags_2" + } + ] + }; +} diff --git a/src/DotTiled.Tests/TestData/Maps/map-with-custom-type-props-without-defs/map-with-custom-type-props-without-defs.tmj b/src/DotTiled.Tests/TestData/Maps/map-with-custom-type-props-without-defs/map-with-custom-type-props-without-defs.tmj new file mode 100644 index 0000000..74f892b --- /dev/null +++ b/src/DotTiled.Tests/TestData/Maps/map-with-custom-type-props-without-defs/map-with-custom-type-props-without-defs.tmj @@ -0,0 +1,68 @@ +{ "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":"customclassprop", + "propertytype":"CustomClass", + "type":"class", + "value": + { + "boolinclass":true, + "floatinclass":13.37, + "stringinclass":"This is a set string" + } + }, + { + "name":"customenumintflagsprop", + "propertytype":"CustomEnumIntFlags", + "type":"int", + "value":6 + }, + { + "name":"customenumintprop", + "propertytype":"CustomEnumInt", + "type":"int", + "value":3 + }, + { + "name":"customenumstringflagsprop", + "propertytype":"CustomEnumStringFlags", + "type":"string", + "value":"CustomEnumStringFlags_1,CustomEnumStringFlags_2" + }, + { + "name":"customenumstringprop", + "propertytype":"CustomEnumString", + "type":"string", + "value":"CustomEnumString_2" + }], + "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/src/DotTiled.Tests/TestData/Maps/map-with-custom-type-props-without-defs/map-with-custom-type-props-without-defs.tmx b/src/DotTiled.Tests/TestData/Maps/map-with-custom-type-props-without-defs/map-with-custom-type-props-without-defs.tmx new file mode 100644 index 0000000..cadc2fa --- /dev/null +++ b/src/DotTiled.Tests/TestData/Maps/map-with-custom-type-props-without-defs/map-with-custom-type-props-without-defs.tmx @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + +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 + + + diff --git a/src/DotTiled.Tests/UnitTests/Serialization/TestData.cs b/src/DotTiled.Tests/UnitTests/Serialization/TestData.cs index 28358b3..677dfb0 100644 --- a/src/DotTiled.Tests/UnitTests/Serialization/TestData.cs +++ b/src/DotTiled.Tests/UnitTests/Serialization/TestData.cs @@ -36,6 +36,7 @@ public static partial class TestData [GetMapPath("default-map"), (string f) => DefaultMap(), Array.Empty()], [GetMapPath("map-with-common-props"), (string f) => MapWithCommonProps(), Array.Empty()], [GetMapPath("map-with-custom-type-props"), (string f) => MapWithCustomTypeProps(), MapWithCustomTypePropsCustomTypeDefinitions()], + [GetMapPath("map-with-custom-type-props-without-defs"), (string f) => MapWithCustomTypePropsWithoutDefs(), Array.Empty()], [GetMapPath("map-with-embedded-tileset"), (string f) => MapWithEmbeddedTileset(), Array.Empty()], [GetMapPath("map-with-external-tileset"), (string f) => MapWithExternalTileset(f), Array.Empty()], [GetMapPath("map-with-flippingflags"), (string f) => MapWithFlippingFlags(f), Array.Empty()], diff --git a/src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs b/src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs index 6ea39e4..ae7ea1c 100644 --- a/src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs +++ b/src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs @@ -75,11 +75,7 @@ public abstract partial class TmjReaderBase { Name = name, PropertyType = propertyType, - Value = ReadPropertiesInsideClass(valueElement, new CustomClassDefinition - { - Name = propertyType, - Members = [] - }) + Value = ReadPropertiesInsideClass(valueElement, null) }; } @@ -104,6 +100,31 @@ public abstract partial class TmjReaderBase { List resultingProps = []; + if (customClassDefinition is null) + { + foreach (var prop in element.EnumerateObject()) + { + var name = prop.Name; + var value = prop.Value; + +#pragma warning disable IDE0072 // Add missing cases + IProperty property = value.ValueKind switch + { + JsonValueKind.String => new StringProperty { Name = name, Value = value.GetString() }, + JsonValueKind.Number => value.TryGetInt32(out var intValue) ? new IntProperty { Name = name, Value = intValue } : new FloatProperty { Name = name, Value = value.GetSingle() }, + JsonValueKind.True => new BoolProperty { Name = name, Value = true }, + JsonValueKind.False => new BoolProperty { Name = name, Value = false }, + JsonValueKind.Object => new ClassProperty { Name = name, PropertyType = "", Value = ReadPropertiesInsideClass(value, null) }, + _ => throw new JsonException("Invalid property type") + }; +#pragma warning restore IDE0072 // Add missing cases + + resultingProps.Add(property); + } + + return resultingProps; + } + foreach (var prop in customClassDefinition.Members) { if (!element.TryGetProperty(prop.Name, out var propElement)) From 111403d7fca8ade6e4410c27ecdab3f21849f3c1 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Mon, 2 Dec 2024 21:33:09 +0100 Subject: [PATCH 32/33] Fix benchmarks and update ratio numbers in README --- README.md | 16 ++++++++-------- src/DotTiled.Benchmark/Program.cs | 4 ++-- src/DotTiled/README.md | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 1e9dd15..66e4d04 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ Other similar libraries exist, and you may want to consider them for your projec |**Comparison**|**DotTiled**|[TiledLib](https://github.com/Ragath/TiledLib.Net)|[TiledCSPlus](https://github.com/nolemretaWxd/TiledCSPlus)|[TiledSharp](https://github.com/marshallward/TiledSharp)|[TiledCS](https://github.com/TheBoneJarmer/TiledCS)|[TiledNet](https://github.com/napen123/Tiled.Net)| |---------------------------------|:-----------------------:|:--------:|:-----------:|:----------:|:-------:|:------:| | Actively maintained | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | -| Benchmark (time)* | 1.00 | 1.83 | 2.16 | - | - | - | -| Benchmark (memory)* | 1.00 | 1.43 | 2.03 | - | - | - | +| Benchmark (time)* | 1.00 | 1.78 | 2.11 | - | - | - | +| Benchmark (memory)* | 1.00 | 1.32 | 1.88 | - | - | - | | .NET Targets | `net8.0` | `net8.0` |`netstandard2.1`|`netstandard2.0`|`netstandard2.0`|`net45`| | Docs |Usage, API,
XML Docs|Usage|Usage, API,
XML Docs|Usage, API|Usage, XML Docs|Usage, XML Docs| | License | MIT | MIT | MIT | Apache-2.0 | MIT | BSD 3-Clause | @@ -36,7 +36,7 @@ Benchmark details The following benchmark results were gathered using the `DotTiled.Benchmark` project which uses [BenchmarkDotNet](https://benchmarkdotnet.org/) to compare the performance of DotTiled with other similar libraries. The benchmark results are grouped by category and show the mean execution time, memory consumption metrics, and ratio to DotTiled. ``` -BenchmarkDotNet v0.13.12, Windows 10 (10.0.19045.4651/22H2/2022Update) +BenchmarkDotNet v0.13.12, Windows 10 (10.0.19045.5131/22H2/2022Update) 12th Gen Intel Core i7-12700K, 1 CPU, 20 logical and 12 physical cores .NET SDK 8.0.202 [Host] : .NET 8.0.3 (8.0.324.11423), X64 RyuJIT AVX2 @@ -44,12 +44,12 @@ BenchmarkDotNet v0.13.12, Windows 10 (10.0.19045.4651/22H2/2022Update) ``` | Method | Categories | Mean | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio | |------------ |------------------------- |---------:|------:|-------:|-------:|----------:|------------:| -| DotTiled | MapFromInMemoryTmjString | 4.431 μs | 1.00 | 0.4349 | - | 5.58 KB | 1.00 | -| TiledLib | MapFromInMemoryTmjString | 6.369 μs | 1.44 | 0.7019 | 0.0153 | 9.01 KB | 1.61 | +| DotTiled | MapFromInMemoryTmjString | 4.602 μs | 1.00 | 0.5417 | - | 7 KB | 1.00 | +| TiledLib | MapFromInMemoryTmjString | 6.385 μs | 1.39 | 0.7019 | 0.0153 | 9.01 KB | 1.29 | | | | | | | | | | -| DotTiled | MapFromInMemoryTmxString | 3.125 μs | 1.00 | 1.2817 | 0.0610 | 16.36 KB | 1.00 | -| TiledLib | MapFromInMemoryTmxString | 5.709 μs | 1.83 | 1.8005 | 0.0916 | 23.32 KB | 1.43 | -| TiledCSPlus | MapFromInMemoryTmxString | 6.757 μs | 2.16 | 2.5940 | 0.1831 | 33.16 KB | 2.03 | +| DotTiled | MapFromInMemoryTmxString | 3.216 μs | 1.00 | 1.3733 | 0.0610 | 17.68 KB | 1.00 | +| TiledLib | MapFromInMemoryTmxString | 5.721 μs | 1.78 | 1.8005 | 0.0916 | 23.32 KB | 1.32 | +| TiledCSPlus | MapFromInMemoryTmxString | 6.696 μs | 2.11 | 2.5940 | 0.1831 | 33.23 KB | 1.88 | It is important to note that the above benchmark results come from loading a very small map with a single tile layer as I had to find a common denominator between the libraries so that they all could load the same map. The results aim to be indicative of the performance of the libraries, but should be taken with a grain of salt. Only the actively maintained libraries are included in the benchmark results. TiledCSPlus does not support the `.tmj` format, so it was not included for that benchmark category. diff --git a/src/DotTiled.Benchmark/Program.cs b/src/DotTiled.Benchmark/Program.cs index 40e695b..5378abc 100644 --- a/src/DotTiled.Benchmark/Program.cs +++ b/src/DotTiled.Benchmark/Program.cs @@ -15,10 +15,10 @@ namespace DotTiled.Benchmark [HideColumns(["StdDev", "Error", "RatioSD"])] public class MapLoading { - private readonly string _tmxPath = @"DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.tmx"; + private readonly string _tmxPath = @"DotTiled.Tests/TestData/Maps/default-map/default-map.tmx"; private readonly string _tmxContents = ""; - private readonly string _tmjPath = @"DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.tmj"; + private readonly string _tmjPath = @"DotTiled.Tests/TestData/Maps/default-map/default-map.tmj"; private readonly string _tmjContents = ""; public MapLoading() diff --git a/src/DotTiled/README.md b/src/DotTiled/README.md index 924b9ee..3f295a2 100644 --- a/src/DotTiled/README.md +++ b/src/DotTiled/README.md @@ -15,8 +15,8 @@ Other similar libraries exist, and you may want to consider them for your projec |**Comparison**|**DotTiled**|[TiledLib](https://github.com/Ragath/TiledLib.Net)|[TiledCSPlus](https://github.com/nolemretaWxd/TiledCSPlus)|[TiledSharp](https://github.com/marshallward/TiledSharp)|[TiledCS](https://github.com/TheBoneJarmer/TiledCS)|[TiledNet](https://github.com/napen123/Tiled.Net)| |---------------------------------|:-----------------------:|:--------:|:-----------:|:----------:|:-------:|:------:| | Actively maintained | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | -| Benchmark (time)* | 1.00 | 1.83 | 2.16 | - | - | - | -| Benchmark (memory)* | 1.00 | 1.43 | 2.03 | - | - | - | +| Benchmark (time)* | 1.00 | 1.78 | 2.11 | - | - | - | +| Benchmark (memory)* | 1.00 | 1.32 | 1.88 | - | - | - | | .NET Targets | `net8.0` | `net8.0` |`netstandard2.1`|`netstandard2.0`|`netstandard2.0`|`net45`| | Docs |Usage, API,
XML Docs|Usage|Usage, API,
XML Docs|Usage, API|Usage, XML Docs|Usage, XML Docs| | License | MIT | MIT | MIT | Apache-2.0 | MIT | BSD 3-Clause | From a38df45869d1d64a66104f36742b253d78c7e92b Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Mon, 2 Dec 2024 22:00:04 +0100 Subject: [PATCH 33/33] Add new version stuff --- docs/docs/essentials/representation-model.md | 2 +- src/DotTiled/DotTiled.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/essentials/representation-model.md b/docs/docs/essentials/representation-model.md index 9fef3e0..b1b6ef7 100644 --- a/docs/docs/essentials/representation-model.md +++ b/docs/docs/essentials/representation-model.md @@ -14,4 +14,4 @@ The representation model is designed to be compatible with the latest version of | Tiled version | Compatible DotTiled version(s) | |---------------|--------------------------------| -| 1.11 | 0.1.0, 0.2.0, 0.2.1 | \ No newline at end of file +| 1.11 | 0.1.0, 0.2.0, 0.2.1, 0.3.0 | \ No newline at end of file diff --git a/src/DotTiled/DotTiled.csproj b/src/DotTiled/DotTiled.csproj index a5d41c3..6e8de2a 100644 --- a/src/DotTiled/DotTiled.csproj +++ b/src/DotTiled/DotTiled.csproj @@ -18,7 +18,7 @@ true Copyright © 2024 dcronqvist LICENSE - 0.2.1 + 0.3.0