From 50036075f56006a6b0da71e11c8942e3626f07f4 Mon Sep 17 00:00:00 2001 From: differenceclouds Date: Thu, 14 Nov 2024 09:26:32 -0500 Subject: [PATCH 01/21] "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 02/21] 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 03/21] 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 04/21] 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 05/21] 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 06/21] 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 07/21] 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 08/21] 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 09/21] 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 10/21] 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 11/21] 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 12/21] 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 zcmYLvbwE@9_x?qYl5PY9X;5kDF}g#L6zN6*328P;T2eYi>28r24T^N0E=~3ge=*hH!!*8&C>WN~*0FCVK$li_mjGkrJ?XA|=3A`b zaI7w`BykP=U@Ab?KVH5VkX6PC&duO_4HV!2mcusIo4~6VfF&37?-UT6dz~481(<$$ zN{v;J1h7!rMJWP~Qb6UHVT=-BEC5j1YxGF~^Dh7aH6wdfptc3*8YO#F58x940=iM3 zcmSLLz;cL<%@+vI1gMlxjigVWRg)ojF_Frwmu_X3Pzo`{f8ma2WW@J`dsL0~8I`Ej z1FI}qz8=3!dZ7>z(zUZc08o%bjq&yjt2uv^mG1($H7v#!7k`Rjzb(r^G5|AuVU@%NoAXWf}KU9vk(8pM(Jx}>%zkJ*4Dgw zpS-EnZ=)czLytw55$gR7L>hT@vDUf49wKBGqJne1*7N6FvxsplnIPPH`EP;>vgOe| zl4**yPtCg3n3rl(m%=?>F)Qv&sE9RMKAESVWAfg11OEIDTL%6TsCAaX3P^fUYG#9d zsli2N?A5h#g~_Zox9(|feC%Myu=O#Y`y(()rGNtnwpUAb2LN+Lc0T>z4d6k108lIl z;jNWpIPa!=-i6E9{cySa5zXjc*Z5>l@VnkH&eXe||c9IjmW zXx!FIFe`zkAbZ@S@i8b)^ebY#!jW__K}KOvpz9wUo^aoL0ltA=`8x6E1SvMdb*z;{ z6$6iJ4J)zRqAzU5>U?l0LX^4)L~(rNS*)JOsTyk)Q5T(#3#!eBfAjH+0_&}}hyZ$7_r1GSz9^20I zvTPC6$80IT7H!|v<4JsZ&hl*N(^l?Q@0Rcu!`9PNQ^H(pS@-YPuQ#~$emkqvZsyJ`#9@H76QF_+cHp0ZLy&8vab>f!s^0C4SHu^?7?`O8Sd9PUfKi*?vnH~NX#nL z5zht>L$QcvtDxQ6cjam6X~q?*6}lBFdmW?cRP&X;D`NQCrsxB6Eji4{>ogC}({w9Y_3J>~G6wOZ^ieLiB^N40 z#<&ec?6^xrqjb{!FWudwnfM2nPIMK_dSw-gtWvGg*Ta)qC#1j4D1TNajdL1!yUD)k zGT<`sjT4viDW`_kuGV3eo>pryPqCDyl;+^bpOM0m+02*OAi+5yL^dLOE*sur`NsFn z^XAg#A9iZ>TW^X@4x8IuKfRGKDKa*AW7eQtm0QIz+g8<9LSE9UouXZlH|x+_d(!-? zxvYi0c6%Oosk+&vImpVvn%1GEzq_78jtiw8?C*gS zxski^XfuE|gMY~EgWuQDp7T#7G-Z4g&n44jHKVk}zZ}bv@s`rWahy{6aKsb(^)U4~ zu<}ocPDITsyybnOeX7c<<)6Ht)c(?sy+?$d+*^&yHEA#e%II*{wJw;zuq+@Rz5U&LD(!`{7?T4_Z%-? zy@Q>RJ!~ZUw?tY+-m{T|KMlW^62)?k^6w8QdMMVDn~Neej9xGq|Lv%BmZ+C@_gf;| zq<;7;!VtGo`bSJNugi>1#eq?JHKc>nU+Z0ndH3(o?k22vdyfnVw?75#Ezcd8=@|2y zz?-aYBXSvJsH0=5sg`KdsiE`S%f8zTk#4_Xnaf`q%PRU>ZO7~? z?%H+YkRhP`Qe=N-$+Z0nCG)Fu$pFhhYsP-Y?oREl;R;+GUA}XjcIRSNrS0N=Fo->g zjlyFiX`XAHo?)5<>FXuTKHF0v8D#E6=N|dIAQmMy_?$Da!S+GXZ)@j@0Dkp^SaQ)j zE}(K9Z;IKQ$$^RASw8DDOF8>>)}5)f=}(#(X_r`_UQ_CRhyVI^_~Y>Hl(4^l&vT4L{_1W^ZnEB4grf z`e}L!%q6f0wcE8vU$ZpOGw`JIm#?s(`MKfULATGc57W^%IH}H7APbcH-tngH;`MmR zT?&0l$~_Kt#u?H^_{9S+OhKL-E=hXKINJ?6Lr03QVaVAm1=Br^bj)-Bnh zUl{<{YSa|v^nDli&Bojq=5p^7S(S7tUI?Wrjm$Q4^H<{3J*9O`5pjGq#7#3_7}>G^ zHS(}7y`Oa+{59akW$n;RWCwKz=8UOjEC%I#o*@(^$9tJ-tY2!YXd;BM#1l<%C4iLa z^a~U|`^&{+N-Cf^ax8V0J9vXeQ{UaxpvSu<%zf<9!zWUv{*1zlCYJEcxhZ&)AQ-{ADYd}fL z)Y<|77O;71#9zcStxpX>-bHuz2XAiL5v7<>=tWSw1UfalRj$DpYbDY@#0RXf*uzGiK0gF#S)2Z4xl z4FJr!VSwo4CZp1s#klK#^9AetPGKuB?w5oM7w03+aJbRw)cnFK7SQgw=Yj#jmn0bH z=uajEVaH+SCj|ug-=%cs%UVsS4p)@dS6wLARpgN89%<)|=Sl}^e49!ww|Tj^iNhRp zU!xz(eY=byx8Y0w$2Kq5OX`1LNvD>f`3WXU&Q4xlO1}U8NIv0^z0K!GL}Xf6eh}H-zcn6W$h54;#`BdZl!5Ky5Ynx zd-a)gbqDTafPbNCKX8)7cZa_Rhv%e!M%qltf2&wcKIvTDQNxVWosz(%>`2OcQk7HJ zvlXX)L6X(Pq?qp=Vvm%_|$!FvmJ<#bp;|1Hcs2IpLu>Hpnpz3X^zKh zO2*#V%_3w3wVK5Dy`XVaw_u?sPhyMTcU^rwRGpHSW<6c=c^K}n6 z^q_!37>QkeUz3!Om+t3NhD02wB$7XsI303DfKcs{ZcWQw=@z+cT$Y#wS^VtukH5L7 z&snMK^XEI`@vLzbnPw5n&^VfLjX#v)0hS|{IMht43Fm0ejgq4;qK5RsKSWD0&a&R>vcz3nndrh_ ziY~Z0E6>7XZr)__^VSTFJR^iw2Lg(5c8|(d=6|@cvL;YMgR@*VYx?mu`<-L@b8Z zJ67RN_t%Wr2EJj0yF{I(T>TCUb$n3D+f?rmbYu5lFfCjQ-p$R@Lk_ zZ|Amc`$f*|RZ>>A606j&L%FLjRSVHzQ~##7ijs7OEL_E|bj&20mSJ(`4&saJ{B2;e zu;L;Bh)fOI-z&NH08h0*%l9n71Ly_v3&rBFFccl95EI6o`N>TpZSS$El|9s42IJuH zuBi1%Mzn8*jT0k#@_s%?bu_#6MTg9_k88(gW9bOr=OjT5G01(QP8V17;P^YBeT#}I zKxHo*L_k{5IGJ)?lzKNV@{FN|V5P_Fc=HhpfgA~FB9I4qD{*ErG-~4XXmN)tkDX3U zOGcDmT^n-$WLNF-N&yu8u<^weX_F$$Rr&$5#QAd1@L~Q)JZlj4k8e!j8{O$n1adc2 zs?KMVGGAYOtpX=zrER!}B0RApy~XINavFF55o(E*&tWlRV)TQVl+~bKdMBb39m@%n1=)!A zLy*{%Y+v8a-%t%bpMT+&X{k)|SkN?1EJ@)wT4K|Z7$$87}gAA=e%s7R-LsJc|Cv*iSU zJ{$Mq(P5;rZEhG!f3U{_R(IO}MUw{HXN{omUZcTfr`{S7Fe3Bq zn}&snuT_+Ze#cDFH^A(w?0&)$5{56+fB<14>LCm=UKe9 zR<1~`U9(bbf@VZ9zxso-6}`UxTd@@1Wyc-UEzG2FcwEgE-iX|g_kCwe8PQa zu&ikJj-1qplddkYZxI)IF{rK_|7;q+zQ?yaY`yUYdUhTvbi$edruD~##-JtwteGnP7b=_Vx@84 zysG)C^)v{(dfgz8Ss*z}|I%&z8Qj_Wv>TeO?=Kc>IrOEVaUIk6FWtDc022~0>fj4G zZCrYblT)a8*@=*^*e+};$ad_gm^s+nXWEGS&jH#DCb(J79L(iQi^A`LK=_Btn1MWy z%E#xQzqx)H;(e5MWki00FdZ=O+((1YQNT5=MWAtw`v*6EK|n-z*PeEApj;SBSeg{^ z@?d`V#$fNn!ur`M>Mn%@Ic^6AT8?`!k>*+e+D*oOOyis%is}+Y4oYR8RTW1k?ICwm>k=zYMKQ9n)doOP{gHB=NXQ))&O}1j!5QNIcIs6Sdm*l+(HN8i zJi;!a!~7|r<*xdLly~(lmOv;%-c6ShjFQ#wJ(>;S3PWT@X->v|#7Fw*=flgNfP=8H z7r^D~c+^MSm)IX39Zmmdl=1{)_O(9js_j*@1-pKmKg8pZeiudYJJgBS8L(y@Z#}^4 z3g}pw_P)CbDNWWac3lv}mhzQ(+;zoosibZ8S;dx2d+bO*VJx8I+bXQT&8R4O5fe<> zYnFVdM#5(Qp{HihwH6L%@-hxfA@$yk9f>VEm%QAlMau&MDvhHCwcc8D7ErfGJGK_GC4G&$`rwcLmLBbTq5)&u z|1pC@rr>Z#CdtXR0%l4XY9f7A(Nx#EVEsvA(CnjVZaiCV{FU877f@$tXD8Tj@f;kz1j!Q<+>IL=)5cr0sOFYm@3N@Un=L^2NHtiwZQ7;wmDb(I< z)T8Y9{!r?IZ4HK)ACs-u-#rN{jlB8^m4;OB-u%MZr4f+0V0*RWP!QTsxy!E!3(kfB zYwxdozmBIfvzYHmpJOL>VikUWDYRBv#woA0MYeaNm9wBE5}e?aou%*(l^R+3`()aZ ztbJf@+r+a~F@2us$WK{p(J{5LO!X>Z@gw<(1x#=%y?W34zL`wrv9c?age&fvwvqD< zFyKs}Kk4+{Q>);T@X;I79g|#j-x3)l?u80GI-I+DtE9*z;Aq)D+wc6sgZ@N5L=BRc zk-GNqg_R>+@H}!8gR+f4?X=W}l?WeGn)itFqzkqWQ)68Rgym0G56}+{#xURrlbKpz zZ2mn=@~m9}$BdMGnO3h*!1oKJnJHWtS(^zM#&v0c+TC_IrqA%!Pnz^ls7v8Q$;S$% zkr*&3vv*^V4~l}|YVWPB&{o%Mi!B!Fjk&1|A&Qt~-~I*I1G2@i3b|JcT=|gfSv?&~ zU;mI|`c6SEWK!%RBHf;Ov5RpVM+8WnTiTtKQCW^s|L=eF6jnM~`RyeP{g9az_@4SErMNnlxB|e_lX|KLmEH zFbwq7W}ATm#W}kyMf-*uh%s)=`J=6D%60jZ1b)eJ{XLmN6s$+aV-4lF61YSY&v5Tl zgap*z$e?ZSRV3u*6SCa4Eh(cvC*X!RVNhiJI}^Z$U<%d$B;-5O+n(%>z71ycmxySC z#vQevcb`-|YR8!WN7>xYe68NT7rE%9o$)t;v<(5vJ*L}( zcy-WN8?Nf({JZr`3Qyfu7;VknR26iv|1m390TbH?R!GIK+Hy)8@XvrQNt`o#RjOto zo%Szh5Y#|iSzSL8`9m$)98*pouf|`8&BF{wnb5o%+i34=i^_E~br6<;i%x}|n8m8| z7fLlKOiRu{-&ZOX?Js+AjFUgA)Br8Nw$K~$tZjo4qQl;3f7_1;QVM~-r6#9;K;>PR zO?o6^Vb1BIDY_W@!KRq^Vjr@%UPbYta2^ap0Xc_9;ZQ0?R_&5v8|&1PeW_mAt*|}| z9=bmoR)h`{h=*0bigzw2ePTI%%SY0c$glUXIy%G5v;1yN3@`S*{=5UV<6Agg95HK6 z+3mDtWk$bp>3kl;a%iDk^KJIUltH(}J|2tdUIPEqhXpvYH2tau$+GF!JDybG3P&m? zLMG+!gsFh)`sV_7jZP-fc9!HhWw{EKq2kqf_hDX4or&Aq+x4CBCTg!1|;COZU#mSVDAH zW-{B+zeNJQ)bG&*iRaLx(HB(AUDc$*z6dPhoDU|p`_;W9O;SVh@=O{ ztETXWt3r}Xl}5>3cLAX2`?PMB)quS=&do0 zz8}-uBVBrf>w8$J;Q%n3X399p6CmEw87WKOwacYfWtmq|*uds%Vje_x{MmKeQn^M$ z!)I=mPw~V}{l-M*qeNL+zpI7Y`WPLR;U;`RSVpd`bs2y=IBRP`FEJ@r?Tcx>OT<7yz zPQ){1yY#Z@v)De~ibzt%PAP@pIlAT9vx;T-uL_tig63|&IM1v`pI+LH0!K}nbzL~6 z#IWyjtI$2L$S(}Ke|Bu9vhJz%f>aW6P}?1H3TO1Fu_>(piyyiS^rW{BHs7;=%VGp7w=d1oS&L{ z$pfnrtcgl0K}HvAcxf==4x4F@SaT0&*hk@;hZ_Ns7+u94?V6P-w&TTk0?GxsNivBM zzbx{$KLIh_4T2&$nyw44a-d?l5!3$N_m|T+))%1v1Q!4G$HR;{8H_5Gor9MF)*j0u z`d=2K`*#Bz8#_-v!Ahiog<7o&?KZ04QuW8qG0V>L8%>bCoiPUK8$d6W9iz&envH39 z9IMpS+qZZ9Y^G^iv4+5UxW#|`1P6FcSt#Av)Zg52qE2cw0^9D+fZXZ0o4BeaAE93 z5M%z&y#Xun%2?8q69SPy--C~#h&B`^*^8SE0$rrwi@$d`I+OF2%ai-kip{xL7@AgQ znJxWcQE$4&@`pJx#<~&?U#42 zZVATy-2HV8C?xQwaeQ;r)Il&a5XvtyJrg*3_?EN=T=mADV(}(J@yL(r&fx>D!n3uG z{c@)r@9W439oa?4?-kVI>-Ph~z>Ghn!vFS+1RBi-IYrX`oaDkRplgW0xQ1Q#r?>SI zshmBZauc-B5X0r|oSVvNp7nw&ArI=UirWp>!=Z(B?%+#K;b8rMpDB-Fc9No#^8@DC z#l$dam-m;dh3VHq$w2&)L5_p(nuNo>nC=6C@+xZ+xa|5?xl>$fc6XZfre-MEg9XyS zU&hs;`pL+}k(X1}+8!&{vImq8--8l-sXQ;svABntQ zYC!Dgyzgw=kCf6mzVqc1$I9k(c%69FGK}A7O5pOV_SZY56r8cG)d%M9FUVzT(9n_G z{Cs8yCEa}~3g6%CUVF%fURXI85ZCGK{?dDc;z@b+@LnwMKjS4>35I7YV^V?6ladt2 zup;mVvl~i0@ebyQOeoVT(CWdPfct4-#c z&ijbdJ`p$XR~%69a(EdmLP<|AKx)h?W?!bdYNfd3p;< z$lgHcZQ!$*YrK9ya%FZxaZPjM3@jsvRWJO4Zs3)`L-5(kN)Khyw3X_DhX?kzG6Tyy zg{ZrhPX>R?@YHV!sx2j{kgf3F->HpPEU2NW{TjS=WKsMZNf*}NXEf-9C}9k~(=qcM zH-d||*hYPBpP=U44+u|DNrX+iqTYX@8&OaeqTu@^4S$`DbT6-cea*U07XsOMklpp% zwHs8xkI9sb0f#-*=50`T1WdKP{;sVX?i4M|bMyAH^p5A4qq$tOi{!p`l^J~9HiG@{ zx#P%a=jZ1m?K`;fDskd5OQ`FJqx(M+v;` z3`m>%^Aqqhu(5KD&*Q}}h#%Fzhc(VlF+a<^0{U0_R;=>oau_y}<&TIB?-%pXuZkpf zzQ6W|{Dea5el>JDDM^1F`pXo<+rWRF&8(6CX?N~Mo~5q4#2`Sp;!N9<{QB&m0xqVe z*TNYbh3Rtp?~a}9;!SdWes8Xgy=G0ML|1D92Z!8;j(A_)A$3ncrApW$2T|2x1HYNv zf9`OtH9c9~Lt{`EIN+zVnsjiBzrWaVn_*Vw=S_7QgpcIW7rb#F$R^55QG@Eqol0uvB<>2q?9(*Wk({{xEwGMEPz;w)Fow}{?B5huFeteDr@?$^TIbT zadb_z{Qz%73)Uckp|?Gd0IPf&#lY2M@FaNUru+f=#EKPN``&u;K~IJovoa_!akwl` z`j5XqwJ(=L*35h9Of7yX%v#pG@(vF;2r&!!Q=E=*68nQBhKE?sN99K)aIQ`!YFfm1 z9yVQ1EQY#;eV#8KGsoAmce~4t+WdGw%WNS}2oP!Il%W^jui8Ev)x5$tu>ar41tSm2 zm@bk^zsD*3*GePxV4>(N29bLTT%QHvj}y{zth01QF@@wz)~#$|9V2eY3MkP8)Ehh> znV=|Vd*8{Q8c0F@xy1Fk@#4gZJxX<0$-e$AGZ;X zaTw0%ZUx7L+xh{8R3+WatS7_YK~OMcqiK#~115Y=839(p!w@9hn5i|=q-(p$RbsTd zRhQf~AimKs^PsXP7zfUA-1*M-+&_{or@u(5j|ihDrs1@mEG+3&1>+yueU$PL4u+!T z#n{HxXO@wFlk`7whEgxR8)kfOzK&(-a%=G-zuUFFIqt`el1qo*kIo&i*#<3akoin_ zQkxp;UQSwe!fGBg1^wA~ga_qFFHb)*$gLs*rnnb-O#9dSIY=6)K#PG5WD67o+WIF?MGmNHj(qZpc+o;Ue7zR> zCt+O{(O4K;UQFChAYm2?u&fa%fx>)%WmyLHFLj%1L$MWFrk*T;wKAPz+CNc+uf-Xp z`>f|3s8dzZ5Zl2nSx4GINz>4#amT#=&d=^aZyZ!Um5!#-p6d@g%S3#W-b!x->Fv$0 z1)5ZP*)a@>sPjdn9YFy0COZDr;btl^7FTwF-2M?#`j+DUp^0iHosfbP&S`>kUx2@xvD!2vjpxzAYM7e%znNkof=RoQc6H|Ho8dE|2y3G*&pOc0?FtHC0;M@I`ySwhkv&9~yNT+)MV?nRK`F+ytDo=(Hz@^`2 z9y67vbhLATv^oH5-QIv8$c(>h0RypbAkQ?)6dD-ak2cu-Ww5*6KW50w8^rbXgp~s8q2#|4ZP#Q)Uxqg!tzUn+N`7Mk-viFRjidL zwlNs|=5*>#Y3)8nJmamRDKvO`dF<^$iuicgWW?R2=O$!7lxU0q`#-rR{OOEhVZvd3 zaY1K>tfP0+cji!xS{Uo&ifO6gAFhtbtGB@VdHhGyqyT@+eBRr>iQ8hEH$2oiQJFqX zhkhpD;F#4P1wChd!2Ww3hGMHn!;;&$1vD7e&=r5untLSU56VKl&gDPSAN&8}cI1z1 z#vmcV?@ZHgd?^Zz#;KWuB+ z&&Y=WwD0g=0(vStXQ8uRl_f0|gNPcuE~8sM9Raxu%2zMK>v!>F2URclxbCj_4b&c( zZIp~53l||KRC3EOeU9jKuGy=rMbhA$fCztBxX_$+Ob)aimxc3z$t5;^0lc?ACK2nE zxpeRo-KFWNvr%Cakj$7g5&14Cj6VwUAc(h90gO`u0jxot=*F0fn6H-K%>8n+CcHd12Z3rL8jh|+BD!L{ z-{%Cse^WQ;&`6BB$vo2L^}{mKntK|ZSI(|=97pc;68BG5jSkL{eP{b~hC~MpoZ+su zXsmq1QrPkdW?aLCT`Nqz&+eb7cvLG1+HhI?L|4U%%Otfy`mVFhyetcUm2lOyY#FWv z2E3;H)!4{5V7@OfLLJ|%ggrn`@#8b+I2fK6kwwTUUsAD18D{x7EPW9r`?n4IG6=4a z!(TIP1X~izX9w&2F~2>vsGN(#A{wsGb{mU?|MOKlQbQ&mLf^K+F+-BxrP;G1sjD5* zLgudipO!!f87y^|c_1*LlNlf{%>M5i71L)@O_coiTw*!3ej1LN{F_7F9=sVN_%q~x77=+oI3PI4+1JD($OJP=A$g3p zHWLW;Y{ST{tK9>X*^VhGUQa~Yjgh@e7t(bxq98RN*s-bw6*jR4>K@>>dx+A1)Hsa! 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 13/21] 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 14/21] 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 15/21] 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 16/21] 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 17/21] 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 18/21] 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 19/21] 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 20/21] 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 21/21] 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