mirror of
https://github.com/dcronqvist/DotTiled.git
synced 2025-05-08 23:46:04 +03:00
Compare commits
36 commits
Author | SHA1 | Date | |
---|---|---|---|
|
7f78a971f9 | ||
|
de41fb5508 | ||
|
a38df45869 | ||
|
58e03188a8 | ||
|
111403d7fc | ||
|
fc710daf8c | ||
|
88ceee46e5 | ||
|
b978b8b50d | ||
|
ade3d8840a | ||
|
f3c4478125 | ||
|
94c1ac0f32 | ||
|
6deb28c1ce | ||
|
374f2b2194 | ||
|
f192a71c56 | ||
|
416cba6604 | ||
|
7a7f360e22 | ||
|
1027b922fe | ||
|
1e41443704 | ||
|
c9e85c9fd6 | ||
|
080f95c698 | ||
|
90a57b125d | ||
|
52f148f71d | ||
|
54bc132154 | ||
|
e553c8e05a | ||
|
8c9068cc97 | ||
|
67876c6532 | ||
|
66d59ffbe1 | ||
|
feb4375cd5 | ||
|
837f58bf68 | ||
|
23d218bac7 | ||
|
cbd03bc224 | ||
|
dcdceb8b78 | ||
|
666a3433e3 | ||
|
2e8eaa5a72 | ||
|
35c6ba5002 | ||
|
50036075f5 |
65 changed files with 1442 additions and 129 deletions
2
Makefile
2
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:
|
||||
|
|
18
README.md
18
README.md
|
@ -2,7 +2,7 @@
|
|||
|
||||
<img src="https://www.mapeditor.org/img/tiled-logo-white.png" align="right" width="20%"/>
|
||||
|
||||
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.
|
||||
|
||||
|
@ -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,<br>XML Docs|Usage|Usage, API,<br>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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -138,7 +141,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 +152,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 <xref:DotTiled.CustomEnumDefinition.FromEnum``1> method, or one of its overloads. This method will generate a <xref:DotTiled.CustomEnumDefinition> 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 <xref:DotTiled.CustomEnumDefinition.FromEnum``1(DotTiled.CustomEnumStorageType)> method, or one of its overloads. This method will generate a <xref:DotTiled.CustomEnumDefinition> from a given C# enum, and you can then use this definition when loading your maps.
|
||||
|
||||
```csharp
|
||||
enum EntityType
|
||||
|
@ -171,6 +174,12 @@ The generated custom enum definition will be identical to the one defined manual
|
|||
|
||||
For enum definitions, the <xref:System.FlagsAttribute> 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 <xref:DotTiled.CustomEnumDefinition>. 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 <xref:DotTiled.CustomEnumDefinition.FromEnum``1(DotTiled.CustomEnumStorageType)> method. To be consistent with Tiled, <xref:DotTiled.CustomEnumDefinition.FromEnum``1(DotTiled.CustomEnumStorageType)> 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 <xref:DotTiled.StringProperty> or <xref:IntProperty>. 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 <xref:EnumProperty> 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.
|
||||
|
@ -200,6 +209,9 @@ var entityDataDef = CustomClassDefinition.FromClass<EntityData>();
|
|||
|
||||
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
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="5" height="5" tilewidth="32" tileheight="32" infinite="0" nextlayerid="8" nextobjectid="7">
|
||||
|
|
|
@ -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 |
|
||||
| 1.11 | 0.1.0, 0.2.0, 0.2.1, 0.3.0 |
|
|
@ -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()
|
||||
|
|
|
@ -73,12 +73,12 @@ public class Program
|
|||
return templateReader.ReadTemplate();
|
||||
}
|
||||
|
||||
private static ICustomTypeDefinition ResolveCustomType(string name)
|
||||
private static Optional<ICustomTypeDefinition> 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<ICustomTypeDefinition>(ctd) : Optional<ICustomTypeDefinition>.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ICustomTypeDefinition> 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<ICustomTypeDefinition>(ctd) : Optional<ICustomTypeDefinition>.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -177,4 +177,96 @@ public class FromTypeUsedInLoaderTests
|
|||
// 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<IResourceReader>();
|
||||
resourceReader.Read("map.tmx").Returns(
|
||||
"""
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.10" tiledversion="1.11.0" class="TestClassWithEnum" orientation="orthogonal" renderorder="right-down" width="5" height="5" tilewidth="32" tileheight="32" infinite="0" nextlayerid="2" nextobjectid="1">
|
||||
<layer id="1" name="Tile Layer 1" width="5" height="5">
|
||||
<data encoding="csv">
|
||||
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
|
||||
</data>
|
||||
</layer>
|
||||
</map>
|
||||
""");
|
||||
var classDefinition = CustomClassDefinition.FromClass<TestClassWithEnum>();
|
||||
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<uint[]>([
|
||||
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[]>([
|
||||
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<string> { "Value1" } }
|
||||
]
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = loader.LoadMap("map.tmx");
|
||||
|
||||
// Assert
|
||||
DotTiledAssert.AssertMap(expectedMap, result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<uint[]>([
|
||||
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[]>([
|
||||
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<uint[]>([
|
||||
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[]>([
|
||||
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<uint[]>([
|
||||
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[]>([
|
||||
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<ICustomTypeDefinition> 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"
|
||||
}
|
||||
];
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="5" height="5" tilewidth="32" tileheight="32" infinite="0" nextlayerid="8" nextobjectid="8">
|
||||
<tileset firstgid="1" source="tileset.tsx"/>
|
||||
<group id="2" name="Root">
|
||||
<objectgroup id="3" name="Objects">
|
||||
<object id="1" name="Object 1" x="25.6667" y="28.6667" width="31.3333" height="31.3333"/>
|
||||
<object id="3" name="P1" x="117.667" y="48.6667">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="4" name="Circle1" x="77" y="72.3333" width="34.6667" height="34.6667">
|
||||
<ellipse/>
|
||||
</object>
|
||||
<object id="5" template="poly.tx" x="20.6667" y="114.667"/>
|
||||
<object id="6" name="TileObj" gid="7" x="-35" y="110.333" width="64" height="146"/>
|
||||
<object id="7" template="random.tx" type="randomclass" x="134.552" y="113.638"/>
|
||||
</objectgroup>
|
||||
<group id="5" name="Sub">
|
||||
<layer id="7" name="Tile 3" width="5" height="5">
|
||||
<data encoding="csv">
|
||||
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
|
||||
</data>
|
||||
</layer>
|
||||
<layer id="6" name="Tile 2" width="5" height="5">
|
||||
<data encoding="csv">
|
||||
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
|
||||
</data>
|
||||
</layer>
|
||||
</group>
|
||||
<imagelayer id="4" name="ImageLayer" repeatx="1">
|
||||
<image source="tileset.png" width="256" height="96"/>
|
||||
</imagelayer>
|
||||
<layer id="1" name="Tile Layer 1" width="5" height="5">
|
||||
<data encoding="csv">
|
||||
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
|
||||
</data>
|
||||
</layer>
|
||||
</group>
|
||||
</map>
|
|
@ -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"
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<template>
|
||||
<object name="Poly">
|
||||
<properties>
|
||||
<property name="templateprop" value="helo there"/>
|
||||
</properties>
|
||||
<polygon points="0,0 104,20 35.6667,32.3333"/>
|
||||
</object>
|
||||
</template>
|
|
@ -0,0 +1,12 @@
|
|||
{ "object":
|
||||
{
|
||||
"height":0,
|
||||
"id":7,
|
||||
"name":"",
|
||||
"rotation":0,
|
||||
"type":"randomclass",
|
||||
"visible":true,
|
||||
"width":0
|
||||
},
|
||||
"type":"template"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<template>
|
||||
<object type="randomclass"/>
|
||||
</template>
|
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -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"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<tileset version="1.10" tiledversion="1.11.0" name="tileset" tilewidth="32" tileheight="32" tilecount="24" columns="8">
|
||||
<image source="tileset.png" width="256" height="96"/>
|
||||
</tileset>
|
|
@ -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<Color>.Empty }
|
||||
]
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<property name="intprop" type="int" value="8"/>
|
||||
<property name="objectprop" type="object" value="5"/>
|
||||
<property name="stringprop" value="This is a string, hello world!"/>
|
||||
<property name="unsetcolorprop" type="color" value=""/>
|
||||
</properties>
|
||||
<layer id="1" name="Tile Layer 1" width="5" height="5">
|
||||
<data encoding="csv">
|
||||
|
|
|
@ -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<uint[]>([
|
||||
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[]>([
|
||||
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"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="5" height="5" tilewidth="32" tileheight="32" infinite="0" nextlayerid="2" nextobjectid="1">
|
||||
<properties>
|
||||
<property name="customclassprop" type="class" propertytype="CustomClass">
|
||||
<properties>
|
||||
<property name="boolinclass" type="bool" value="true"/>
|
||||
<property name="floatinclass" type="float" value="13.37"/>
|
||||
<property name="stringinclass" value="This is a set string"/>
|
||||
</properties>
|
||||
</property>
|
||||
<property name="customenumintflagsprop" type="int" propertytype="CustomEnumIntFlags" value="6"/>
|
||||
<property name="customenumintprop" type="int" propertytype="CustomEnumInt" value="3"/>
|
||||
<property name="customenumstringflagsprop" propertytype="CustomEnumStringFlags" value="CustomEnumStringFlags_1,CustomEnumStringFlags_2"/>
|
||||
<property name="customenumstringprop" propertytype="CustomEnumString" value="CustomEnumString_2"/>
|
||||
</properties>
|
||||
<layer id="1" name="Tile Layer 1" width="5" height="5">
|
||||
<data encoding="csv">
|
||||
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
|
||||
</data>
|
||||
</layer>
|
||||
</map>
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="5" height="5" tilewidth="32" tileheight="32" infinite="0" nextlayerid="2" nextobjectid="1">
|
||||
<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="5" height="5" tilewidth="32" tileheight="32" infinite="0" nextlayerid="3" nextobjectid="3">
|
||||
<tileset firstgid="1" source="tileset.tsx"/>
|
||||
<layer id="1" name="Tile Layer 1" width="5" height="5">
|
||||
<data encoding="csv">
|
||||
|
@ -10,4 +10,8 @@
|
|||
2147483669,2147483669,2147483669,2147483669,1
|
||||
</data>
|
||||
</layer>
|
||||
<objectgroup id="2" name="Object Layer 1">
|
||||
<object id="1" gid="2147483669" x="80.0555" y="48.3887" width="32" height="32"/>
|
||||
<object id="2" gid="1073741845" x="15.8334" y="112.056" width="32" height="32"/>
|
||||
</objectgroup>
|
||||
</map>
|
||||
|
|
|
@ -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<uint[]>([
|
||||
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[]>([
|
||||
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 = "" }
|
||||
]
|
||||
};
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.10" tiledversion="1.11.0" orientation="isometric" renderorder="right-down" width="5" height="5" tilewidth="32" tileheight="16" infinite="0" backgroundcolor="#00ff00" nextlayerid="2" nextobjectid="1">
|
||||
<properties>
|
||||
<property name="boolprop" type="bool" value="true"/>
|
||||
<property name="colorprop" type="color" value="#ff55ffff"/>
|
||||
<property name="fileprop" type="file" value="file.txt"/>
|
||||
<property name="floatprop" type="float" value="4.2"/>
|
||||
<property name="intprop" type="int" value="8"/>
|
||||
<property name="objectprop" type="object" value="5"/>
|
||||
<property name="stringmultiline">hello there
|
||||
|
||||
i am a multiline
|
||||
string property</property>
|
||||
<property name="stringprop" value="This is a string, hello world!"/>
|
||||
<property name="unsetstringprop" value=""/>
|
||||
</properties>
|
||||
<layer id="1" name="Tile Layer 1" width="5" height="5">
|
||||
<data encoding="csv">
|
||||
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
|
||||
</data>
|
||||
</layer>
|
||||
</map>
|
|
@ -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<string> { "Value2" } },
|
||||
new EnumProperty { Name = "Flags", PropertyType = "TestFlags1", Value = new HashSet<string> { "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<object[]> CustomClassDefinitionTestData =>
|
||||
|
|
|
@ -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<TestEnum1>();
|
||||
var result = CustomEnumDefinition.FromEnum<TestEnum1>(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<TestEnum2>();
|
||||
var result = CustomEnumDefinition.FromEnum<TestEnum2>(storageType);
|
||||
|
||||
// Assert
|
||||
DotTiledAssert.AssertCustomEnumDefinitionEqual(expected, result);
|
||||
|
|
|
@ -246,7 +246,7 @@ public class LoaderTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadMap_MapHasClassAndLoaderHasNoCustomTypes_ThrowsException()
|
||||
public void LoadMap_MapHasClassAndLoaderHasNoCustomTypes_ReturnsMapWithEmptyProperties()
|
||||
{
|
||||
// Arrange
|
||||
var resourceReader = Substitute.For<IResourceReader>();
|
||||
|
@ -270,8 +270,11 @@ public class LoaderTests
|
|||
var customTypeDefinitions = Enumerable.Empty<ICustomTypeDefinition>();
|
||||
var loader = new Loader(resourceReader, resourceCache, customTypeDefinitions);
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<KeyNotFoundException>(() => loader.LoadMap("map.tmx"));
|
||||
// Act
|
||||
var result = loader.LoadMap("map.tmx");
|
||||
|
||||
// Assert
|
||||
DotTiledAssert.AssertProperties([], result.Properties);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
@ -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<ICustomTypeDefinition> ResolveCustomType(string name)
|
||||
{
|
||||
return customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name)!;
|
||||
if (customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name) is ICustomTypeDefinition ctd)
|
||||
{
|
||||
return new Optional<ICustomTypeDefinition>(ctd);
|
||||
}
|
||||
|
||||
return Optional<ICustomTypeDefinition>.Empty;
|
||||
}
|
||||
using var mapReader = new MapReader(mapString, ResolveTileset, ResolveTemplate, ResolveCustomType);
|
||||
|
||||
|
|
|
@ -36,14 +36,17 @@ public static partial class TestData
|
|||
[GetMapPath("default-map"), (string f) => DefaultMap(), Array.Empty<ICustomTypeDefinition>()],
|
||||
[GetMapPath("map-with-common-props"), (string f) => MapWithCommonProps(), Array.Empty<ICustomTypeDefinition>()],
|
||||
[GetMapPath("map-with-custom-type-props"), (string f) => MapWithCustomTypeProps(), MapWithCustomTypePropsCustomTypeDefinitions()],
|
||||
[GetMapPath("map-with-custom-type-props-without-defs"), (string f) => MapWithCustomTypePropsWithoutDefs(), Array.Empty<ICustomTypeDefinition>()],
|
||||
[GetMapPath("map-with-embedded-tileset"), (string f) => MapWithEmbeddedTileset(), Array.Empty<ICustomTypeDefinition>()],
|
||||
[GetMapPath("map-with-external-tileset"), (string f) => MapWithExternalTileset(f), Array.Empty<ICustomTypeDefinition>()],
|
||||
[GetMapPath("map-with-flippingflags"), (string f) => MapWithFlippingFlags(f), Array.Empty<ICustomTypeDefinition>()],
|
||||
[GetMapPath("map-external-tileset-multi"), (string f) => MapExternalTilesetMulti(f), Array.Empty<ICustomTypeDefinition>()],
|
||||
[GetMapPath("map-external-tileset-wangset"), (string f) => MapExternalTilesetWangset(f), Array.Empty<ICustomTypeDefinition>()],
|
||||
[GetMapPath("map-with-many-layers"), (string f) => MapWithManyLayers(f), Array.Empty<ICustomTypeDefinition>()],
|
||||
[GetMapPath("map-with-multiline-string-prop"), (string f) => MapWithMultilineStringProp(), Array.Empty<ICustomTypeDefinition>()],
|
||||
[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()],
|
||||
];
|
||||
}
|
||||
|
|
|
@ -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<ICustomTypeDefinition> ResolveCustomType(string name)
|
||||
{
|
||||
return customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name)!;
|
||||
if (customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name) is ICustomTypeDefinition ctd)
|
||||
{
|
||||
return new Optional<ICustomTypeDefinition>(ctd);
|
||||
}
|
||||
|
||||
return Optional<ICustomTypeDefinition>.Empty;
|
||||
}
|
||||
using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate, ResolveCustomType);
|
||||
|
||||
|
|
|
@ -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<ICustomTypeDefinition> ResolveCustomType(string name)
|
||||
{
|
||||
return customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name)!;
|
||||
if (customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name) is ICustomTypeDefinition ctd)
|
||||
{
|
||||
return new Optional<ICustomTypeDefinition>(ctd);
|
||||
}
|
||||
|
||||
return Optional<ICustomTypeDefinition>.Empty;
|
||||
}
|
||||
using var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate, ResolveCustomType);
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<Copyright>Copyright © 2024 dcronqvist</Copyright>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<Version>0.2.1</Version>
|
||||
<Version>0.3.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -9,4 +9,9 @@ public class TileObject : Object
|
|||
/// A reference to a tile.
|
||||
/// </summary>
|
||||
public uint GID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The flipping flags for the tile.
|
||||
/// </summary>
|
||||
public FlippingFlags FlippingFlags { get; set; }
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ namespace DotTiled;
|
|||
/// <summary>
|
||||
/// Represents a color property.
|
||||
/// </summary>
|
||||
public class ColorProperty : IProperty<Color>
|
||||
public class ColorProperty : IProperty<Optional<Color>>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public required string Name { get; set; }
|
||||
|
@ -14,7 +14,7 @@ public class ColorProperty : IProperty<Color>
|
|||
/// <summary>
|
||||
/// The color value of the property.
|
||||
/// </summary>
|
||||
public required Color Value { get; set; }
|
||||
public required Optional<Color> Value { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IProperty Clone() => new ColorProperty
|
||||
|
|
|
@ -165,6 +165,17 @@ 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 enumDefinition = CustomEnumDefinition.FromEnum(t);
|
||||
|
||||
if (!enumDefinition.ValueAsFlags)
|
||||
return new EnumProperty { Name = propertyInfo.Name, PropertyType = t.Name, Value = new HashSet<string> { propertyInfo.GetValue(instance).ToString() } };
|
||||
|
||||
var flags = (Enum)propertyInfo.GetValue(instance);
|
||||
var enumValues = Enum.GetValues(t).Cast<Enum>();
|
||||
var enumNames = enumValues.Where(flags.HasFlag).Select(e => e.ToString());
|
||||
|
||||
return new EnumProperty { Name = propertyInfo.Name, PropertyType = t.Name, Value = enumNames.ToHashSet() };
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -51,8 +51,9 @@ public class CustomEnumDefinition : ICustomTypeDefinition
|
|||
/// Creates a custom enum definition from the specified enum type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="storageType">The storage type of the custom enum. Defaults to <see cref="CustomEnumStorageType.String"/> to be consistent with Tiled.</param>
|
||||
/// <returns></returns>
|
||||
public static CustomEnumDefinition FromEnum<T>() where T : Enum
|
||||
public static CustomEnumDefinition FromEnum<T>(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
|
|||
/// <summary>
|
||||
/// Creates a custom enum definition from the specified enum type.
|
||||
/// </summary>
|
||||
/// <param name="type">The enum type to create a custom enum definition from.</param>
|
||||
/// <param name="storageType">The storage type of the custom enum. Defaults to <see cref="CustomEnumStorageType.String"/> to be consistent with Tiled.</param>
|
||||
/// <returns></returns>
|
||||
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
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
@ -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,<br>XML Docs|Usage|Usage, API,<br>XML Docs|Usage, API|Usage, XML Docs|Usage, XML Docs|
|
||||
| License | MIT | MIT | MIT | Apache-2.0 | MIT | BSD 3-Clause |
|
||||
|
|
|
@ -86,13 +86,16 @@ internal static partial class Helpers
|
|||
};
|
||||
}
|
||||
|
||||
internal static List<IProperty> ResolveClassProperties(string className, Func<string, ICustomTypeDefinition> customTypeResolver)
|
||||
internal static List<IProperty> ResolveClassProperties(string className, Func<string, Optional<ICustomTypeDefinition>> 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<IProperty> CreateInstanceOfCustomClass(
|
||||
CustomClassDefinition customClassDefinition,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver)
|
||||
Func<string, Optional<ICustomTypeDefinition>> 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)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ public class Loader
|
|||
{
|
||||
private readonly IResourceReader _resourceReader;
|
||||
private readonly IResourceCache _resourceCache;
|
||||
private readonly IDictionary<string, ICustomTypeDefinition> _customTypeDefinitions;
|
||||
private readonly Dictionary<string, ICustomTypeDefinition> _customTypeDefinitions;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Loader"/> class with the given <paramref name="resourceReader"/>, <paramref name="resourceCache"/>, and <paramref name="customTypeDefinitions"/>.
|
||||
|
@ -114,5 +114,11 @@ public class Loader
|
|||
return templateReader.ReadTemplate();
|
||||
});
|
||||
|
||||
private ICustomTypeDefinition CustomTypeResolver(string name) => _customTypeDefinitions[name];
|
||||
private Optional<ICustomTypeDefinition> CustomTypeResolver(string name)
|
||||
{
|
||||
if (_customTypeDefinitions.TryGetValue(name, out var customTypeDefinition))
|
||||
return new Optional<ICustomTypeDefinition>(customTypeDefinition);
|
||||
|
||||
return Optional<ICustomTypeDefinition>.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ public class MapReader : IMapReader
|
|||
// External resolvers
|
||||
private readonly Func<string, Tileset> _externalTilesetResolver;
|
||||
private readonly Func<string, Template> _externalTemplateResolver;
|
||||
private readonly Func<string, ICustomTypeDefinition> _customTypeResolver;
|
||||
private readonly Func<string, Optional<ICustomTypeDefinition>> _customTypeResolver;
|
||||
|
||||
private readonly StringReader _mapStringReader;
|
||||
private readonly XmlReader _xmlReader;
|
||||
|
@ -33,7 +33,7 @@ public class MapReader : IMapReader
|
|||
string map,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver)
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver)
|
||||
{
|
||||
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
||||
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
||||
|
|
|
@ -14,7 +14,7 @@ public class TemplateReader : ITemplateReader
|
|||
// External resolvers
|
||||
private readonly Func<string, Tileset> _externalTilesetResolver;
|
||||
private readonly Func<string, Template> _externalTemplateResolver;
|
||||
private readonly Func<string, ICustomTypeDefinition> _customTypeResolver;
|
||||
private readonly Func<string, Optional<ICustomTypeDefinition>> _customTypeResolver;
|
||||
|
||||
private readonly StringReader _templateStringReader;
|
||||
private readonly XmlReader _xmlReader;
|
||||
|
@ -33,7 +33,7 @@ public class TemplateReader : ITemplateReader
|
|||
string template,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver)
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver)
|
||||
{
|
||||
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
||||
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
||||
|
|
|
@ -14,7 +14,7 @@ public class TilesetReader : ITilesetReader
|
|||
// External resolvers
|
||||
private readonly Func<string, Tileset> _externalTilesetResolver;
|
||||
private readonly Func<string, Template> _externalTemplateResolver;
|
||||
private readonly Func<string, ICustomTypeDefinition> _customTypeResolver;
|
||||
private readonly Func<string, Optional<ICustomTypeDefinition>> _customTypeResolver;
|
||||
|
||||
private readonly StringReader _tilesetStringReader;
|
||||
private readonly XmlReader _xmlReader;
|
||||
|
@ -33,7 +33,7 @@ public class TilesetReader : ITilesetReader
|
|||
string tileset,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver)
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver)
|
||||
{
|
||||
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
||||
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
||||
|
|
|
@ -19,7 +19,7 @@ public class TjTemplateReader : TmjReaderBase, ITemplateReader
|
|||
string jsonString,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver) : base(
|
||||
jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||
{ }
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ public class TmjMapReader : TmjReaderBase, IMapReader
|
|||
string jsonString,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver) : base(
|
||||
jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||
{ }
|
||||
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
@ -35,7 +36,7 @@ public abstract partial class TmjReaderBase
|
|||
PropertyType.Int => new IntProperty { Name = name, Value = e.GetRequiredProperty<int>("value") },
|
||||
PropertyType.Float => new FloatProperty { Name = name, Value = e.GetRequiredProperty<float>("value") },
|
||||
PropertyType.Bool => new BoolProperty { Name = name, Value = e.GetRequiredProperty<bool>("value") },
|
||||
PropertyType.Color => new ColorProperty { Name = name, Value = e.GetRequiredPropertyParseable<Color>("value") },
|
||||
PropertyType.Color => new ColorProperty { Name = name, Value = e.GetRequiredPropertyParseable<Color>("value", s => s == "" ? default : Color.Parse(s, CultureInfo.InvariantCulture)) },
|
||||
PropertyType.File => new FileProperty { Name = name, Value = e.GetRequiredProperty<string>("value") },
|
||||
PropertyType.Object => new ObjectProperty { Name = name, Value = e.GetRequiredProperty<uint>("value") },
|
||||
PropertyType.Class => throw new JsonException("Class property must have a property type"),
|
||||
|
@ -63,21 +64,34 @@ public abstract partial class TmjReaderBase
|
|||
var propertyType = element.GetRequiredProperty<string>("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<List<IProperty>>("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, null)
|
||||
};
|
||||
}
|
||||
|
||||
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<List<IProperty>>("value", e => ReadPropertiesInsideClass(e, ccd)).GetValueOr([]);
|
||||
var mergedProps = Helpers.MergeProperties(propsInType, props);
|
||||
|
||||
return new ClassProperty
|
||||
{
|
||||
Name = name,
|
||||
PropertyType = propertyType,
|
||||
Value = mergedProps
|
||||
};
|
||||
}
|
||||
|
||||
internal List<IProperty> ReadPropertiesInsideClass(
|
||||
|
@ -86,11 +100,63 @@ public abstract partial class TmjReaderBase
|
|||
{
|
||||
List<IProperty> 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))
|
||||
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<string>() },
|
||||
|
@ -100,8 +166,8 @@ public abstract partial class TmjReaderBase
|
|||
PropertyType.Color => new ColorProperty { Name = prop.Name, Value = Color.Parse(propElement.GetValueAs<string>(), CultureInfo.InvariantCulture) },
|
||||
PropertyType.File => new FileProperty { Name = prop.Name, Value = propElement.GetValueAs<string>() },
|
||||
PropertyType.Object => new ObjectProperty { Name = prop.Name, Value = propElement.GetValueAs<uint>() },
|
||||
PropertyType.Class => 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")
|
||||
};
|
||||
|
||||
|
@ -111,11 +177,11 @@ public abstract partial class TmjReaderBase
|
|||
return resultingProps;
|
||||
}
|
||||
|
||||
internal EnumProperty ReadEnumProperty(JsonElement element)
|
||||
internal IProperty ReadEnumProperty(JsonElement element)
|
||||
{
|
||||
var name = element.GetRequiredProperty<string>("name");
|
||||
var propertyType = element.GetRequiredProperty<string>("propertytype");
|
||||
var typeInXml = element.GetOptionalPropertyParseable<PropertyType>("type", (s) => s switch
|
||||
var typeInJson = element.GetOptionalPropertyParseable<PropertyType>("type", (s) => s switch
|
||||
{
|
||||
"string" => PropertyType.String,
|
||||
"int" => PropertyType.Int,
|
||||
|
@ -123,8 +189,21 @@ 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)
|
||||
{
|
||||
#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
|
||||
{
|
||||
PropertyType.String => new StringProperty { Name = name, Value = element.GetRequiredProperty<string>("value") },
|
||||
PropertyType.Int => new IntProperty { Name = name, Value = element.GetRequiredProperty<int>("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)
|
||||
throw new JsonException($"Custom type '{propertyType}' is not an enum.");
|
||||
|
||||
if (ced.StorageType == CustomEnumStorageType.String)
|
||||
{
|
||||
|
|
|
@ -13,7 +13,7 @@ public abstract partial class TmjReaderBase : IDisposable
|
|||
// External resolvers
|
||||
private readonly Func<string, Tileset> _externalTilesetResolver;
|
||||
private readonly Func<string, Template> _externalTemplateResolver;
|
||||
private readonly Func<string, ICustomTypeDefinition> _customTypeResolver;
|
||||
private readonly Func<string, Optional<ICustomTypeDefinition>> _customTypeResolver;
|
||||
|
||||
/// <summary>
|
||||
/// The root element of the JSON document being read.
|
||||
|
@ -34,7 +34,7 @@ public abstract partial class TmjReaderBase : IDisposable
|
|||
string jsonString,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver)
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver)
|
||||
{
|
||||
RootElement = JsonDocument.Parse(jsonString ?? throw new ArgumentNullException(nameof(jsonString))).RootElement;
|
||||
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
||||
|
|
|
@ -19,7 +19,7 @@ public class TsjTilesetReader : TmjReaderBase, ITilesetReader
|
|||
string jsonString,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver) : base(
|
||||
jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||
{ }
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ internal static class ExtensionsXmlReader
|
|||
return T.Parse(value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
internal static Optional<T> GetOptionalAttributeParseable<T>(this XmlReader reader, string attribute, Func<string, T> parser) where T : struct
|
||||
internal static Optional<T> GetOptionalAttributeParseable<T>(this XmlReader reader, string attribute, Func<string, T> parser)
|
||||
{
|
||||
var value = reader.GetAttribute(attribute);
|
||||
if (value is null)
|
||||
|
|
|
@ -16,7 +16,7 @@ public class TmxMapReader : TmxReaderBase, IMapReader
|
|||
XmlReader reader,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver) : base(
|
||||
reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||
{ }
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -143,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;
|
||||
}
|
||||
|
||||
|
@ -226,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
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
|
||||
|
@ -32,13 +34,18 @@ 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<int>("value") },
|
||||
PropertyType.Float => new FloatProperty { Name = name, Value = r.GetRequiredAttributeParseable<float>("value") },
|
||||
PropertyType.Bool => new BoolProperty { Name = name, Value = r.GetRequiredAttributeParseable<bool>("value") },
|
||||
PropertyType.Color => new ColorProperty { Name = name, Value = r.GetRequiredAttributeParseable<Color>("value") },
|
||||
PropertyType.Color => new ColorProperty { Name = name, Value = r.GetRequiredAttributeParseable<Color>("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<uint>("value") },
|
||||
PropertyType.Class => throw new XmlException("Class property must have a property type"),
|
||||
|
@ -49,6 +56,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";
|
||||
|
@ -66,28 +92,38 @@ 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()
|
||||
internal IProperty ReadEnumProperty()
|
||||
{
|
||||
var name = _reader.GetRequiredAttribute("name");
|
||||
var propertyType = _reader.GetRequiredAttribute("propertytype");
|
||||
|
@ -96,11 +132,26 @@ 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 (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)
|
||||
{
|
||||
#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
|
||||
{
|
||||
PropertyType.String => new StringProperty { Name = name, Value = _reader.GetRequiredAttribute("value") },
|
||||
PropertyType.Int => new IntProperty { Name = name, Value = _reader.GetRequiredAttributeParseable<int>("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)
|
||||
throw new XmlException($"Custom defined type {propertyType} is not an enum.");
|
||||
|
||||
if (ced.StorageType == CustomEnumStorageType.String)
|
||||
{
|
||||
|
@ -144,6 +195,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}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<TileRenderSize>("rendersize", s => s switch
|
||||
var renderSize = _reader.GetOptionalAttributeEnum<TileRenderSize>("tilerendersize", s => s switch
|
||||
{
|
||||
"tile" => TileRenderSize.Tile,
|
||||
"grid" => TileRenderSize.Grid,
|
||||
|
|
|
@ -11,7 +11,7 @@ public abstract partial class TmxReaderBase : IDisposable
|
|||
// External resolvers
|
||||
private readonly Func<string, Tileset> _externalTilesetResolver;
|
||||
private readonly Func<string, Template> _externalTemplateResolver;
|
||||
private readonly Func<string, ICustomTypeDefinition> _customTypeResolver;
|
||||
private readonly Func<string, Optional<ICustomTypeDefinition>> _customTypeResolver;
|
||||
|
||||
private readonly XmlReader _reader;
|
||||
private bool disposedValue;
|
||||
|
@ -28,7 +28,7 @@ public abstract partial class TmxReaderBase : IDisposable
|
|||
XmlReader reader,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver)
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver)
|
||||
{
|
||||
_reader = reader ?? throw new ArgumentNullException(nameof(reader));
|
||||
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
||||
|
|
|
@ -16,7 +16,7 @@ public class TsxTilesetReader : TmxReaderBase, ITilesetReader
|
|||
XmlReader reader,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver) : base(
|
||||
reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||
{ }
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ public class TxTemplateReader : TmxReaderBase, ITemplateReader
|
|||
XmlReader reader,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver) : base(
|
||||
reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||
{ }
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue