mirror of
https://github.com/dcronqvist/DotTiled.git
synced 2025-02-05 08:52:50 +02:00
Merge pull request #14 from dcronqvist/props-remodel
Remodel how properties work, and make readers a base class
This commit is contained in:
commit
30c0d22f93
77 changed files with 2114 additions and 1484 deletions
6
Makefile
6
Makefile
|
@ -13,10 +13,10 @@ lint:
|
||||||
dotnet format style --verify-no-changes src/DotTiled.sln
|
dotnet format style --verify-no-changes src/DotTiled.sln
|
||||||
dotnet format analyzers --verify-no-changes src/DotTiled.sln
|
dotnet format analyzers --verify-no-changes src/DotTiled.sln
|
||||||
|
|
||||||
BENCHMARK_SOURCES = DotTiled.Benchmark/Program.cs DotTiled.Benchmark/DotTiled.Benchmark.csproj
|
BENCHMARK_SOURCES = src/DotTiled.Benchmark/Program.cs src/DotTiled.Benchmark/DotTiled.Benchmark.csproj
|
||||||
BENCHMARK_OUTPUTDIR = DotTiled.Benchmark/BenchmarkDotNet.Artifacts
|
BENCHMARK_OUTPUTDIR = src/DotTiled.Benchmark/BenchmarkDotNet.Artifacts
|
||||||
.PHONY: benchmark
|
.PHONY: benchmark
|
||||||
benchmark: $(BENCHMARK_OUTPUTDIR)/results/MyBenchmarks.MapLoading-report-github.md
|
benchmark: $(BENCHMARK_OUTPUTDIR)/results/MyBenchmarks.MapLoading-report-github.md
|
||||||
|
|
||||||
$(BENCHMARK_OUTPUTDIR)/results/MyBenchmarks.MapLoading-report-github.md: $(BENCHMARK_SOURCES)
|
$(BENCHMARK_OUTPUTDIR)/results/MyBenchmarks.MapLoading-report-github.md: $(BENCHMARK_SOURCES)
|
||||||
dotnet run --project DotTiled.Benchmark/DotTiled.Benchmark.csproj -c Release -- $(BENCHMARK_OUTPUTDIR)
|
dotnet run --project src/DotTiled.Benchmark/DotTiled.Benchmark.csproj -c Release -- $(BENCHMARK_OUTPUTDIR)
|
161
docs/docs/custom-properties.md
Normal file
161
docs/docs/custom-properties.md
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
# Custom properties
|
||||||
|
|
||||||
|
[Tiled facilitates a very flexible way to store custom data in your maps using properties](https://doc.mapeditor.org/en/stable/manual/custom-properties/#custom-properties). Accessing these properties is a common task when working with Tiled maps in your game since it will allow you to fully utilize the strengths of Tiled, such as customizing the behavior of your game objects or setting up the initial state of your game world.
|
||||||
|
|
||||||
|
## All classes that can contain properties
|
||||||
|
|
||||||
|
All classes that can contain custom properties implement the interface <xref:DotTiled.Model.IHasProperties> in some way. Below is an exhaustive list of all classes that can contain custom properties:
|
||||||
|
|
||||||
|
- <xref:DotTiled.Model.BaseLayer>
|
||||||
|
- <xref:DotTiled.Model.TileLayer>
|
||||||
|
- <xref:DotTiled.Model.ObjectLayer>
|
||||||
|
- <xref:DotTiled.Model.ImageLayer>
|
||||||
|
- <xref:DotTiled.Model.Group>
|
||||||
|
- <xref:DotTiled.Model.ClassProperty> (allows for recursive property objects)
|
||||||
|
- <xref:DotTiled.Model.CustomClassDefinition> (used to define custom Tiled property types)
|
||||||
|
- <xref:DotTiled.Model.Object>
|
||||||
|
- <xref:DotTiled.Model.EllipseObject>
|
||||||
|
- <xref:DotTiled.Model.PointObject>
|
||||||
|
- <xref:DotTiled.Model.PolygonObject>
|
||||||
|
- <xref:DotTiled.Model.PolylineObject>
|
||||||
|
- <xref:DotTiled.Model.RectangleObject>
|
||||||
|
- <xref:DotTiled.Model.TextObject>
|
||||||
|
- <xref:DotTiled.Model.TileObject>
|
||||||
|
- <xref:DotTiled.Model.Tileset>
|
||||||
|
- <xref:DotTiled.Model.Tile>
|
||||||
|
- <xref:DotTiled.Model.WangTile>
|
||||||
|
- <xref:DotTiled.Model.WangColor>
|
||||||
|
|
||||||
|
## How to access properties
|
||||||
|
|
||||||
|
To access the properties on one of the classes listed above, you will make use of the <xref:DotTiled.Model.IHasProperties> interface.
|
||||||
|
|
||||||
|
In situations where you know that a property must exist, and you simply want to retrieve it, you can use the <xref:DotTiled.Model.IHasProperties.GetProperty``1(System.String)> method like so:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var map = LoadMap();
|
||||||
|
var propertyValue = map.GetProperty<BoolProperty>("boolPropertyInMap").Value;
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are unsure whether a property exists, or you want to provide some kind of default behaviour if the property is not present, you can instead use the <xref:DotTiled.Model.IHasProperties.TryGetProperty``1(System.String,``0@)> method like so:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var map = LoadMap();
|
||||||
|
if (map.TryGetProperty<BoolProperty>("boolPropertyInMap", out var property))
|
||||||
|
{
|
||||||
|
// Do something with existing property
|
||||||
|
var propertyValue = property.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Do something if property does not exist
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For both methods, you can replace `BoolProperty` with any of the property types that Tiled supports. You can find a list of all property types and their corresponding classes in the [next section](#all-types-of-properties).
|
||||||
|
|
||||||
|
## All types of properties
|
||||||
|
|
||||||
|
Tiled supports a variety of property types, which are represented in the DotTiled library as classes that implement the <xref:DotTiled.Model.IProperty`1> interface. Below is a list of all property types that Tiled supports and their corresponding classes in DotTiled:
|
||||||
|
|
||||||
|
- `bool` - <xref:DotTiled.Model.BoolProperty>
|
||||||
|
- `color` - <xref:DotTiled.Model.ColorProperty>
|
||||||
|
- `float` - <xref:DotTiled.Model.FloatProperty>
|
||||||
|
- `file` - <xref:DotTiled.Model.FileProperty>
|
||||||
|
- `int` - <xref:DotTiled.Model.IntProperty>
|
||||||
|
- `object` - <xref:DotTiled.Model.ObjectProperty>
|
||||||
|
- `string` - <xref:DotTiled.Model.StringProperty>
|
||||||
|
|
||||||
|
In addition to these primitive property types, [Tiled also supports more complex property types](https://doc.mapeditor.org/en/stable/manual/custom-properties/#custom-types). These custom property types are defined in Tiled according to the linked documentation, and to work with them in DotTiled, you *must* define their equivalences as a <xref:DotTiled.Model.ICustomTypeDefinition>. You must then provide a resolving function to a defined type given a custom type name, as it is defined in Tiled.
|
||||||
|
|
||||||
|
## Custom types
|
||||||
|
|
||||||
|
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# and then providing a custom type resolver function that will return the equivalent definition given a custom type name.
|
||||||
|
|
||||||
|
### Class properties
|
||||||
|
|
||||||
|
Whenever DotTiled encounters a property that is of type `class` in a Tiled file, it will use the supplied custom type resolver function to retrieve the custom type definition. It will then use that definition to know the default values of the properties of that class, and then override those defaults with the values found in the Tiled file when populating a <xref:DotTiled.Model.ClassProperty> instance. `class` properties allow you to create hierarchical structures of properties.
|
||||||
|
|
||||||
|
For example, if you have a `class` property in Tiled that looks like this:
|
||||||
|
|
||||||
|
![MonsterSpawner class in Tiled UI](../images/monster-spawner-class.png)
|
||||||
|
|
||||||
|
The equivalent definition in DotTiled would look like the following:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var monsterSpawnerDefinition = new CustomClassDefinition
|
||||||
|
{
|
||||||
|
Name = "MonsterSpawner",
|
||||||
|
UseAs = CustomClassUseAs.All, // Not really validated by DotTiled
|
||||||
|
Members = [ // Make sure that the default values match the Tiled UI
|
||||||
|
new BoolProperty { Name = "enabled", Value = true },
|
||||||
|
new IntProperty { Name = "maxSpawnAmount", Value = 10 },
|
||||||
|
new IntProperty { Name = "minSpawnAmount", Value = 0 },
|
||||||
|
new StringProperty { Name = "monsterNames", Value = "" }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Enum properties
|
||||||
|
|
||||||
|
Tiled also allows you to define custom property types that work as enums. Similarly to `class` properties, you must define the equivalent in DotTiled as a <xref:DotTiled.Model.CustomEnumDefinition>. You can then return the corresponding definition in the resolving function.
|
||||||
|
|
||||||
|
For example, if you have a custom property type in Tiled that looks like this:
|
||||||
|
|
||||||
|
![EntityType enum in Tiled UI](../images/entity-type-enum.png)
|
||||||
|
|
||||||
|
The equivalent definition in DotTiled would look like the following:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var entityTypeDefinition = new CustomEnumDefinition
|
||||||
|
{
|
||||||
|
Name = "EntityType",
|
||||||
|
StorageType = CustomEnumStorageType.String,
|
||||||
|
ValueAsFlags = false,
|
||||||
|
Values = [
|
||||||
|
"Bomb",
|
||||||
|
"Chest",
|
||||||
|
"Flower",
|
||||||
|
"Chair"
|
||||||
|
]
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### [Future] Automatically map custom property `class` types to C# classes
|
||||||
|
|
||||||
|
In the future, DotTiled will support automatically mapping custom property `class` types to C# classes. This will allow you to define a C# class that matches the structure of the `class` property in Tiled, and DotTiled will automatically map the properties of the `class` property to the properties of the C# class. This will make working with `class` properties much easier and more intuitive.
|
||||||
|
|
||||||
|
The idea is to expand on the <xref:DotTiled.Model.IHasProperties> interface with a method like `GetMappedProperty<T>(string propertyName)`, where `T` is a class that matches the structure of the `class` property in Tiled.
|
||||||
|
|
||||||
|
This functionality would be accompanied by a way to automatically create a matching <xref:DotTiled.Model.ICustomTypeDefinition> given a C# class or enum. Something like this would then be possible:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
class MonsterSpawner
|
||||||
|
{
|
||||||
|
public bool Enabled { get; set; } = true;
|
||||||
|
public int MaxSpawnAmount { get; set; } = 10;
|
||||||
|
public int MinSpawnAmount { get; set; } = 0;
|
||||||
|
public string MonsterNames { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EntityType
|
||||||
|
{
|
||||||
|
Bomb,
|
||||||
|
Chest,
|
||||||
|
Flower,
|
||||||
|
Chair
|
||||||
|
}
|
||||||
|
|
||||||
|
var monsterSpawnerDefinition = CustomClassDefinition.FromClass<MonsterSpawner>();
|
||||||
|
var entityTypeDefinition = CustomEnumDefinition.FromEnum<EntityType>();
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
var map = LoadMap();
|
||||||
|
var monsterSpawner = map.GetMappedProperty<MonsterSpawner>("monsterSpawnerPropertyInMap");
|
||||||
|
var entityType = map.GetMappedProperty<EntityType>("entityTypePropertyInMap");
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, it might be possible to also make some kind of exporting functionality for <xref:DotTiled.Model.ICustomTypeDefinition>. Given a collection of custom type definitions, DotTiled could generate a corresponding `propertytypes.json` file that you then can import into Tiled. This would make it so that you only have to define your custom property types once (in C#) and then import them into Tiled to use them in your maps.
|
||||||
|
|
||||||
|
Depending on implementation this might become something that can inhibit native AOT compilation due to potential reflection usage. Source generators could be used to mitigate this, but it is not yet clear how this will be implemented.
|
|
@ -4,3 +4,4 @@
|
||||||
|
|
||||||
- name: Essentials
|
- name: Essentials
|
||||||
- href: loading-a-map.md
|
- href: loading-a-map.md
|
||||||
|
- href: custom-properties.md
|
BIN
docs/images/entity-type-enum.png
Normal file
BIN
docs/images/entity-type-enum.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
docs/images/monster-spawner-class.png
Normal file
BIN
docs/images/monster-spawner-class.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
BIN
docs/images/resolve-types.png
Normal file
BIN
docs/images/resolve-types.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
|
@ -39,7 +39,7 @@ namespace DotTiled.Benchmark
|
||||||
{
|
{
|
||||||
using var stringReader = new StringReader(_tmxContents);
|
using var stringReader = new StringReader(_tmxContents);
|
||||||
using var xmlReader = XmlReader.Create(stringReader);
|
using var xmlReader = XmlReader.Create(stringReader);
|
||||||
using var mapReader = new DotTiled.Serialization.Tmx.TmxMapReader(xmlReader, _ => throw new NotSupportedException(), _ => throw new NotSupportedException(), []);
|
using var mapReader = new DotTiled.Serialization.Tmx.TmxMapReader(xmlReader, _ => throw new NotSupportedException(), _ => throw new NotSupportedException(), _ => throw new NotSupportedException());
|
||||||
return mapReader.ReadMap();
|
return mapReader.ReadMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ namespace DotTiled.Benchmark
|
||||||
[Benchmark(Baseline = true, Description = "DotTiled")]
|
[Benchmark(Baseline = true, Description = "DotTiled")]
|
||||||
public DotTiled.Model.Map LoadWithDotTiledFromInMemoryTmjString()
|
public DotTiled.Model.Map LoadWithDotTiledFromInMemoryTmjString()
|
||||||
{
|
{
|
||||||
using var mapReader = new DotTiled.Serialization.Tmj.TmjMapReader(_tmjContents, _ => throw new NotSupportedException(), _ => throw new NotSupportedException(), []);
|
using var mapReader = new DotTiled.Serialization.Tmj.TmjMapReader(_tmjContents, _ => throw new NotSupportedException(), _ => throw new NotSupportedException(), _ => throw new NotSupportedException());
|
||||||
return mapReader.ReadMap();
|
return mapReader.ReadMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,7 +91,7 @@ public static partial class DotTiledAssert
|
||||||
AssertEqual(expected.NextObjectID, actual.NextObjectID, nameof(Map.NextObjectID));
|
AssertEqual(expected.NextObjectID, actual.NextObjectID, nameof(Map.NextObjectID));
|
||||||
AssertEqual(expected.Infinite, actual.Infinite, nameof(Map.Infinite));
|
AssertEqual(expected.Infinite, actual.Infinite, nameof(Map.Infinite));
|
||||||
|
|
||||||
AssertProperties(actual.Properties, expected.Properties);
|
AssertProperties(expected.Properties, actual.Properties);
|
||||||
|
|
||||||
Assert.NotNull(actual.Tilesets);
|
Assert.NotNull(actual.Tilesets);
|
||||||
AssertEqual(expected.Tilesets.Count, actual.Tilesets.Count, "Tilesets.Count");
|
AssertEqual(expected.Tilesets.Count, actual.Tilesets.Count, "Tilesets.Count");
|
||||||
|
|
|
@ -4,7 +4,7 @@ namespace DotTiled.Tests;
|
||||||
|
|
||||||
public static partial class DotTiledAssert
|
public static partial class DotTiledAssert
|
||||||
{
|
{
|
||||||
internal static void AssertProperties(Dictionary<string, IProperty>? expected, Dictionary<string, IProperty>? actual)
|
internal static void AssertProperties(IList<IProperty>? expected, IList<IProperty>? actual)
|
||||||
{
|
{
|
||||||
if (expected is null)
|
if (expected is null)
|
||||||
{
|
{
|
||||||
|
@ -14,18 +14,16 @@ public static partial class DotTiledAssert
|
||||||
|
|
||||||
Assert.NotNull(actual);
|
Assert.NotNull(actual);
|
||||||
AssertEqual(expected.Count, actual.Count, "Properties.Count");
|
AssertEqual(expected.Count, actual.Count, "Properties.Count");
|
||||||
foreach (var kvp in expected)
|
foreach (var prop in expected)
|
||||||
{
|
{
|
||||||
Assert.Contains(kvp.Key, actual.Keys);
|
Assert.Contains(actual, p => p.Name == prop.Name);
|
||||||
AssertProperty((dynamic)kvp.Value, (dynamic)actual[kvp.Key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void AssertProperty(IProperty expected, IProperty actual)
|
var actualProp = actual.First(p => p.Name == prop.Name);
|
||||||
{
|
AssertEqual(prop.Type, actualProp.Type, "Property.Type");
|
||||||
AssertEqual(expected.Type, actual.Type, "Property.Type");
|
AssertEqual(prop.Name, actualProp.Name, "Property.Name");
|
||||||
AssertEqual(expected.Name, actual.Name, "Property.Name");
|
|
||||||
AssertProperties((dynamic)actual, (dynamic)expected);
|
AssertProperty((dynamic)prop, (dynamic)actualProp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AssertProperty(StringProperty expected, StringProperty actual) => AssertEqual(expected.Value, actual.Value, "StringProperty.Value");
|
private static void AssertProperty(StringProperty expected, StringProperty actual) => AssertEqual(expected.Value, actual.Value, "StringProperty.Value");
|
||||||
|
@ -45,6 +43,16 @@ public static partial class DotTiledAssert
|
||||||
private static void AssertProperty(ClassProperty expected, ClassProperty actual)
|
private static void AssertProperty(ClassProperty expected, ClassProperty actual)
|
||||||
{
|
{
|
||||||
AssertEqual(expected.PropertyType, actual.PropertyType, "ClassProperty.PropertyType");
|
AssertEqual(expected.PropertyType, actual.PropertyType, "ClassProperty.PropertyType");
|
||||||
AssertProperties(expected.Properties, actual.Properties);
|
AssertProperties(expected.Value, actual.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertProperty(EnumProperty expected, EnumProperty actual)
|
||||||
|
{
|
||||||
|
AssertEqual(expected.PropertyType, actual.PropertyType, "EnumProperty.PropertyType");
|
||||||
|
AssertEqual(expected.Value.Count, actual.Value.Count, "EnumProperty.Value.Count");
|
||||||
|
foreach (var value in expected.Value)
|
||||||
|
{
|
||||||
|
Assert.Contains(actual.Value, v => v == value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,9 +141,9 @@ public static partial class DotTiledAssert
|
||||||
AssertEqual(expected.Height, actual.Height, nameof(Tile.Height));
|
AssertEqual(expected.Height, actual.Height, nameof(Tile.Height));
|
||||||
|
|
||||||
// Elements
|
// Elements
|
||||||
AssertProperties(actual.Properties, expected.Properties);
|
AssertProperties(expected.Properties, actual.Properties);
|
||||||
AssertImage(actual.Image, expected.Image);
|
AssertImage(expected.Image, actual.Image);
|
||||||
AssertLayer((BaseLayer?)actual.ObjectLayer, (BaseLayer?)expected.ObjectLayer);
|
AssertLayer((BaseLayer?)expected.ObjectLayer, (BaseLayer?)actual.ObjectLayer);
|
||||||
if (expected.Animation is not null)
|
if (expected.Animation is not null)
|
||||||
{
|
{
|
||||||
Assert.NotNull(actual.Animation);
|
Assert.NotNull(actual.Animation);
|
||||||
|
|
|
@ -32,14 +32,15 @@ public static partial class TestData
|
||||||
|
|
||||||
public static IEnumerable<object[]> MapTests =>
|
public static IEnumerable<object[]> MapTests =>
|
||||||
[
|
[
|
||||||
["Serialization/TestData/Map/default_map/default-map", (string f) => DefaultMap(), Array.Empty<CustomTypeDefinition>()],
|
["Serialization/TestData/Map/default_map/default-map", (string f) => DefaultMap(), Array.Empty<ICustomTypeDefinition>()],
|
||||||
["Serialization/TestData/Map/map_with_common_props/map-with-common-props", (string f) => MapWithCommonProps(), Array.Empty<CustomTypeDefinition>()],
|
["Serialization/TestData/Map/map_with_common_props/map-with-common-props", (string f) => MapWithCommonProps(), Array.Empty<ICustomTypeDefinition>()],
|
||||||
["Serialization/TestData/Map/map_with_custom_type_props/map-with-custom-type-props", (string f) => MapWithCustomTypeProps(), MapWithCustomTypePropsCustomTypeDefinitions()],
|
["Serialization/TestData/Map/map_with_custom_type_props/map-with-custom-type-props", (string f) => MapWithCustomTypeProps(), MapWithCustomTypePropsCustomTypeDefinitions()],
|
||||||
["Serialization/TestData/Map/map_with_embedded_tileset/map-with-embedded-tileset", (string f) => MapWithEmbeddedTileset(), Array.Empty<CustomTypeDefinition>()],
|
["Serialization/TestData/Map/map_with_embedded_tileset/map-with-embedded-tileset", (string f) => MapWithEmbeddedTileset(), Array.Empty<ICustomTypeDefinition>()],
|
||||||
["Serialization/TestData/Map/map_with_external_tileset/map-with-external-tileset", (string f) => MapWithExternalTileset(f), Array.Empty<CustomTypeDefinition>()],
|
["Serialization/TestData/Map/map_with_external_tileset/map-with-external-tileset", (string f) => MapWithExternalTileset(f), Array.Empty<ICustomTypeDefinition>()],
|
||||||
["Serialization/TestData/Map/map_with_flippingflags/map-with-flippingflags", (string f) => MapWithFlippingFlags(f), Array.Empty<CustomTypeDefinition>()],
|
["Serialization/TestData/Map/map_with_flippingflags/map-with-flippingflags", (string f) => MapWithFlippingFlags(f), Array.Empty<ICustomTypeDefinition>()],
|
||||||
["Serialization/TestData/Map/map_external_tileset_multi/map-external-tileset-multi", (string f) => MapExternalTilesetMulti(f), Array.Empty<CustomTypeDefinition>()],
|
["Serialization/TestData/Map/map_external_tileset_multi/map-external-tileset-multi", (string f) => MapExternalTilesetMulti(f), Array.Empty<ICustomTypeDefinition>()],
|
||||||
["Serialization/TestData/Map/map_external_tileset_wangset/map-external-tileset-wangset", (string f) => MapExternalTilesetWangset(f), Array.Empty<CustomTypeDefinition>()],
|
["Serialization/TestData/Map/map_external_tileset_wangset/map-external-tileset-wangset", (string f) => MapExternalTilesetWangset(f), Array.Empty<ICustomTypeDefinition>()],
|
||||||
["Serialization/TestData/Map/map_with_many_layers/map-with-many-layers", (string f) => MapWithManyLayers(f), Array.Empty<CustomTypeDefinition>()],
|
["Serialization/TestData/Map/map_with_many_layers/map-with-many-layers", (string f) => MapWithManyLayers(f), Array.Empty<ICustomTypeDefinition>()],
|
||||||
|
["Serialization/TestData/Map/map_with_deep_props/map-with-deep-props", (string f) => MapWithDeepProps(), MapWithDeepPropsCustomTypeDefinitions()],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,16 +49,15 @@ public partial class TestData
|
||||||
Width = 1,
|
Width = 1,
|
||||||
Height = 1
|
Height = 1
|
||||||
},
|
},
|
||||||
Properties = new Dictionary<string, IProperty>
|
Properties = [
|
||||||
{
|
new BoolProperty { Name = "tilesetbool", Value = true },
|
||||||
["tilesetbool"] = new BoolProperty { Name = "tilesetbool", Value = true },
|
new ColorProperty { Name = "tilesetcolor", Value = Color.Parse("#ffff0000", CultureInfo.InvariantCulture) },
|
||||||
["tilesetcolor"] = new ColorProperty { Name = "tilesetcolor", Value = Color.Parse("#ffff0000", CultureInfo.InvariantCulture) },
|
new FileProperty { Name = "tilesetfile", Value = "" },
|
||||||
["tilesetfile"] = new FileProperty { Name = "tilesetfile", Value = "" },
|
new FloatProperty { Name = "tilesetfloat", Value = 5.2f },
|
||||||
["tilesetfloat"] = new FloatProperty { Name = "tilesetfloat", Value = 5.2f },
|
new IntProperty { Name = "tilesetint", Value = 9 },
|
||||||
["tilesetint"] = new IntProperty { Name = "tilesetint", Value = 9 },
|
new ObjectProperty { Name = "tilesetobject", Value = 0 },
|
||||||
["tilesetobject"] = new ObjectProperty { Name = "tilesetobject", Value = 0 },
|
new StringProperty { Name = "tilesetstring", Value = "hello world!" }
|
||||||
["tilesetstring"] = new StringProperty { Name = "tilesetstring", Value = "hello world!" }
|
],
|
||||||
},
|
|
||||||
Tiles = [
|
Tiles = [
|
||||||
new Tile
|
new Tile
|
||||||
{
|
{
|
||||||
|
|
|
@ -55,15 +55,15 @@ public partial class TestData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
Properties = new Dictionary<string, IProperty>
|
Properties =
|
||||||
{
|
[
|
||||||
["boolprop"] = new BoolProperty { Name = "boolprop", Value = true },
|
new BoolProperty { Name = "boolprop", Value = true },
|
||||||
["colorprop"] = new ColorProperty { Name = "colorprop", Value = Color.Parse("#ff55ffff", CultureInfo.InvariantCulture) },
|
new ColorProperty { Name = "colorprop", Value = Color.Parse("#ff55ffff", CultureInfo.InvariantCulture) },
|
||||||
["fileprop"] = new FileProperty { Name = "fileprop", Value = "file.txt" },
|
new FileProperty { Name = "fileprop", Value = "file.txt" },
|
||||||
["floatprop"] = new FloatProperty { Name = "floatprop", Value = 4.2f },
|
new FloatProperty { Name = "floatprop", Value = 4.2f },
|
||||||
["intprop"] = new IntProperty { Name = "intprop", Value = 8 },
|
new IntProperty { Name = "intprop", Value = 8 },
|
||||||
["objectprop"] = new ObjectProperty { Name = "objectprop", Value = 5 },
|
new ObjectProperty { Name = "objectprop", Value = 5 },
|
||||||
["stringprop"] = new StringProperty { Name = "stringprop", Value = "This is a string, hello world!" }
|
new StringProperty { Name = "stringprop", Value = "This is a string, hello world!" }
|
||||||
}
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,28 +55,50 @@ public partial class TestData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
Properties = new Dictionary<string, IProperty>
|
Properties = [
|
||||||
{
|
new ClassProperty
|
||||||
["customclassprop"] = new ClassProperty
|
|
||||||
{
|
{
|
||||||
Name = "customclassprop",
|
Name = "customclassprop",
|
||||||
PropertyType = "CustomClass",
|
PropertyType = "CustomClass",
|
||||||
Properties = new Dictionary<string, IProperty>
|
Value = [
|
||||||
|
new BoolProperty { Name = "boolinclass", Value = true },
|
||||||
|
new ColorProperty { Name = "colorinclass", Value = Color.Parse("#000000ff", CultureInfo.InvariantCulture) },
|
||||||
|
new FileProperty { Name = "fileinclass", Value = "" },
|
||||||
|
new FloatProperty { Name = "floatinclass", Value = 13.37f },
|
||||||
|
new IntProperty { Name = "intinclass", Value = 0 },
|
||||||
|
new ObjectProperty { Name = "objectinclass", Value = 0 },
|
||||||
|
new StringProperty { Name = "stringinclass", Value = "This is a set string" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
new EnumProperty
|
||||||
{
|
{
|
||||||
["boolinclass"] = new BoolProperty { Name = "boolinclass", Value = true },
|
Name = "customenumstringprop",
|
||||||
["colorinclass"] = new ColorProperty { Name = "colorinclass", Value = Color.Parse("#000000ff", CultureInfo.InvariantCulture) },
|
PropertyType = "CustomEnumString",
|
||||||
["fileinclass"] = new FileProperty { Name = "fileinclass", Value = "" },
|
Value = new HashSet<string> { "CustomEnumString_2" }
|
||||||
["floatinclass"] = new FloatProperty { Name = "floatinclass", Value = 13.37f },
|
},
|
||||||
["intinclass"] = new IntProperty { Name = "intinclass", Value = 0 },
|
new EnumProperty
|
||||||
["objectinclass"] = new ObjectProperty { Name = "objectinclass", Value = 0 },
|
{
|
||||||
["stringinclass"] = new StringProperty { Name = "stringinclass", Value = "This is a set string" }
|
Name = "customenumstringflagsprop",
|
||||||
}
|
PropertyType = "CustomEnumStringFlags",
|
||||||
}
|
Value = new HashSet<string> { "CustomEnumStringFlags_1", "CustomEnumStringFlags_2" }
|
||||||
|
},
|
||||||
|
new EnumProperty
|
||||||
|
{
|
||||||
|
Name = "customenumintprop",
|
||||||
|
PropertyType = "CustomEnumInt",
|
||||||
|
Value = new HashSet<string> { "CustomEnumInt_4" }
|
||||||
|
},
|
||||||
|
new EnumProperty
|
||||||
|
{
|
||||||
|
Name = "customenumintflagsprop",
|
||||||
|
PropertyType = "CustomEnumIntFlags",
|
||||||
|
Value = new HashSet<string> { "CustomEnumIntFlags_2", "CustomEnumIntFlags_3" }
|
||||||
}
|
}
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
// This comes from map-with-custom-type-props/propertytypes.json
|
// This comes from map-with-custom-type-props/propertytypes.json
|
||||||
public static IReadOnlyCollection<CustomTypeDefinition> MapWithCustomTypePropsCustomTypeDefinitions() => [
|
public static IReadOnlyCollection<ICustomTypeDefinition> MapWithCustomTypePropsCustomTypeDefinitions() => [
|
||||||
new CustomClassDefinition
|
new CustomClassDefinition
|
||||||
{
|
{
|
||||||
Name = "CustomClass",
|
Name = "CustomClass",
|
||||||
|
@ -118,6 +140,50 @@ public partial class TestData
|
||||||
Value = ""
|
Value = ""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
new CustomEnumDefinition
|
||||||
|
{
|
||||||
|
Name = "CustomEnumString",
|
||||||
|
StorageType = CustomEnumStorageType.String,
|
||||||
|
ValueAsFlags = false,
|
||||||
|
Values = [
|
||||||
|
"CustomEnumString_1",
|
||||||
|
"CustomEnumString_2",
|
||||||
|
"CustomEnumString_3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
new CustomEnumDefinition
|
||||||
|
{
|
||||||
|
Name = "CustomEnumStringFlags",
|
||||||
|
StorageType = CustomEnumStorageType.String,
|
||||||
|
ValueAsFlags = true,
|
||||||
|
Values = [
|
||||||
|
"CustomEnumStringFlags_1",
|
||||||
|
"CustomEnumStringFlags_2"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
new CustomEnumDefinition
|
||||||
|
{
|
||||||
|
Name = "CustomEnumInt",
|
||||||
|
StorageType = CustomEnumStorageType.Int,
|
||||||
|
ValueAsFlags = false,
|
||||||
|
Values = [
|
||||||
|
"CustomEnumInt_1",
|
||||||
|
"CustomEnumInt_2",
|
||||||
|
"CustomEnumInt_3",
|
||||||
|
"CustomEnumInt_4",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
new CustomEnumDefinition
|
||||||
|
{
|
||||||
|
Name = "CustomEnumIntFlags",
|
||||||
|
StorageType = CustomEnumStorageType.Int,
|
||||||
|
ValueAsFlags = true,
|
||||||
|
Values = [
|
||||||
|
"CustomEnumIntFlags_1",
|
||||||
|
"CustomEnumIntFlags_2",
|
||||||
|
"CustomEnumIntFlags_3"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,30 @@
|
||||||
"floatinclass":13.37,
|
"floatinclass":13.37,
|
||||||
"stringinclass":"This is a set string"
|
"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",
|
"renderorder":"right-down",
|
||||||
"tiledversion":"1.11.0",
|
"tiledversion":"1.11.0",
|
||||||
|
|
|
@ -8,6 +8,10 @@
|
||||||
<property name="stringinclass" value="This is a set string"/>
|
<property name="stringinclass" value="This is a set string"/>
|
||||||
</properties>
|
</properties>
|
||||||
</property>
|
</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>
|
</properties>
|
||||||
<layer id="1" name="Tile Layer 1" width="5" height="5">
|
<layer id="1" name="Tile Layer 1" width="5" height="5">
|
||||||
<data encoding="csv">
|
<data encoding="csv">
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
using System.Globalization;
|
||||||
|
using DotTiled.Model;
|
||||||
|
|
||||||
|
namespace DotTiled.Tests;
|
||||||
|
|
||||||
|
public partial class TestData
|
||||||
|
{
|
||||||
|
public static Map MapWithDeepProps() => new Map
|
||||||
|
{
|
||||||
|
Class = "",
|
||||||
|
Orientation = MapOrientation.Orthogonal,
|
||||||
|
Width = 5,
|
||||||
|
Height = 5,
|
||||||
|
TileWidth = 32,
|
||||||
|
TileHeight = 32,
|
||||||
|
Infinite = false,
|
||||||
|
HexSideLength = null,
|
||||||
|
StaggerAxis = null,
|
||||||
|
StaggerIndex = null,
|
||||||
|
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,
|
||||||
|
Chunks = null,
|
||||||
|
Compression = null,
|
||||||
|
GlobalTileIDs = [
|
||||||
|
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 = [
|
||||||
|
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 = "customouterclassprop",
|
||||||
|
PropertyType = "CustomOuterClass",
|
||||||
|
Value = [
|
||||||
|
new ClassProperty
|
||||||
|
{
|
||||||
|
Name = "customclasspropinclass",
|
||||||
|
PropertyType = "CustomClass",
|
||||||
|
Value = [
|
||||||
|
new BoolProperty { Name = "boolinclass", Value = false },
|
||||||
|
new ColorProperty { Name = "colorinclass", Value = Color.Parse("#000000ff", CultureInfo.InvariantCulture) },
|
||||||
|
new FileProperty { Name = "fileinclass", Value = "" },
|
||||||
|
new FloatProperty { Name = "floatinclass", Value = 0f },
|
||||||
|
new IntProperty { Name = "intinclass", Value = 0 },
|
||||||
|
new ObjectProperty { Name = "objectinclass", Value = 0 },
|
||||||
|
new StringProperty { Name = "stringinclass", Value = "" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
new ClassProperty
|
||||||
|
{
|
||||||
|
Name = "customouterclasspropset",
|
||||||
|
PropertyType = "CustomOuterClass",
|
||||||
|
Value = [
|
||||||
|
new ClassProperty
|
||||||
|
{
|
||||||
|
Name = "customclasspropinclass",
|
||||||
|
PropertyType = "CustomClass",
|
||||||
|
Value = [
|
||||||
|
new BoolProperty { Name = "boolinclass", Value = true },
|
||||||
|
new ColorProperty { Name = "colorinclass", Value = Color.Parse("#000000ff", CultureInfo.InvariantCulture) },
|
||||||
|
new FileProperty { Name = "fileinclass", Value = "" },
|
||||||
|
new FloatProperty { Name = "floatinclass", Value = 13.37f },
|
||||||
|
new IntProperty { Name = "intinclass", Value = 0 },
|
||||||
|
new ObjectProperty { Name = "objectinclass", Value = 0 },
|
||||||
|
new StringProperty { Name = "stringinclass", Value = "" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
public static IReadOnlyCollection<ICustomTypeDefinition> MapWithDeepPropsCustomTypeDefinitions() => [
|
||||||
|
new CustomClassDefinition
|
||||||
|
{
|
||||||
|
Name = "CustomClass",
|
||||||
|
UseAs = CustomClassUseAs.Property,
|
||||||
|
Members = [
|
||||||
|
new BoolProperty
|
||||||
|
{
|
||||||
|
Name = "boolinclass",
|
||||||
|
Value = false
|
||||||
|
},
|
||||||
|
new ColorProperty
|
||||||
|
{
|
||||||
|
Name = "colorinclass",
|
||||||
|
Value = Color.Parse("#000000ff", CultureInfo.InvariantCulture)
|
||||||
|
},
|
||||||
|
new FileProperty
|
||||||
|
{
|
||||||
|
Name = "fileinclass",
|
||||||
|
Value = ""
|
||||||
|
},
|
||||||
|
new FloatProperty
|
||||||
|
{
|
||||||
|
Name = "floatinclass",
|
||||||
|
Value = 0f
|
||||||
|
},
|
||||||
|
new IntProperty
|
||||||
|
{
|
||||||
|
Name = "intinclass",
|
||||||
|
Value = 0
|
||||||
|
},
|
||||||
|
new ObjectProperty
|
||||||
|
{
|
||||||
|
Name = "objectinclass",
|
||||||
|
Value = 0
|
||||||
|
},
|
||||||
|
new StringProperty
|
||||||
|
{
|
||||||
|
Name = "stringinclass",
|
||||||
|
Value = ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
new CustomClassDefinition
|
||||||
|
{
|
||||||
|
Name = "CustomOuterClass",
|
||||||
|
UseAs = CustomClassUseAs.Property,
|
||||||
|
Members = [
|
||||||
|
new ClassProperty
|
||||||
|
{
|
||||||
|
Name = "customclasspropinclass",
|
||||||
|
PropertyType = "CustomClass",
|
||||||
|
Value = [] // So no overrides of defaults in CustomClass
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
{ "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":"customouterclassprop",
|
||||||
|
"propertytype":"CustomOuterClass",
|
||||||
|
"type":"class",
|
||||||
|
"value":
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"customouterclasspropset",
|
||||||
|
"propertytype":"CustomOuterClass",
|
||||||
|
"type":"class",
|
||||||
|
"value":
|
||||||
|
{
|
||||||
|
"customclasspropinclass":
|
||||||
|
{
|
||||||
|
"boolinclass":true,
|
||||||
|
"floatinclass":13.37
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
"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="customouterclassprop" type="class" propertytype="CustomOuterClass"/>
|
||||||
|
<property name="customouterclasspropset" type="class" propertytype="CustomOuterClass">
|
||||||
|
<properties>
|
||||||
|
<property name="customclasspropinclass" type="class" propertytype="CustomClass">
|
||||||
|
<properties>
|
||||||
|
<property name="boolinclass" type="bool" value="true"/>
|
||||||
|
<property name="floatinclass" type="float" value="13.37"/>
|
||||||
|
</properties>
|
||||||
|
</property>
|
||||||
|
</properties>
|
||||||
|
</property>
|
||||||
|
</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>
|
|
@ -95,10 +95,9 @@ public partial class TestData
|
||||||
new Vector2(35.6667f, 32.3333f)
|
new Vector2(35.6667f, 32.3333f)
|
||||||
],
|
],
|
||||||
Template = fileExt == "tmx" ? "poly.tx" : "poly.tj",
|
Template = fileExt == "tmx" ? "poly.tx" : "poly.tj",
|
||||||
Properties = new Dictionary<string, IProperty>
|
Properties = [
|
||||||
{
|
new StringProperty { Name = "templateprop", Value = "helo there" }
|
||||||
["templateprop"] = new StringProperty { Name = "templateprop", Value = "helo there" }
|
]
|
||||||
}
|
|
||||||
},
|
},
|
||||||
new TileObject
|
new TileObject
|
||||||
{
|
{
|
||||||
|
|
|
@ -11,7 +11,7 @@ public partial class TmjMapReaderTests
|
||||||
public void TmxMapReaderReadMap_ValidTmjExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(
|
public void TmxMapReaderReadMap_ValidTmjExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(
|
||||||
string testDataFile,
|
string testDataFile,
|
||||||
Func<string, Map> expectedMap,
|
Func<string, Map> expectedMap,
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
testDataFile += ".tmj";
|
testDataFile += ".tmj";
|
||||||
|
@ -20,16 +20,20 @@ public partial class TmjMapReaderTests
|
||||||
Template ResolveTemplate(string source)
|
Template ResolveTemplate(string source)
|
||||||
{
|
{
|
||||||
var templateJson = TestData.GetRawStringFor($"{fileDir}/{source}");
|
var templateJson = TestData.GetRawStringFor($"{fileDir}/{source}");
|
||||||
using var templateReader = new TjTemplateReader(templateJson, ResolveTileset, ResolveTemplate, customTypeDefinitions);
|
using var templateReader = new TjTemplateReader(templateJson, ResolveTileset, ResolveTemplate, ResolveCustomType);
|
||||||
return templateReader.ReadTemplate();
|
return templateReader.ReadTemplate();
|
||||||
}
|
}
|
||||||
Tileset ResolveTileset(string source)
|
Tileset ResolveTileset(string source)
|
||||||
{
|
{
|
||||||
var tilesetJson = TestData.GetRawStringFor($"{fileDir}/{source}");
|
var tilesetJson = TestData.GetRawStringFor($"{fileDir}/{source}");
|
||||||
using var tilesetReader = new TsjTilesetReader(tilesetJson, ResolveTemplate, customTypeDefinitions);
|
using var tilesetReader = new TsjTilesetReader(tilesetJson, ResolveTileset, ResolveTemplate, ResolveCustomType);
|
||||||
return tilesetReader.ReadTileset();
|
return tilesetReader.ReadTileset();
|
||||||
}
|
}
|
||||||
using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate, customTypeDefinitions);
|
ICustomTypeDefinition ResolveCustomType(string name)
|
||||||
|
{
|
||||||
|
return customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name)!;
|
||||||
|
}
|
||||||
|
using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate, ResolveCustomType);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var map = mapReader.ReadMap();
|
var map = mapReader.ReadMap();
|
||||||
|
|
|
@ -11,7 +11,7 @@ public partial class TmxMapReaderTests
|
||||||
public void TmxMapReaderReadMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(
|
public void TmxMapReaderReadMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(
|
||||||
string testDataFile,
|
string testDataFile,
|
||||||
Func<string, Map> expectedMap,
|
Func<string, Map> expectedMap,
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
testDataFile += ".tmx";
|
testDataFile += ".tmx";
|
||||||
|
@ -20,16 +20,20 @@ public partial class TmxMapReaderTests
|
||||||
Template ResolveTemplate(string source)
|
Template ResolveTemplate(string source)
|
||||||
{
|
{
|
||||||
using var xmlTemplateReader = TestData.GetXmlReaderFor($"{fileDir}/{source}");
|
using var xmlTemplateReader = TestData.GetXmlReaderFor($"{fileDir}/{source}");
|
||||||
using var templateReader = new TxTemplateReader(xmlTemplateReader, ResolveTileset, ResolveTemplate, customTypeDefinitions);
|
using var templateReader = new TxTemplateReader(xmlTemplateReader, ResolveTileset, ResolveTemplate, ResolveCustomType);
|
||||||
return templateReader.ReadTemplate();
|
return templateReader.ReadTemplate();
|
||||||
}
|
}
|
||||||
Tileset ResolveTileset(string source)
|
Tileset ResolveTileset(string source)
|
||||||
{
|
{
|
||||||
using var xmlTilesetReader = TestData.GetXmlReaderFor($"{fileDir}/{source}");
|
using var xmlTilesetReader = TestData.GetXmlReaderFor($"{fileDir}/{source}");
|
||||||
using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTemplate, customTypeDefinitions);
|
using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTileset, ResolveTemplate, ResolveCustomType);
|
||||||
return tilesetReader.ReadTileset();
|
return tilesetReader.ReadTileset();
|
||||||
}
|
}
|
||||||
using var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate, customTypeDefinitions);
|
ICustomTypeDefinition ResolveCustomType(string name)
|
||||||
|
{
|
||||||
|
return customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name)!;
|
||||||
|
}
|
||||||
|
using var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate, ResolveCustomType);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var map = mapReader.ReadMap();
|
var map = mapReader.ReadMap();
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace DotTiled.Model;
|
||||||
/// To check the type of a layer, <see href="https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/pattern-matching">use C# pattern matching</see>,
|
/// To check the type of a layer, <see href="https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/pattern-matching">use C# pattern matching</see>,
|
||||||
/// or some other mechanism to determine the type of the layer at runtime.
|
/// or some other mechanism to determine the type of the layer at runtime.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class BaseLayer
|
public abstract class BaseLayer : HasPropertiesBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unique ID of the layer. Each layer that is added to a map gets a unique ID. Even if a layer is deleted, no layer ever gets the same ID.
|
/// Unique ID of the layer. Each layer that is added to a map gets a unique ID. Even if a layer is deleted, no layer ever gets the same ID.
|
||||||
|
@ -62,5 +62,8 @@ public abstract class BaseLayer
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Layer properties.
|
/// Layer properties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, IProperty>? Properties { get; set; }
|
public List<IProperty> Properties { get; set; } = [];
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override IList<IProperty> GetProperties() => Properties;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ namespace DotTiled.Model;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class for objects in object layers.
|
/// Base class for objects in object layers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class Object
|
public abstract class Object : HasPropertiesBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unique ID of the objects. Each object that is placed on a map gets a unique ID. Even if an object was deleted, no object gets the same ID.
|
/// Unique ID of the objects. Each object that is placed on a map gets a unique ID. Even if an object was deleted, no object gets the same ID.
|
||||||
|
@ -60,5 +60,8 @@ public abstract class Object
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Object properties.
|
/// Object properties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, IProperty>? Properties { get; set; }
|
public List<IProperty> Properties { get; set; } = [];
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override IList<IProperty> GetProperties() => Properties;
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,7 +90,7 @@ public enum StaggerIndex
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a Tiled map.
|
/// Represents a Tiled map.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Map
|
public class Map : HasPropertiesBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The TMX format version. Is incremented to match minor Tiled releases.
|
/// The TMX format version. Is incremented to match minor Tiled releases.
|
||||||
|
@ -191,7 +191,10 @@ public class Map
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Map properties.
|
/// Map properties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, IProperty>? Properties { get; set; }
|
public List<IProperty> Properties { get; set; } = [];
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override IList<IProperty> GetProperties() => Properties;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of tilesets used by the map.
|
/// List of tilesets used by the map.
|
||||||
|
|
|
@ -3,7 +3,7 @@ namespace DotTiled.Model;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a boolean property.
|
/// Represents a boolean property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class BoolProperty : IProperty
|
public class BoolProperty : IProperty<bool>
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public required string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace DotTiled.Model;
|
namespace DotTiled.Model;
|
||||||
|
@ -6,7 +8,7 @@ namespace DotTiled.Model;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a class property.
|
/// Represents a class property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ClassProperty : IProperty
|
public class ClassProperty : IHasProperties, IProperty<IList<IProperty>>
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public required string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
|
@ -23,13 +25,41 @@ public class ClassProperty : IProperty
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The properties of the class property.
|
/// The properties of the class property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public required Dictionary<string, IProperty> Properties { get; set; }
|
public required IList<IProperty> Value { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IProperty Clone() => new ClassProperty
|
public IProperty Clone() => new ClassProperty
|
||||||
{
|
{
|
||||||
Name = Name,
|
Name = Name,
|
||||||
PropertyType = PropertyType,
|
PropertyType = PropertyType,
|
||||||
Properties = Properties.ToDictionary(p => p.Key, p => p.Value.Clone())
|
Value = Value.Select(property => property.Clone()).ToList()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IList<IProperty> GetProperties() => Value;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public T GetProperty<T>(string name) where T : IProperty
|
||||||
|
{
|
||||||
|
var property = Value.FirstOrDefault(_properties => _properties.Name == name) ?? throw new InvalidOperationException($"Property '{name}' not found.");
|
||||||
|
if (property is T prop)
|
||||||
|
{
|
||||||
|
return prop;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException($"Property '{name}' is not of type '{typeof(T).Name}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGetProperty<T>(string name, [NotNullWhen(true)] out T? property) where T : IProperty
|
||||||
|
{
|
||||||
|
if (Value.FirstOrDefault(_properties => _properties.Name == name) is T prop)
|
||||||
|
{
|
||||||
|
property = prop;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
property = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ namespace DotTiled.Model;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a color property.
|
/// Represents a color property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ColorProperty : IProperty
|
public class ColorProperty : IProperty<Color>
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public required string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
|
|
|
@ -65,8 +65,14 @@ public enum CustomClassUseAs
|
||||||
/// Represents a custom class definition in Tiled. Refer to the
|
/// Represents a custom class definition in Tiled. Refer to the
|
||||||
/// <see href="https://doc.mapeditor.org/en/stable/manual/custom-properties/#custom-types">documentation of custom types to understand how they work</see>.
|
/// <see href="https://doc.mapeditor.org/en/stable/manual/custom-properties/#custom-types">documentation of custom types to understand how they work</see>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CustomClassDefinition : CustomTypeDefinition
|
public class CustomClassDefinition : HasPropertiesBase, ICustomTypeDefinition
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public uint ID { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public required string Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The color of the custom class inside the Tiled editor.
|
/// The color of the custom class inside the Tiled editor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -86,4 +92,7 @@ public class CustomClassDefinition : CustomTypeDefinition
|
||||||
/// The members of the custom class, with their names, types and default values.
|
/// The members of the custom class, with their names, types and default values.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<IProperty> Members { get; set; } = [];
|
public List<IProperty> Members { get; set; } = [];
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override IList<IProperty> GetProperties() => Members;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,14 @@ public enum CustomEnumStorageType
|
||||||
/// Represents a custom enum definition in Tiled. Refer to the
|
/// Represents a custom enum definition in Tiled. Refer to the
|
||||||
/// <see href="https://doc.mapeditor.org/en/stable/manual/custom-properties/#custom-types">documentation of custom types to understand how they work</see>.
|
/// <see href="https://doc.mapeditor.org/en/stable/manual/custom-properties/#custom-types">documentation of custom types to understand how they work</see>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CustomEnumDefinition : CustomTypeDefinition
|
public class CustomEnumDefinition : ICustomTypeDefinition
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public uint ID { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public required string Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The storage type of the custom enum.
|
/// The storage type of the custom enum.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -3,7 +3,7 @@ namespace DotTiled.Model;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class for custom type definitions.
|
/// Base class for custom type definitions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class CustomTypeDefinition
|
public interface ICustomTypeDefinition
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The ID of the custom type.
|
/// The ID of the custom type.
|
||||||
|
@ -13,5 +13,5 @@ public abstract class CustomTypeDefinition
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The name of the custom type.
|
/// The name of the custom type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; set; } = "";
|
public string Name { get; set; }
|
||||||
}
|
}
|
||||||
|
|
50
src/DotTiled/Model/Properties/EnumProperty.cs
Normal file
50
src/DotTiled/Model/Properties/EnumProperty.cs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace DotTiled.Model;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an enum property.
|
||||||
|
/// </summary>
|
||||||
|
public class EnumProperty : IProperty<ISet<string>>
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public required string Name { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public PropertyType Type => Model.PropertyType.Enum;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of the class property. This will be the name of a custom defined
|
||||||
|
/// type in Tiled.
|
||||||
|
/// </summary>
|
||||||
|
public required string PropertyType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The value of the enum property.
|
||||||
|
/// </summary>
|
||||||
|
public required ISet<string> Value { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IProperty Clone() => new EnumProperty
|
||||||
|
{
|
||||||
|
Name = Name,
|
||||||
|
PropertyType = PropertyType,
|
||||||
|
Value = Value.ToHashSet()
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the enum property is equal to the specified value.
|
||||||
|
/// For enums which have multiple values (e.g. flag enums), this method will only return true if it is the only value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to check.</param>
|
||||||
|
/// <returns>True if the enum property is equal to the specified value; otherwise, false.</returns>
|
||||||
|
public bool IsValue(string value) => Value.Contains(value) && Value.Count == 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the enum property has the specified value. This method is very similar to the common <see cref="System.Enum.HasFlag" /> method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to check.</param>
|
||||||
|
/// <returns>True if the enum property has the specified value as one of its values; otherwise, false.</returns>
|
||||||
|
public bool HasValue(string value) => Value.Contains(value);
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ namespace DotTiled.Model;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a file property.
|
/// Represents a file property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FileProperty : IProperty
|
public class FileProperty : IProperty<string>
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public required string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
|
|
|
@ -3,7 +3,7 @@ namespace DotTiled.Model;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a float property.
|
/// Represents a float property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FloatProperty : IProperty
|
public class FloatProperty : IProperty<float>
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public required string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
|
|
72
src/DotTiled/Model/Properties/IHasProperties.cs
Normal file
72
src/DotTiled/Model/Properties/IHasProperties.cs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace DotTiled.Model;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for objects that have properties attached to them.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHasProperties
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The properties attached to the object.
|
||||||
|
/// </summary>
|
||||||
|
IList<IProperty> GetProperties();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to get a property of the specified type with the specified name.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the property to get.</typeparam>
|
||||||
|
/// <param name="name">The name of the property to get.</param>
|
||||||
|
/// <param name="property">The property with the specified name, if found.</param>
|
||||||
|
/// <returns>True if a property with the specified name was found; otherwise, false.</returns>
|
||||||
|
bool TryGetProperty<T>(string name, out T? property) where T : IProperty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a property of the specified type with the specified name.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the property to get.</typeparam>
|
||||||
|
/// <param name="name">The name of the property to get.</param>
|
||||||
|
/// <returns>The property with the specified name.</returns>
|
||||||
|
T GetProperty<T>(string name) where T : IProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for objects that have properties attached to them.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class HasPropertiesBase : IHasProperties
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public abstract IList<IProperty> GetProperties();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
/// <exception cref="KeyNotFoundException">Thrown when a property with the specified name is not found.</exception>
|
||||||
|
/// <exception cref="InvalidCastException">Thrown when a property with the specified name is not of the specified type.</exception>
|
||||||
|
public T GetProperty<T>(string name) where T : IProperty
|
||||||
|
{
|
||||||
|
var properties = GetProperties();
|
||||||
|
var property = properties.FirstOrDefault(_properties => _properties.Name == name) ?? throw new KeyNotFoundException($"Property '{name}' not found.");
|
||||||
|
if (property is T prop)
|
||||||
|
{
|
||||||
|
return prop;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidCastException($"Property '{name}' is not of type '{typeof(T).Name}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGetProperty<T>(string name, [NotNullWhen(true)] out T? property) where T : IProperty
|
||||||
|
{
|
||||||
|
var properties = GetProperties();
|
||||||
|
if (properties.FirstOrDefault(_properties => _properties.Name == name) is T prop)
|
||||||
|
{
|
||||||
|
property = prop;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
property = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,3 +22,15 @@ public interface IProperty
|
||||||
/// <returns>An identical, but non-reference-equal, instance of the same property.</returns>
|
/// <returns>An identical, but non-reference-equal, instance of the same property.</returns>
|
||||||
IProperty Clone();
|
IProperty Clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for properties that can be attached to objects, tiles, tilesets, maps etc.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the property value.</typeparam>
|
||||||
|
public interface IProperty<T> : IProperty
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The value of the property.
|
||||||
|
/// </summary>
|
||||||
|
public T Value { get; set; }
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ namespace DotTiled.Model;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an integer property.
|
/// Represents an integer property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class IntProperty : IProperty
|
public class IntProperty : IProperty<int>
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public required string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
|
|
|
@ -3,7 +3,7 @@ namespace DotTiled.Model;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an object property.
|
/// Represents an object property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ObjectProperty : IProperty
|
public class ObjectProperty : IProperty<uint>
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public required string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
|
|
|
@ -43,5 +43,10 @@ public enum PropertyType
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A class property.
|
/// A class property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Class
|
Class,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An enum property.
|
||||||
|
/// </summary>
|
||||||
|
Enum
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ namespace DotTiled.Model;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a string property.
|
/// Represents a string property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class StringProperty : IProperty
|
public class StringProperty : IProperty<string>
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public required string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
|
|
|
@ -6,7 +6,7 @@ namespace DotTiled.Model;
|
||||||
/// Represents a single tile in a tileset, when using a collection of images to represent the tileset.
|
/// Represents a single tile in a tileset, when using a collection of images to represent the tileset.
|
||||||
/// <see href="https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#tile">Tiled documentation for Tileset tiles</see>
|
/// <see href="https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#tile">Tiled documentation for Tileset tiles</see>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Tile
|
public class Tile : HasPropertiesBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The local tile ID within its tileset.
|
/// The local tile ID within its tileset.
|
||||||
|
@ -46,7 +46,10 @@ public class Tile
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tile properties.
|
/// Tile properties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, IProperty>? Properties { get; set; }
|
public List<IProperty> Properties { get; set; } = [];
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override IList<IProperty> GetProperties() => Properties;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The image representing this tile. Only used for tilesets that composed of a collection of images.
|
/// The image representing this tile. Only used for tilesets that composed of a collection of images.
|
||||||
|
|
|
@ -93,7 +93,7 @@ public enum FillMode
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A tileset is a collection of tiles that can be used in a tile layer, or by tile objects.
|
/// A tileset is a collection of tiles that can be used in a tile layer, or by tile objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Tileset
|
public class Tileset : HasPropertiesBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The TMX format version. Is incremented to match minor Tiled releases.
|
/// The TMX format version. Is incremented to match minor Tiled releases.
|
||||||
|
@ -188,7 +188,10 @@ public class Tileset
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tileset properties.
|
/// Tileset properties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, IProperty>? Properties { get; set; }
|
public List<IProperty> Properties { get; set; } = [];
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override IList<IProperty> GetProperties() => Properties;
|
||||||
|
|
||||||
// public List<Terrain>? TerrainTypes { get; set; } TODO: Implement Terrain -> Wangset conversion during deserialization
|
// public List<Terrain>? TerrainTypes { get; set; } TODO: Implement Terrain -> Wangset conversion during deserialization
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ namespace DotTiled.Model;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a Wang color in a Wang set.
|
/// Represents a Wang color in a Wang set.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WangColor
|
public class WangColor : HasPropertiesBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The name of this color.
|
/// The name of this color.
|
||||||
|
@ -35,5 +35,8 @@ public class WangColor
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Wang color properties.
|
/// The Wang color properties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, IProperty>? Properties { get; set; }
|
public List<IProperty> Properties { get; set; } = [];
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override IList<IProperty> GetProperties() => Properties;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ namespace DotTiled.Model;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines a list of colors and any number of Wang tiles using these colors.
|
/// Defines a list of colors and any number of Wang tiles using these colors.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Wangset
|
public class Wangset : HasPropertiesBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The name of the Wang set.
|
/// The name of the Wang set.
|
||||||
|
@ -25,7 +25,10 @@ public class Wangset
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Wang set properties.
|
/// The Wang set properties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, IProperty>? Properties { get; set; }
|
public List<IProperty> Properties { get; set; } = [];
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override IList<IProperty> GetProperties() => Properties;
|
||||||
|
|
||||||
// Up to 254 Wang colors
|
// Up to 254 Wang colors
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -73,31 +73,52 @@ internal static partial class Helpers
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Dictionary<string, IProperty> MergeProperties(Dictionary<string, IProperty>? baseProperties, Dictionary<string, IProperty>? overrideProperties)
|
internal static List<IProperty> CreateInstanceOfCustomClass(
|
||||||
|
CustomClassDefinition customClassDefinition,
|
||||||
|
Func<string, ICustomTypeDefinition> customTypeResolver)
|
||||||
|
{
|
||||||
|
return customClassDefinition.Members.Select(x =>
|
||||||
|
{
|
||||||
|
if (x is ClassProperty cp)
|
||||||
|
{
|
||||||
|
return new ClassProperty
|
||||||
|
{
|
||||||
|
Name = cp.Name,
|
||||||
|
PropertyType = cp.PropertyType,
|
||||||
|
Value = CreateInstanceOfCustomClass((CustomClassDefinition)customTypeResolver(cp.PropertyType), customTypeResolver)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.Clone();
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static IList<IProperty> MergeProperties(IList<IProperty>? baseProperties, IList<IProperty>? overrideProperties)
|
||||||
{
|
{
|
||||||
if (baseProperties is null)
|
if (baseProperties is null)
|
||||||
return overrideProperties ?? new Dictionary<string, IProperty>();
|
return overrideProperties ?? [];
|
||||||
|
|
||||||
if (overrideProperties is null)
|
if (overrideProperties is null)
|
||||||
return baseProperties;
|
return baseProperties;
|
||||||
|
|
||||||
var result = baseProperties.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Clone());
|
var result = baseProperties.Select(x => x.Clone()).ToList();
|
||||||
foreach (var (key, value) in overrideProperties)
|
foreach (var overrideProp in overrideProperties)
|
||||||
{
|
{
|
||||||
if (!result.TryGetValue(key, out var baseProp))
|
if (!result.Any(x => x.Name == overrideProp.Name))
|
||||||
{
|
{
|
||||||
result[key] = value;
|
result.Add(overrideProp);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (value is ClassProperty classProp)
|
var existingProp = result.First(x => x.Name == overrideProp.Name);
|
||||||
|
if (existingProp is ClassProperty classProp)
|
||||||
{
|
{
|
||||||
((ClassProperty)baseProp).Properties = MergeProperties(((ClassProperty)baseProp).Properties, classProp.Properties);
|
classProp.Value = MergeProperties(classProp.Value, ((ClassProperty)overrideProp).Value);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result[key] = value;
|
ReplacePropertyInList(result, overrideProp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,6 +126,15 @@ internal static partial class Helpers
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void ReplacePropertyInList(List<IProperty> properties, IProperty property)
|
||||||
|
{
|
||||||
|
var index = properties.FindIndex(p => p.Name == property.Name);
|
||||||
|
if (index == -1)
|
||||||
|
properties.Add(property);
|
||||||
|
else
|
||||||
|
properties[index] = property;
|
||||||
|
}
|
||||||
|
|
||||||
internal static void SetAtMostOnce<T>(ref T? field, T value, string fieldName)
|
internal static void SetAtMostOnce<T>(ref T? field, T value, string fieldName)
|
||||||
{
|
{
|
||||||
if (field is not null)
|
if (field is not null)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using DotTiled.Model;
|
using DotTiled.Model;
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmj;
|
namespace DotTiled.Serialization.Tmj;
|
||||||
|
@ -7,73 +6,24 @@ namespace DotTiled.Serialization.Tmj;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A template reader for reading Tiled JSON templates.
|
/// A template reader for reading Tiled JSON templates.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TjTemplateReader : ITemplateReader
|
public class TjTemplateReader : TmjReaderBase, ITemplateReader
|
||||||
{
|
{
|
||||||
// External resolvers
|
|
||||||
private readonly Func<string, Tileset> _externalTilesetResolver;
|
|
||||||
private readonly Func<string, Template> _externalTemplateResolver;
|
|
||||||
|
|
||||||
private readonly string _jsonString;
|
|
||||||
private bool disposedValue;
|
|
||||||
|
|
||||||
private readonly IReadOnlyCollection<CustomTypeDefinition> _customTypeDefinitions;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs a new <see cref="TjTemplateReader"/>.
|
/// Constructs a new <see cref="TjTemplateReader"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="jsonString">A string containing a Tiled template in the Tiled JSON format.</param>
|
/// <param name="jsonString">A string containing a Tiled map in the Tiled JSON format.</param>
|
||||||
/// <param name="externalTilesetResolver">A function that resolves external tilesets given their source.</param>
|
/// <param name="externalTilesetResolver">A function that resolves external tilesets given their source.</param>
|
||||||
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
|
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
|
||||||
/// <param name="customTypeDefinitions">A collection of custom type definitions that can be used to resolve custom types when encountering <see cref="ClassProperty"/>.</param>
|
/// <param name="customTypeResolver">A function that resolves custom types given their name.</param>
|
||||||
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
|
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
|
||||||
public TjTemplateReader(
|
public TjTemplateReader(
|
||||||
string jsonString,
|
string jsonString,
|
||||||
Func<string, Tileset> externalTilesetResolver,
|
Func<string, Tileset> externalTilesetResolver,
|
||||||
Func<string, Template> externalTemplateResolver,
|
Func<string, Template> externalTemplateResolver,
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||||
{
|
jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||||
_jsonString = jsonString ?? throw new ArgumentNullException(nameof(jsonString));
|
{ }
|
||||||
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
|
||||||
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
|
||||||
_customTypeDefinitions = customTypeDefinitions ?? throw new ArgumentNullException(nameof(customTypeDefinitions));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Template ReadTemplate()
|
public Template ReadTemplate() => ReadTemplate(RootElement);
|
||||||
{
|
|
||||||
var jsonDoc = System.Text.Json.JsonDocument.Parse(_jsonString);
|
|
||||||
var rootElement = jsonDoc.RootElement;
|
|
||||||
return Tmj.ReadTemplate(rootElement, _externalTilesetResolver, _externalTemplateResolver, _customTypeDefinitions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!disposedValue)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
// TODO: dispose managed state (managed objects)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
|
||||||
// TODO: set large fields to null
|
|
||||||
disposedValue = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
|
||||||
// ~TjTemplateReader()
|
|
||||||
// {
|
|
||||||
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
||||||
// Dispose(disposing: false);
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
||||||
Dispose(disposing: true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text.Json;
|
|
||||||
using DotTiled.Model;
|
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmj;
|
|
||||||
|
|
||||||
internal partial class Tmj
|
|
||||||
{
|
|
||||||
internal static BaseLayer ReadLayer(
|
|
||||||
JsonElement element,
|
|
||||||
Func<string, Template> externalTemplateResolver,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
|
||||||
var type = element.GetRequiredProperty<string>("type");
|
|
||||||
|
|
||||||
return type switch
|
|
||||||
{
|
|
||||||
"tilelayer" => ReadTileLayer(element, customTypeDefinitions),
|
|
||||||
"objectgroup" => ReadObjectLayer(element, externalTemplateResolver, customTypeDefinitions),
|
|
||||||
"imagelayer" => ReadImageLayer(element, customTypeDefinitions),
|
|
||||||
"group" => ReadGroup(element, externalTemplateResolver, customTypeDefinitions),
|
|
||||||
_ => throw new JsonException($"Unsupported layer type '{type}'.")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text.Json;
|
|
||||||
using DotTiled.Model;
|
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmj;
|
|
||||||
|
|
||||||
internal partial class Tmj
|
|
||||||
{
|
|
||||||
internal static Dictionary<string, IProperty> ReadProperties(
|
|
||||||
JsonElement element,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions) =>
|
|
||||||
element.GetValueAsList<IProperty>(e =>
|
|
||||||
{
|
|
||||||
var name = e.GetRequiredProperty<string>("name");
|
|
||||||
var type = e.GetOptionalPropertyParseable<PropertyType>("type", s => s switch
|
|
||||||
{
|
|
||||||
"string" => PropertyType.String,
|
|
||||||
"int" => PropertyType.Int,
|
|
||||||
"float" => PropertyType.Float,
|
|
||||||
"bool" => PropertyType.Bool,
|
|
||||||
"color" => PropertyType.Color,
|
|
||||||
"file" => PropertyType.File,
|
|
||||||
"object" => PropertyType.Object,
|
|
||||||
"class" => PropertyType.Class,
|
|
||||||
_ => throw new JsonException("Invalid property type")
|
|
||||||
}, PropertyType.String);
|
|
||||||
|
|
||||||
IProperty property = type switch
|
|
||||||
{
|
|
||||||
PropertyType.String => new StringProperty { Name = name, Value = e.GetRequiredProperty<string>("value") },
|
|
||||||
PropertyType.Int => new IntProperty { Name = name, Value = e.GetRequiredProperty<int>("value") },
|
|
||||||
PropertyType.Float => new FloatProperty { Name = name, Value = e.GetRequiredProperty<float>("value") },
|
|
||||||
PropertyType.Bool => new BoolProperty { Name = name, Value = e.GetRequiredProperty<bool>("value") },
|
|
||||||
PropertyType.Color => new ColorProperty { Name = name, Value = e.GetRequiredPropertyParseable<Color>("value") },
|
|
||||||
PropertyType.File => new FileProperty { Name = name, Value = e.GetRequiredProperty<string>("value") },
|
|
||||||
PropertyType.Object => new ObjectProperty { Name = name, Value = e.GetRequiredProperty<uint>("value") },
|
|
||||||
PropertyType.Class => ReadClassProperty(e, customTypeDefinitions),
|
|
||||||
_ => throw new JsonException("Invalid property type")
|
|
||||||
};
|
|
||||||
|
|
||||||
return property!;
|
|
||||||
}).ToDictionary(p => p.Name);
|
|
||||||
|
|
||||||
internal static ClassProperty ReadClassProperty(
|
|
||||||
JsonElement element,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
|
||||||
var name = element.GetRequiredProperty<string>("name");
|
|
||||||
var propertyType = element.GetRequiredProperty<string>("propertytype");
|
|
||||||
|
|
||||||
var customTypeDef = customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == propertyType);
|
|
||||||
|
|
||||||
if (customTypeDef is CustomClassDefinition ccd)
|
|
||||||
{
|
|
||||||
var propsInType = CreateInstanceOfCustomClass(ccd);
|
|
||||||
var props = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>>("value", el => ReadCustomClassProperties(el, ccd, customTypeDefinitions), []);
|
|
||||||
|
|
||||||
var mergedProps = Helpers.MergeProperties(propsInType, props);
|
|
||||||
|
|
||||||
return new ClassProperty
|
|
||||||
{
|
|
||||||
Name = name,
|
|
||||||
PropertyType = propertyType,
|
|
||||||
Properties = mergedProps
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new JsonException($"Unknown custom class '{propertyType}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static Dictionary<string, IProperty> ReadCustomClassProperties(
|
|
||||||
JsonElement element,
|
|
||||||
CustomClassDefinition customClassDefinition,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
|
||||||
Dictionary<string, IProperty> resultingProps = [];
|
|
||||||
|
|
||||||
foreach (var prop in customClassDefinition.Members)
|
|
||||||
{
|
|
||||||
if (!element.TryGetProperty(prop.Name, out var propElement))
|
|
||||||
continue; // Property not present in element, therefore will use default value
|
|
||||||
|
|
||||||
IProperty property = prop.Type switch
|
|
||||||
{
|
|
||||||
PropertyType.String => new StringProperty { Name = prop.Name, Value = propElement.GetValueAs<string>() },
|
|
||||||
PropertyType.Int => new IntProperty { Name = prop.Name, Value = propElement.GetValueAs<int>() },
|
|
||||||
PropertyType.Float => new FloatProperty { Name = prop.Name, Value = propElement.GetValueAs<float>() },
|
|
||||||
PropertyType.Bool => new BoolProperty { Name = prop.Name, Value = propElement.GetValueAs<bool>() },
|
|
||||||
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 => ReadClassProperty(propElement, customTypeDefinitions),
|
|
||||||
_ => throw new JsonException("Invalid property type")
|
|
||||||
};
|
|
||||||
|
|
||||||
resultingProps[prop.Name] = property;
|
|
||||||
}
|
|
||||||
|
|
||||||
return resultingProps;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static Dictionary<string, IProperty> CreateInstanceOfCustomClass(CustomClassDefinition customClassDefinition) =>
|
|
||||||
customClassDefinition.Members.ToDictionary(m => m.Name, m => m.Clone());
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text.Json;
|
|
||||||
using DotTiled.Model;
|
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmj;
|
|
||||||
|
|
||||||
internal partial class Tmj
|
|
||||||
{
|
|
||||||
internal static Template ReadTemplate(
|
|
||||||
JsonElement element,
|
|
||||||
Func<string, Tileset> externalTilesetResolver,
|
|
||||||
Func<string, Template> externalTemplateResolver,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
|
||||||
var type = element.GetRequiredProperty<string>("type");
|
|
||||||
var tileset = element.GetOptionalPropertyCustom<Tileset?>("tileset", el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions), null);
|
|
||||||
var @object = element.GetRequiredPropertyCustom<Model.Object>("object", el => ReadObject(el, externalTemplateResolver, customTypeDefinitions));
|
|
||||||
|
|
||||||
return new Template
|
|
||||||
{
|
|
||||||
Tileset = tileset,
|
|
||||||
Object = @object
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text.Json;
|
|
||||||
using DotTiled.Model;
|
using DotTiled.Model;
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmj;
|
namespace DotTiled.Serialization.Tmj;
|
||||||
|
@ -8,73 +6,24 @@ namespace DotTiled.Serialization.Tmj;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A map reader for reading Tiled JSON maps.
|
/// A map reader for reading Tiled JSON maps.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TmjMapReader : IMapReader
|
public class TmjMapReader : TmjReaderBase, IMapReader
|
||||||
{
|
{
|
||||||
// External resolvers
|
|
||||||
private readonly Func<string, Tileset> _externalTilesetResolver;
|
|
||||||
private readonly Func<string, Template> _externalTemplateResolver;
|
|
||||||
|
|
||||||
private readonly string _jsonString;
|
|
||||||
private bool disposedValue;
|
|
||||||
|
|
||||||
private readonly IReadOnlyCollection<CustomTypeDefinition> _customTypeDefinitions;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs a new <see cref="TmjMapReader"/>.
|
/// Constructs a new <see cref="TmjMapReader"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="jsonString">A string containing a Tiled map in the Tiled JSON format.</param>
|
/// <param name="jsonString">A string containing a Tiled map in the Tiled JSON format.</param>
|
||||||
/// <param name="externalTilesetResolver">A function that resolves external tilesets given their source.</param>
|
/// <param name="externalTilesetResolver">A function that resolves external tilesets given their source.</param>
|
||||||
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
|
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
|
||||||
/// <param name="customTypeDefinitions">A collection of custom type definitions that can be used to resolve custom types when encountering <see cref="ClassProperty"/>.</param>
|
/// <param name="customTypeResolver">A function that resolves custom types given their name.</param>
|
||||||
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
|
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
|
||||||
public TmjMapReader(
|
public TmjMapReader(
|
||||||
string jsonString,
|
string jsonString,
|
||||||
Func<string, Tileset> externalTilesetResolver,
|
Func<string, Tileset> externalTilesetResolver,
|
||||||
Func<string, Template> externalTemplateResolver,
|
Func<string, Template> externalTemplateResolver,
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||||
{
|
jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||||
_jsonString = jsonString ?? throw new ArgumentNullException(nameof(jsonString));
|
{ }
|
||||||
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
|
||||||
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
|
||||||
_customTypeDefinitions = customTypeDefinitions ?? throw new ArgumentNullException(nameof(customTypeDefinitions));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Map ReadMap()
|
public Map ReadMap() => ReadMap(RootElement);
|
||||||
{
|
|
||||||
var jsonDoc = JsonDocument.Parse(_jsonString);
|
|
||||||
var rootElement = jsonDoc.RootElement;
|
|
||||||
return Tmj.ReadMap(rootElement, _externalTilesetResolver, _externalTemplateResolver, _customTypeDefinitions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!disposedValue)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
// TODO: dispose managed state (managed objects)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
|
||||||
// TODO: set large fields to null
|
|
||||||
disposedValue = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
|
||||||
// ~TmjMapReader()
|
|
||||||
// {
|
|
||||||
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
||||||
// Dispose(disposing: false);
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
||||||
Dispose(disposing: true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ using DotTiled.Model;
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmj;
|
namespace DotTiled.Serialization.Tmj;
|
||||||
|
|
||||||
internal partial class Tmj
|
public abstract partial class TmjReaderBase
|
||||||
{
|
{
|
||||||
internal static Data ReadDataAsChunks(JsonElement element, DataCompression? compression, DataEncoding encoding)
|
internal static Data ReadDataAsChunks(JsonElement element, DataCompression? compression, DataEncoding encoding)
|
||||||
{
|
{
|
|
@ -1,4 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
@ -6,12 +5,9 @@ using DotTiled.Model;
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmj;
|
namespace DotTiled.Serialization.Tmj;
|
||||||
|
|
||||||
internal partial class Tmj
|
public abstract partial class TmjReaderBase
|
||||||
{
|
{
|
||||||
internal static Group ReadGroup(
|
internal Group ReadGroup(JsonElement element)
|
||||||
JsonElement element,
|
|
||||||
Func<string, Template> externalTemplateResolver,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
{
|
||||||
var id = element.GetRequiredProperty<uint>("id");
|
var id = element.GetRequiredProperty<uint>("id");
|
||||||
var name = element.GetRequiredProperty<string>("name");
|
var name = element.GetRequiredProperty<string>("name");
|
||||||
|
@ -23,8 +19,8 @@ internal partial class Tmj
|
||||||
var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f);
|
var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f);
|
||||||
var parallaxX = element.GetOptionalProperty<float>("parallaxx", 1.0f);
|
var parallaxX = element.GetOptionalProperty<float>("parallaxx", 1.0f);
|
||||||
var parallaxY = element.GetOptionalProperty<float>("parallaxy", 1.0f);
|
var parallaxY = element.GetOptionalProperty<float>("parallaxy", 1.0f);
|
||||||
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null);
|
var properties = element.GetOptionalPropertyCustom("properties", ReadProperties, []);
|
||||||
var layers = element.GetOptionalPropertyCustom<List<BaseLayer>>("layers", e => e.GetValueAsList<BaseLayer>(el => ReadLayer(el, externalTemplateResolver, customTypeDefinitions)), []);
|
var layers = element.GetOptionalPropertyCustom<List<BaseLayer>>("layers", e => e.GetValueAsList<BaseLayer>(ReadLayer), []);
|
||||||
|
|
||||||
return new Group
|
return new Group
|
||||||
{
|
{
|
|
@ -1,15 +1,12 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using DotTiled.Model;
|
using DotTiled.Model;
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmj;
|
namespace DotTiled.Serialization.Tmj;
|
||||||
|
|
||||||
internal partial class Tmj
|
public abstract partial class TmjReaderBase
|
||||||
{
|
{
|
||||||
internal static ImageLayer ReadImageLayer(
|
internal ImageLayer ReadImageLayer(JsonElement element)
|
||||||
JsonElement element,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
{
|
||||||
var id = element.GetRequiredProperty<uint>("id");
|
var id = element.GetRequiredProperty<uint>("id");
|
||||||
var name = element.GetRequiredProperty<string>("name");
|
var name = element.GetRequiredProperty<string>("name");
|
||||||
|
@ -21,7 +18,7 @@ internal partial class Tmj
|
||||||
var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f);
|
var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f);
|
||||||
var parallaxX = element.GetOptionalProperty<float>("parallaxx", 1.0f);
|
var parallaxX = element.GetOptionalProperty<float>("parallaxx", 1.0f);
|
||||||
var parallaxY = element.GetOptionalProperty<float>("parallaxy", 1.0f);
|
var parallaxY = element.GetOptionalProperty<float>("parallaxy", 1.0f);
|
||||||
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null);
|
var properties = element.GetOptionalPropertyCustom("properties", ReadProperties, []);
|
||||||
|
|
||||||
var image = element.GetRequiredProperty<string>("image");
|
var image = element.GetRequiredProperty<string>("image");
|
||||||
var repeatX = element.GetOptionalProperty<bool>("repeatx", false);
|
var repeatX = element.GetOptionalProperty<bool>("repeatx", false);
|
21
src/DotTiled/Serialization/Tmj/TmjReaderBase.Layer.cs
Normal file
21
src/DotTiled/Serialization/Tmj/TmjReaderBase.Layer.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using System.Text.Json;
|
||||||
|
using DotTiled.Model;
|
||||||
|
|
||||||
|
namespace DotTiled.Serialization.Tmj;
|
||||||
|
|
||||||
|
public abstract partial class TmjReaderBase
|
||||||
|
{
|
||||||
|
internal BaseLayer ReadLayer(JsonElement element)
|
||||||
|
{
|
||||||
|
var type = element.GetRequiredProperty<string>("type");
|
||||||
|
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
"tilelayer" => ReadTileLayer(element),
|
||||||
|
"objectgroup" => ReadObjectLayer(element),
|
||||||
|
"imagelayer" => ReadImageLayer(element),
|
||||||
|
"group" => ReadGroup(element),
|
||||||
|
_ => throw new JsonException($"Unsupported layer type '{type}'.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
@ -6,13 +5,9 @@ using DotTiled.Model;
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmj;
|
namespace DotTiled.Serialization.Tmj;
|
||||||
|
|
||||||
internal partial class Tmj
|
public abstract partial class TmjReaderBase
|
||||||
{
|
{
|
||||||
internal static Map ReadMap(
|
internal Map ReadMap(JsonElement element)
|
||||||
JsonElement element,
|
|
||||||
Func<string, Tileset>? externalTilesetResolver,
|
|
||||||
Func<string, Template> externalTemplateResolver,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
{
|
||||||
var version = element.GetRequiredProperty<string>("version");
|
var version = element.GetRequiredProperty<string>("version");
|
||||||
var tiledVersion = element.GetRequiredProperty<string>("tiledversion");
|
var tiledVersion = element.GetRequiredProperty<string>("tiledversion");
|
||||||
|
@ -58,10 +53,10 @@ internal partial class Tmj
|
||||||
var nextObjectID = element.GetRequiredProperty<uint>("nextobjectid");
|
var nextObjectID = element.GetRequiredProperty<uint>("nextobjectid");
|
||||||
var infinite = element.GetOptionalProperty<bool>("infinite", false);
|
var infinite = element.GetOptionalProperty<bool>("infinite", false);
|
||||||
|
|
||||||
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", el => ReadProperties(el, customTypeDefinitions), null);
|
var properties = element.GetOptionalPropertyCustom("properties", ReadProperties, []);
|
||||||
|
|
||||||
List<BaseLayer> layers = element.GetOptionalPropertyCustom<List<BaseLayer>>("layers", e => e.GetValueAsList<BaseLayer>(el => ReadLayer(el, externalTemplateResolver, customTypeDefinitions)), []);
|
List<BaseLayer> layers = element.GetOptionalPropertyCustom<List<BaseLayer>>("layers", e => e.GetValueAsList<BaseLayer>(el => ReadLayer(el)), []);
|
||||||
List<Tileset> tilesets = element.GetOptionalPropertyCustom<List<Tileset>>("tilesets", e => e.GetValueAsList<Tileset>(el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions)), []);
|
List<Tileset> tilesets = element.GetOptionalPropertyCustom<List<Tileset>>("tilesets", e => e.GetValueAsList<Tileset>(el => ReadTileset(el)), []);
|
||||||
|
|
||||||
return new Map
|
return new Map
|
||||||
{
|
{
|
|
@ -1,4 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
@ -7,12 +6,9 @@ using DotTiled.Model;
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmj;
|
namespace DotTiled.Serialization.Tmj;
|
||||||
|
|
||||||
internal partial class Tmj
|
public abstract partial class TmjReaderBase
|
||||||
{
|
{
|
||||||
internal static ObjectLayer ReadObjectLayer(
|
internal ObjectLayer ReadObjectLayer(JsonElement element)
|
||||||
JsonElement element,
|
|
||||||
Func<string, Template> externalTemplateResolver,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
{
|
||||||
var id = element.GetRequiredProperty<uint>("id");
|
var id = element.GetRequiredProperty<uint>("id");
|
||||||
var name = element.GetRequiredProperty<string>("name");
|
var name = element.GetRequiredProperty<string>("name");
|
||||||
|
@ -24,7 +20,7 @@ internal partial class Tmj
|
||||||
var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f);
|
var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f);
|
||||||
var parallaxX = element.GetOptionalProperty<float>("parallaxx", 1.0f);
|
var parallaxX = element.GetOptionalProperty<float>("parallaxx", 1.0f);
|
||||||
var parallaxY = element.GetOptionalProperty<float>("parallaxy", 1.0f);
|
var parallaxY = element.GetOptionalProperty<float>("parallaxy", 1.0f);
|
||||||
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null);
|
var properties = element.GetOptionalPropertyCustom("properties", ReadProperties, []);
|
||||||
|
|
||||||
var x = element.GetOptionalProperty<uint>("x", 0);
|
var x = element.GetOptionalProperty<uint>("x", 0);
|
||||||
var y = element.GetOptionalProperty<uint>("y", 0);
|
var y = element.GetOptionalProperty<uint>("y", 0);
|
||||||
|
@ -38,7 +34,7 @@ internal partial class Tmj
|
||||||
_ => throw new JsonException($"Unknown draw order '{s}'.")
|
_ => throw new JsonException($"Unknown draw order '{s}'.")
|
||||||
}, DrawOrder.TopDown);
|
}, DrawOrder.TopDown);
|
||||||
|
|
||||||
var objects = element.GetOptionalPropertyCustom<List<Model.Object>>("objects", e => e.GetValueAsList<Model.Object>(el => ReadObject(el, externalTemplateResolver, customTypeDefinitions)), []);
|
var objects = element.GetOptionalPropertyCustom<List<Model.Object>>("objects", e => e.GetValueAsList<Model.Object>(el => ReadObject(el)), []);
|
||||||
|
|
||||||
return new ObjectLayer
|
return new ObjectLayer
|
||||||
{
|
{
|
||||||
|
@ -63,10 +59,7 @@ internal partial class Tmj
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Model.Object ReadObject(
|
internal Model.Object ReadObject(JsonElement element)
|
||||||
JsonElement element,
|
|
||||||
Func<string, Template> externalTemplateResolver,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
{
|
||||||
uint? idDefault = null;
|
uint? idDefault = null;
|
||||||
string nameDefault = "";
|
string nameDefault = "";
|
||||||
|
@ -82,12 +75,12 @@ internal partial class Tmj
|
||||||
bool pointDefault = false;
|
bool pointDefault = false;
|
||||||
List<Vector2>? polygonDefault = null;
|
List<Vector2>? polygonDefault = null;
|
||||||
List<Vector2>? polylineDefault = null;
|
List<Vector2>? polylineDefault = null;
|
||||||
Dictionary<string, IProperty>? propertiesDefault = null;
|
List<IProperty> propertiesDefault = [];
|
||||||
|
|
||||||
var template = element.GetOptionalProperty<string?>("template", null);
|
var template = element.GetOptionalProperty<string?>("template", null);
|
||||||
if (template is not null)
|
if (template is not null)
|
||||||
{
|
{
|
||||||
var resolvedTemplate = externalTemplateResolver(template);
|
var resolvedTemplate = _externalTemplateResolver(template);
|
||||||
var templObj = resolvedTemplate.Object;
|
var templObj = resolvedTemplate.Object;
|
||||||
|
|
||||||
idDefault = templObj.ID;
|
idDefault = templObj.ID;
|
||||||
|
@ -114,7 +107,7 @@ internal partial class Tmj
|
||||||
var point = element.GetOptionalProperty<bool>("point", pointDefault);
|
var point = element.GetOptionalProperty<bool>("point", pointDefault);
|
||||||
var polygon = element.GetOptionalPropertyCustom<List<Vector2>?>("polygon", ReadPoints, polygonDefault);
|
var polygon = element.GetOptionalPropertyCustom<List<Vector2>?>("polygon", ReadPoints, polygonDefault);
|
||||||
var polyline = element.GetOptionalPropertyCustom<List<Vector2>?>("polyline", ReadPoints, polylineDefault);
|
var polyline = element.GetOptionalPropertyCustom<List<Vector2>?>("polyline", ReadPoints, polylineDefault);
|
||||||
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), propertiesDefault);
|
var properties = element.GetOptionalPropertyCustom("properties", ReadProperties, propertiesDefault);
|
||||||
var rotation = element.GetOptionalProperty<float>("rotation", rotationDefault);
|
var rotation = element.GetOptionalProperty<float>("rotation", rotationDefault);
|
||||||
var text = element.GetOptionalPropertyCustom<TextObject?>("text", ReadText, null);
|
var text = element.GetOptionalPropertyCustom<TextObject?>("text", ReadText, null);
|
||||||
var type = element.GetOptionalProperty<string>("type", typeDefault);
|
var type = element.GetOptionalProperty<string>("type", typeDefault);
|
174
src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs
Normal file
174
src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using DotTiled.Model;
|
||||||
|
|
||||||
|
namespace DotTiled.Serialization.Tmj;
|
||||||
|
|
||||||
|
public abstract partial class TmjReaderBase
|
||||||
|
{
|
||||||
|
internal List<IProperty> ReadProperties(JsonElement element) =>
|
||||||
|
element.GetValueAsList<IProperty>(e =>
|
||||||
|
{
|
||||||
|
var name = e.GetRequiredProperty<string>("name");
|
||||||
|
var type = e.GetOptionalPropertyParseable<PropertyType>("type", s => s switch
|
||||||
|
{
|
||||||
|
"string" => PropertyType.String,
|
||||||
|
"int" => PropertyType.Int,
|
||||||
|
"float" => PropertyType.Float,
|
||||||
|
"bool" => PropertyType.Bool,
|
||||||
|
"color" => PropertyType.Color,
|
||||||
|
"file" => PropertyType.File,
|
||||||
|
"object" => PropertyType.Object,
|
||||||
|
"class" => PropertyType.Class,
|
||||||
|
_ => throw new JsonException("Invalid property type")
|
||||||
|
}, PropertyType.String);
|
||||||
|
var propertyType = e.GetOptionalProperty<string?>("propertytype", null);
|
||||||
|
if (propertyType is not null)
|
||||||
|
{
|
||||||
|
return ReadPropertyWithCustomType(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
IProperty property = type switch
|
||||||
|
{
|
||||||
|
PropertyType.String => new StringProperty { Name = name, Value = e.GetRequiredProperty<string>("value") },
|
||||||
|
PropertyType.Int => new IntProperty { Name = name, Value = e.GetRequiredProperty<int>("value") },
|
||||||
|
PropertyType.Float => new FloatProperty { Name = name, Value = e.GetRequiredProperty<float>("value") },
|
||||||
|
PropertyType.Bool => new BoolProperty { Name = name, Value = e.GetRequiredProperty<bool>("value") },
|
||||||
|
PropertyType.Color => new ColorProperty { Name = name, Value = e.GetRequiredPropertyParseable<Color>("value") },
|
||||||
|
PropertyType.File => new FileProperty { Name = name, Value = e.GetRequiredProperty<string>("value") },
|
||||||
|
PropertyType.Object => new ObjectProperty { Name = name, Value = e.GetRequiredProperty<uint>("value") },
|
||||||
|
PropertyType.Class => throw new JsonException("Class property must have a property type"),
|
||||||
|
PropertyType.Enum => throw new JsonException("Enum property must have a property type"),
|
||||||
|
_ => throw new JsonException("Invalid property type")
|
||||||
|
};
|
||||||
|
|
||||||
|
return property!;
|
||||||
|
});
|
||||||
|
|
||||||
|
internal IProperty ReadPropertyWithCustomType(JsonElement element)
|
||||||
|
{
|
||||||
|
var isClass = element.GetOptionalProperty<string?>("type", null) == "class";
|
||||||
|
if (isClass)
|
||||||
|
{
|
||||||
|
return ReadClassProperty(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ReadEnumProperty(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ClassProperty ReadClassProperty(JsonElement element)
|
||||||
|
{
|
||||||
|
var name = element.GetRequiredProperty<string>("name");
|
||||||
|
var propertyType = element.GetRequiredProperty<string>("propertytype");
|
||||||
|
var customTypeDef = _customTypeResolver(propertyType);
|
||||||
|
|
||||||
|
if (customTypeDef is CustomClassDefinition ccd)
|
||||||
|
{
|
||||||
|
var propsInType = Helpers.CreateInstanceOfCustomClass(ccd, _customTypeResolver);
|
||||||
|
var props = element.GetOptionalPropertyCustom<List<IProperty>>("value", e => ReadPropertiesInsideClass(e, ccd), []);
|
||||||
|
var mergedProps = Helpers.MergeProperties(propsInType, props);
|
||||||
|
|
||||||
|
return new ClassProperty
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
PropertyType = propertyType,
|
||||||
|
Value = mergedProps
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new JsonException($"Unknown custom class '{propertyType}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal List<IProperty> ReadPropertiesInsideClass(
|
||||||
|
JsonElement element,
|
||||||
|
CustomClassDefinition customClassDefinition)
|
||||||
|
{
|
||||||
|
List<IProperty> resultingProps = [];
|
||||||
|
|
||||||
|
foreach (var prop in customClassDefinition.Members)
|
||||||
|
{
|
||||||
|
if (!element.TryGetProperty(prop.Name, out var propElement))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
IProperty property = prop.Type switch
|
||||||
|
{
|
||||||
|
PropertyType.String => new StringProperty { Name = prop.Name, Value = propElement.GetValueAs<string>() },
|
||||||
|
PropertyType.Int => new IntProperty { Name = prop.Name, Value = propElement.GetValueAs<int>() },
|
||||||
|
PropertyType.Float => new FloatProperty { Name = prop.Name, Value = propElement.GetValueAs<float>() },
|
||||||
|
PropertyType.Bool => new BoolProperty { Name = prop.Name, Value = propElement.GetValueAs<bool>() },
|
||||||
|
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),
|
||||||
|
_ => throw new JsonException("Invalid property type")
|
||||||
|
};
|
||||||
|
|
||||||
|
resultingProps.Add(property);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultingProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal EnumProperty ReadEnumProperty(JsonElement element)
|
||||||
|
{
|
||||||
|
var name = element.GetRequiredProperty<string>("name");
|
||||||
|
var propertyType = element.GetRequiredProperty<string>("propertytype");
|
||||||
|
var typeInXml = element.GetOptionalPropertyParseable<PropertyType>("type", (s) => s switch
|
||||||
|
{
|
||||||
|
"string" => PropertyType.String,
|
||||||
|
"int" => PropertyType.Int,
|
||||||
|
_ => throw new JsonException("Invalid property type")
|
||||||
|
}, PropertyType.String);
|
||||||
|
var customTypeDef = _customTypeResolver(propertyType);
|
||||||
|
|
||||||
|
if (customTypeDef is not CustomEnumDefinition ced)
|
||||||
|
throw new JsonException($"Unknown custom enum '{propertyType}'. Enums must be defined");
|
||||||
|
|
||||||
|
if (ced.StorageType == CustomEnumStorageType.String)
|
||||||
|
{
|
||||||
|
var value = element.GetRequiredProperty<string>("value");
|
||||||
|
if (value.Contains(',') && !ced.ValueAsFlags)
|
||||||
|
throw new JsonException("Enum value must not contain ',' if not ValueAsFlags is set to true.");
|
||||||
|
|
||||||
|
if (ced.ValueAsFlags)
|
||||||
|
{
|
||||||
|
var values = value.Split(',').Select(v => v.Trim()).ToHashSet();
|
||||||
|
return new EnumProperty { Name = name, PropertyType = propertyType, Value = values };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new EnumProperty { Name = name, PropertyType = propertyType, Value = new HashSet<string> { value } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ced.StorageType == CustomEnumStorageType.Int)
|
||||||
|
{
|
||||||
|
var value = element.GetRequiredProperty<int>("value");
|
||||||
|
if (ced.ValueAsFlags)
|
||||||
|
{
|
||||||
|
var allValues = ced.Values;
|
||||||
|
var enumValues = new HashSet<string>();
|
||||||
|
for (var i = 0; i < allValues.Count; i++)
|
||||||
|
{
|
||||||
|
var mask = 1 << i;
|
||||||
|
if ((value & mask) == mask)
|
||||||
|
{
|
||||||
|
var enumValue = allValues[i];
|
||||||
|
_ = enumValues.Add(enumValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new EnumProperty { Name = name, PropertyType = propertyType, Value = enumValues };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var allValues = ced.Values;
|
||||||
|
var enumValue = allValues[value];
|
||||||
|
return new EnumProperty { Name = name, PropertyType = propertyType, Value = new HashSet<string> { enumValue } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new JsonException($"Unknown custom enum '{propertyType}'. Enums must be defined");
|
||||||
|
}
|
||||||
|
}
|
20
src/DotTiled/Serialization/Tmj/TmjReaderBase.Template.cs
Normal file
20
src/DotTiled/Serialization/Tmj/TmjReaderBase.Template.cs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
using System.Text.Json;
|
||||||
|
using DotTiled.Model;
|
||||||
|
|
||||||
|
namespace DotTiled.Serialization.Tmj;
|
||||||
|
|
||||||
|
public abstract partial class TmjReaderBase
|
||||||
|
{
|
||||||
|
internal Template ReadTemplate(JsonElement element)
|
||||||
|
{
|
||||||
|
var type = element.GetRequiredProperty<string>("type");
|
||||||
|
var tileset = element.GetOptionalPropertyCustom<Tileset?>("tileset", ReadTileset, null);
|
||||||
|
var @object = element.GetRequiredPropertyCustom<Model.Object>("object", ReadObject);
|
||||||
|
|
||||||
|
return new Template
|
||||||
|
{
|
||||||
|
Tileset = tileset,
|
||||||
|
Object = @object
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,12 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using DotTiled.Model;
|
using DotTiled.Model;
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmj;
|
namespace DotTiled.Serialization.Tmj;
|
||||||
|
|
||||||
internal partial class Tmj
|
public abstract partial class TmjReaderBase
|
||||||
{
|
{
|
||||||
internal static TileLayer ReadTileLayer(
|
internal TileLayer ReadTileLayer(JsonElement element)
|
||||||
JsonElement element,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
{
|
||||||
var compression = element.GetOptionalPropertyParseable<DataCompression?>("compression", s => s switch
|
var compression = element.GetOptionalPropertyParseable<DataCompression?>("compression", s => s switch
|
||||||
{
|
{
|
||||||
|
@ -35,7 +32,7 @@ internal partial class Tmj
|
||||||
var opacity = element.GetOptionalProperty<float>("opacity", 1.0f);
|
var opacity = element.GetOptionalProperty<float>("opacity", 1.0f);
|
||||||
var parallaxx = element.GetOptionalProperty<float>("parallaxx", 1.0f);
|
var parallaxx = element.GetOptionalProperty<float>("parallaxx", 1.0f);
|
||||||
var parallaxy = element.GetOptionalProperty<float>("parallaxy", 1.0f);
|
var parallaxy = element.GetOptionalProperty<float>("parallaxy", 1.0f);
|
||||||
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null);
|
var properties = element.GetOptionalPropertyCustom("properties", ReadProperties, []);
|
||||||
var repeatX = element.GetOptionalProperty<bool>("repeatx", false);
|
var repeatX = element.GetOptionalProperty<bool>("repeatx", false);
|
||||||
var repeatY = element.GetOptionalProperty<bool>("repeaty", false);
|
var repeatY = element.GetOptionalProperty<bool>("repeaty", false);
|
||||||
var startX = element.GetOptionalProperty<int>("startx", 0);
|
var startX = element.GetOptionalProperty<int>("startx", 0);
|
|
@ -1,4 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
@ -6,13 +5,9 @@ using DotTiled.Model;
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmj;
|
namespace DotTiled.Serialization.Tmj;
|
||||||
|
|
||||||
internal partial class Tmj
|
public abstract partial class TmjReaderBase
|
||||||
{
|
{
|
||||||
internal static Tileset ReadTileset(
|
internal Tileset ReadTileset(JsonElement element)
|
||||||
JsonElement element,
|
|
||||||
Func<string, Tileset>? externalTilesetResolver,
|
|
||||||
Func<string, Template> externalTemplateResolver,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
{
|
||||||
var backgroundColor = element.GetOptionalPropertyParseable<Color?>("backgroundcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
|
var backgroundColor = element.GetOptionalPropertyParseable<Color?>("backgroundcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
|
||||||
var @class = element.GetOptionalProperty<string>("class", "");
|
var @class = element.GetOptionalProperty<string>("class", "");
|
||||||
|
@ -44,7 +39,7 @@ internal partial class Tmj
|
||||||
"bottomright" => ObjectAlignment.BottomRight,
|
"bottomright" => ObjectAlignment.BottomRight,
|
||||||
_ => throw new JsonException($"Unknown object alignment '{s}'")
|
_ => throw new JsonException($"Unknown object alignment '{s}'")
|
||||||
}, ObjectAlignment.Unspecified);
|
}, ObjectAlignment.Unspecified);
|
||||||
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", el => ReadProperties(el, customTypeDefinitions), null);
|
var properties = element.GetOptionalPropertyCustom("properties", ReadProperties, []);
|
||||||
var source = element.GetOptionalProperty<string?>("source", null);
|
var source = element.GetOptionalProperty<string?>("source", null);
|
||||||
var spacing = element.GetOptionalProperty<uint?>("spacing", null);
|
var spacing = element.GetOptionalProperty<uint?>("spacing", null);
|
||||||
var tileCount = element.GetOptionalProperty<uint?>("tilecount", null);
|
var tileCount = element.GetOptionalProperty<uint?>("tilecount", null);
|
||||||
|
@ -57,20 +52,17 @@ internal partial class Tmj
|
||||||
"grid" => TileRenderSize.Grid,
|
"grid" => TileRenderSize.Grid,
|
||||||
_ => throw new JsonException($"Unknown tile render size '{s}'")
|
_ => throw new JsonException($"Unknown tile render size '{s}'")
|
||||||
}, TileRenderSize.Tile);
|
}, TileRenderSize.Tile);
|
||||||
var tiles = element.GetOptionalPropertyCustom<List<Tile>>("tiles", el => ReadTiles(el, externalTemplateResolver, customTypeDefinitions), []);
|
var tiles = element.GetOptionalPropertyCustom<List<Tile>>("tiles", ReadTiles, []);
|
||||||
var tileWidth = element.GetOptionalProperty<uint?>("tilewidth", null);
|
var tileWidth = element.GetOptionalProperty<uint?>("tilewidth", null);
|
||||||
var transparentColor = element.GetOptionalPropertyParseable<Color?>("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
|
var transparentColor = element.GetOptionalPropertyParseable<Color?>("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
|
||||||
var type = element.GetOptionalProperty<string?>("type", null);
|
var type = element.GetOptionalProperty<string?>("type", null);
|
||||||
var version = element.GetOptionalProperty<string?>("version", null);
|
var version = element.GetOptionalProperty<string?>("version", null);
|
||||||
var transformations = element.GetOptionalPropertyCustom<Transformations?>("transformations", ReadTransformations, null);
|
var transformations = element.GetOptionalPropertyCustom<Transformations?>("transformations", ReadTransformations, null);
|
||||||
var wangsets = element.GetOptionalPropertyCustom<List<Wangset>?>("wangsets", el => el.GetValueAsList<Wangset>(e => ReadWangset(e, customTypeDefinitions)), null);
|
var wangsets = element.GetOptionalPropertyCustom<List<Wangset>?>("wangsets", el => el.GetValueAsList<Wangset>(e => ReadWangset(e)), null);
|
||||||
|
|
||||||
if (source is not null)
|
if (source is not null)
|
||||||
{
|
{
|
||||||
if (externalTilesetResolver is null)
|
var resolvedTileset = _externalTilesetResolver(source);
|
||||||
throw new JsonException("External tileset resolver is required to resolve external tilesets.");
|
|
||||||
|
|
||||||
var resolvedTileset = externalTilesetResolver(source);
|
|
||||||
resolvedTileset.FirstGID = firstGID;
|
resolvedTileset.FirstGID = firstGID;
|
||||||
resolvedTileset.Source = source;
|
resolvedTileset.Source = source;
|
||||||
return resolvedTileset;
|
return resolvedTileset;
|
||||||
|
@ -159,10 +151,7 @@ internal partial class Tmj
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static List<Tile> ReadTiles(
|
internal List<Tile> ReadTiles(JsonElement element) =>
|
||||||
JsonElement element,
|
|
||||||
Func<string, Template> externalTemplateResolver,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions) =>
|
|
||||||
element.GetValueAsList<Tile>(e =>
|
element.GetValueAsList<Tile>(e =>
|
||||||
{
|
{
|
||||||
var animation = e.GetOptionalPropertyCustom<List<Frame>?>("animation", e => e.GetValueAsList<Frame>(ReadFrame), null);
|
var animation = e.GetOptionalPropertyCustom<List<Frame>?>("animation", e => e.GetValueAsList<Frame>(ReadFrame), null);
|
||||||
|
@ -174,9 +163,9 @@ internal partial class Tmj
|
||||||
var y = e.GetOptionalProperty<uint>("y", 0);
|
var y = e.GetOptionalProperty<uint>("y", 0);
|
||||||
var width = e.GetOptionalProperty<uint>("width", imageWidth ?? 0);
|
var width = e.GetOptionalProperty<uint>("width", imageWidth ?? 0);
|
||||||
var height = e.GetOptionalProperty<uint>("height", imageHeight ?? 0);
|
var height = e.GetOptionalProperty<uint>("height", imageHeight ?? 0);
|
||||||
var objectGroup = e.GetOptionalPropertyCustom<ObjectLayer?>("objectgroup", e => ReadObjectLayer(e, externalTemplateResolver, customTypeDefinitions), null);
|
var objectGroup = e.GetOptionalPropertyCustom<ObjectLayer?>("objectgroup", e => ReadObjectLayer(e), null);
|
||||||
var probability = e.GetOptionalProperty<float>("probability", 0.0f);
|
var probability = e.GetOptionalProperty<float>("probability", 0.0f);
|
||||||
var properties = e.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", el => ReadProperties(el, customTypeDefinitions), null);
|
var properties = e.GetOptionalPropertyCustom("properties", ReadProperties, []);
|
||||||
// var terrain, replaced by wangsets
|
// var terrain, replaced by wangsets
|
||||||
var type = e.GetOptionalProperty<string>("type", "");
|
var type = e.GetOptionalProperty<string>("type", "");
|
||||||
|
|
||||||
|
@ -216,14 +205,12 @@ internal partial class Tmj
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Wangset ReadWangset(
|
internal Wangset ReadWangset(JsonElement element)
|
||||||
JsonElement element,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
{
|
||||||
var @clalss = element.GetOptionalProperty<string>("class", "");
|
var @clalss = element.GetOptionalProperty<string>("class", "");
|
||||||
var colors = element.GetOptionalPropertyCustom<List<WangColor>>("colors", e => e.GetValueAsList<WangColor>(el => ReadWangColor(el, customTypeDefinitions)), []);
|
var colors = element.GetOptionalPropertyCustom<List<WangColor>>("colors", e => e.GetValueAsList<WangColor>(el => ReadWangColor(el)), []);
|
||||||
var name = element.GetRequiredProperty<string>("name");
|
var name = element.GetRequiredProperty<string>("name");
|
||||||
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null);
|
var properties = element.GetOptionalPropertyCustom("properties", ReadProperties, []);
|
||||||
var tile = element.GetOptionalProperty<int>("tile", 0);
|
var tile = element.GetOptionalProperty<int>("tile", 0);
|
||||||
var type = element.GetOptionalProperty<string>("type", "");
|
var type = element.GetOptionalProperty<string>("type", "");
|
||||||
var wangTiles = element.GetOptionalPropertyCustom<List<WangTile>>("wangtiles", e => e.GetValueAsList<WangTile>(ReadWangTile), []);
|
var wangTiles = element.GetOptionalPropertyCustom<List<WangTile>>("wangtiles", e => e.GetValueAsList<WangTile>(ReadWangTile), []);
|
||||||
|
@ -239,15 +226,13 @@ internal partial class Tmj
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static WangColor ReadWangColor(
|
internal WangColor ReadWangColor(JsonElement element)
|
||||||
JsonElement element,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
{
|
||||||
var @class = element.GetOptionalProperty<string>("class", "");
|
var @class = element.GetOptionalProperty<string>("class", "");
|
||||||
var color = element.GetRequiredPropertyParseable<Color>("color", s => Color.Parse(s, CultureInfo.InvariantCulture));
|
var color = element.GetRequiredPropertyParseable<Color>("color", s => Color.Parse(s, CultureInfo.InvariantCulture));
|
||||||
var name = element.GetRequiredProperty<string>("name");
|
var name = element.GetRequiredProperty<string>("name");
|
||||||
var probability = element.GetOptionalProperty<float>("probability", 1.0f);
|
var probability = element.GetOptionalProperty<float>("probability", 1.0f);
|
||||||
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null);
|
var properties = element.GetOptionalPropertyCustom("properties", ReadProperties, []);
|
||||||
var tile = element.GetOptionalProperty<int>("tile", 0);
|
var tile = element.GetOptionalProperty<int>("tile", 0);
|
||||||
|
|
||||||
return new WangColor
|
return new WangColor
|
74
src/DotTiled/Serialization/Tmj/TmjReaderBase.cs
Normal file
74
src/DotTiled/Serialization/Tmj/TmjReaderBase.cs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
using DotTiled.Model;
|
||||||
|
|
||||||
|
namespace DotTiled.Serialization.Tmj;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for Tiled JSON format readers.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The root element of the JSON document being read.
|
||||||
|
/// </summary>
|
||||||
|
protected JsonElement RootElement { get; private set; }
|
||||||
|
|
||||||
|
private bool disposedValue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a new <see cref="TmjMapReader"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsonString">A string containing a Tiled map in the Tiled JSON format.</param>
|
||||||
|
/// <param name="externalTilesetResolver">A function that resolves external tilesets given their source.</param>
|
||||||
|
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
|
||||||
|
/// <param name="customTypeResolver">A collection of custom type definitions that can be used to resolve custom types when encountering <see cref="ClassProperty"/>.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
|
||||||
|
protected TmjReaderBase(
|
||||||
|
string jsonString,
|
||||||
|
Func<string, Tileset> externalTilesetResolver,
|
||||||
|
Func<string, Template> externalTemplateResolver,
|
||||||
|
Func<string, ICustomTypeDefinition> customTypeResolver)
|
||||||
|
{
|
||||||
|
RootElement = JsonDocument.Parse(jsonString ?? throw new ArgumentNullException(nameof(jsonString))).RootElement;
|
||||||
|
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
||||||
|
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
||||||
|
_customTypeResolver = customTypeResolver ?? throw new ArgumentNullException(nameof(customTypeResolver));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposedValue)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
// TODO: dispose managed state (managed objects)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
||||||
|
// TODO: set large fields to null
|
||||||
|
disposedValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||||
|
// ~TmjMapReader()
|
||||||
|
// {
|
||||||
|
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
// Dispose(disposing: false);
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using DotTiled.Model;
|
using DotTiled.Model;
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmj;
|
namespace DotTiled.Serialization.Tmj;
|
||||||
|
@ -7,73 +6,24 @@ namespace DotTiled.Serialization.Tmj;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A tileset reader for the Tiled JSON format.
|
/// A tileset reader for the Tiled JSON format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TsjTilesetReader : ITilesetReader
|
public class TsjTilesetReader : TmjReaderBase, ITilesetReader
|
||||||
{
|
{
|
||||||
// External resolvers
|
|
||||||
private readonly Func<string, Template> _externalTemplateResolver;
|
|
||||||
|
|
||||||
private readonly string _jsonString;
|
|
||||||
private bool disposedValue;
|
|
||||||
|
|
||||||
private readonly IReadOnlyCollection<CustomTypeDefinition> _customTypeDefinitions;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs a new <see cref="TsjTilesetReader"/>.
|
/// Constructs a new <see cref="TsjTilesetReader"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="jsonString">A string containing a Tiled tileset in the Tiled JSON format.</param>
|
/// <param name="jsonString">A string containing a Tiled map in the Tiled JSON format.</param>
|
||||||
|
/// <param name="externalTilesetResolver">A function that resolves external tilesets given their source.</param>
|
||||||
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
|
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
|
||||||
/// <param name="customTypeDefinitions">A collection of custom type definitions that can be used to resolve custom types when encountering <see cref="ClassProperty"/>.</param>
|
/// <param name="customTypeResolver">A function that resolves custom types given their name.</param>
|
||||||
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
|
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
|
||||||
public TsjTilesetReader(
|
public TsjTilesetReader(
|
||||||
string jsonString,
|
string jsonString,
|
||||||
|
Func<string, Tileset> externalTilesetResolver,
|
||||||
Func<string, Template> externalTemplateResolver,
|
Func<string, Template> externalTemplateResolver,
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||||
{
|
jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||||
_jsonString = jsonString ?? throw new ArgumentNullException(nameof(jsonString));
|
{ }
|
||||||
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
|
||||||
_customTypeDefinitions = customTypeDefinitions ?? throw new ArgumentNullException(nameof(customTypeDefinitions));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Tileset ReadTileset()
|
public Tileset ReadTileset() => ReadTileset(RootElement);
|
||||||
{
|
|
||||||
var jsonDoc = System.Text.Json.JsonDocument.Parse(_jsonString);
|
|
||||||
var rootElement = jsonDoc.RootElement;
|
|
||||||
return Tmj.ReadTileset(
|
|
||||||
rootElement,
|
|
||||||
_ => throw new NotSupportedException("External tilesets cannot refer to other external tilesets."),
|
|
||||||
_externalTemplateResolver,
|
|
||||||
_customTypeDefinitions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!disposedValue)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
// TODO: dispose managed state (managed objects)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
|
||||||
// TODO: set large fields to null
|
|
||||||
disposedValue = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
|
||||||
// ~TsjTilesetReader()
|
|
||||||
// {
|
|
||||||
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
||||||
// Dispose(disposing: false);
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
||||||
Dispose(disposing: true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,107 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Xml;
|
|
||||||
using DotTiled.Model;
|
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmx;
|
|
||||||
|
|
||||||
internal partial class Tmx
|
|
||||||
{
|
|
||||||
internal static Map ReadMap(
|
|
||||||
XmlReader reader,
|
|
||||||
Func<string, Tileset> externalTilesetResolver,
|
|
||||||
Func<string, Template> externalTemplateResolver,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
|
||||||
// Attributes
|
|
||||||
var version = reader.GetRequiredAttribute("version");
|
|
||||||
var tiledVersion = reader.GetRequiredAttribute("tiledversion");
|
|
||||||
var @class = reader.GetOptionalAttribute("class") ?? "";
|
|
||||||
var orientation = reader.GetRequiredAttributeEnum<MapOrientation>("orientation", s => s switch
|
|
||||||
{
|
|
||||||
"orthogonal" => MapOrientation.Orthogonal,
|
|
||||||
"isometric" => MapOrientation.Isometric,
|
|
||||||
"staggered" => MapOrientation.Staggered,
|
|
||||||
"hexagonal" => MapOrientation.Hexagonal,
|
|
||||||
_ => throw new InvalidOperationException($"Unknown orientation '{s}'")
|
|
||||||
});
|
|
||||||
var renderOrder = reader.GetOptionalAttributeEnum<RenderOrder>("renderorder", s => s switch
|
|
||||||
{
|
|
||||||
"right-down" => RenderOrder.RightDown,
|
|
||||||
"right-up" => RenderOrder.RightUp,
|
|
||||||
"left-down" => RenderOrder.LeftDown,
|
|
||||||
"left-up" => RenderOrder.LeftUp,
|
|
||||||
_ => throw new InvalidOperationException($"Unknown render order '{s}'")
|
|
||||||
}) ?? RenderOrder.RightDown;
|
|
||||||
var compressionLevel = reader.GetOptionalAttributeParseable<int>("compressionlevel") ?? -1;
|
|
||||||
var width = reader.GetRequiredAttributeParseable<uint>("width");
|
|
||||||
var height = reader.GetRequiredAttributeParseable<uint>("height");
|
|
||||||
var tileWidth = reader.GetRequiredAttributeParseable<uint>("tilewidth");
|
|
||||||
var tileHeight = reader.GetRequiredAttributeParseable<uint>("tileheight");
|
|
||||||
var hexSideLength = reader.GetOptionalAttributeParseable<uint>("hexsidelength");
|
|
||||||
var staggerAxis = reader.GetOptionalAttributeEnum<StaggerAxis>("staggeraxis", s => s switch
|
|
||||||
{
|
|
||||||
"x" => StaggerAxis.X,
|
|
||||||
"y" => StaggerAxis.Y,
|
|
||||||
_ => throw new InvalidOperationException($"Unknown stagger axis '{s}'")
|
|
||||||
});
|
|
||||||
var staggerIndex = reader.GetOptionalAttributeEnum<StaggerIndex>("staggerindex", s => s switch
|
|
||||||
{
|
|
||||||
"odd" => StaggerIndex.Odd,
|
|
||||||
"even" => StaggerIndex.Even,
|
|
||||||
_ => throw new InvalidOperationException($"Unknown stagger index '{s}'")
|
|
||||||
});
|
|
||||||
var parallaxOriginX = reader.GetOptionalAttributeParseable<float>("parallaxoriginx") ?? 0.0f;
|
|
||||||
var parallaxOriginY = reader.GetOptionalAttributeParseable<float>("parallaxoriginy") ?? 0.0f;
|
|
||||||
var backgroundColor = reader.GetOptionalAttributeClass<Color>("backgroundcolor") ?? Color.Parse("#00000000", CultureInfo.InvariantCulture);
|
|
||||||
var nextLayerID = reader.GetRequiredAttributeParseable<uint>("nextlayerid");
|
|
||||||
var nextObjectID = reader.GetRequiredAttributeParseable<uint>("nextobjectid");
|
|
||||||
var infinite = (reader.GetOptionalAttributeParseable<uint>("infinite") ?? 0) == 1;
|
|
||||||
|
|
||||||
// At most one of
|
|
||||||
Dictionary<string, IProperty>? properties = null;
|
|
||||||
|
|
||||||
// Any number of
|
|
||||||
List<BaseLayer> layers = [];
|
|
||||||
List<Tileset> tilesets = [];
|
|
||||||
|
|
||||||
reader.ProcessChildren("map", (r, elementName) => elementName switch
|
|
||||||
{
|
|
||||||
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"),
|
|
||||||
"tileset" => () => tilesets.Add(ReadTileset(r, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions)),
|
|
||||||
"layer" => () => layers.Add(ReadTileLayer(r, infinite, customTypeDefinitions)),
|
|
||||||
"objectgroup" => () => layers.Add(ReadObjectLayer(r, externalTemplateResolver, customTypeDefinitions)),
|
|
||||||
"imagelayer" => () => layers.Add(ReadImageLayer(r, customTypeDefinitions)),
|
|
||||||
"group" => () => layers.Add(ReadGroup(r, externalTemplateResolver, customTypeDefinitions)),
|
|
||||||
_ => r.Skip
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Map
|
|
||||||
{
|
|
||||||
Version = version,
|
|
||||||
TiledVersion = tiledVersion,
|
|
||||||
Class = @class,
|
|
||||||
Orientation = orientation,
|
|
||||||
RenderOrder = renderOrder,
|
|
||||||
CompressionLevel = compressionLevel,
|
|
||||||
Width = width,
|
|
||||||
Height = height,
|
|
||||||
TileWidth = tileWidth,
|
|
||||||
TileHeight = tileHeight,
|
|
||||||
HexSideLength = hexSideLength,
|
|
||||||
StaggerAxis = staggerAxis,
|
|
||||||
StaggerIndex = staggerIndex,
|
|
||||||
ParallaxOriginX = parallaxOriginX,
|
|
||||||
ParallaxOriginY = parallaxOriginY,
|
|
||||||
BackgroundColor = backgroundColor,
|
|
||||||
NextLayerID = nextLayerID,
|
|
||||||
NextObjectID = nextObjectID,
|
|
||||||
Infinite = infinite,
|
|
||||||
Properties = properties,
|
|
||||||
Tilesets = tilesets,
|
|
||||||
Layers = layers
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Xml;
|
|
||||||
using DotTiled.Model;
|
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmx;
|
|
||||||
|
|
||||||
internal partial class Tmx
|
|
||||||
{
|
|
||||||
internal static Dictionary<string, IProperty> ReadProperties(
|
|
||||||
XmlReader reader,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
|
||||||
return reader.ReadList("properties", "property", (r) =>
|
|
||||||
{
|
|
||||||
var name = r.GetRequiredAttribute("name");
|
|
||||||
var type = r.GetOptionalAttributeEnum<PropertyType>("type", (s) => s switch
|
|
||||||
{
|
|
||||||
"string" => PropertyType.String,
|
|
||||||
"int" => PropertyType.Int,
|
|
||||||
"float" => PropertyType.Float,
|
|
||||||
"bool" => PropertyType.Bool,
|
|
||||||
"color" => PropertyType.Color,
|
|
||||||
"file" => PropertyType.File,
|
|
||||||
"object" => PropertyType.Object,
|
|
||||||
"class" => PropertyType.Class,
|
|
||||||
_ => throw new XmlException("Invalid property type")
|
|
||||||
}) ?? PropertyType.String;
|
|
||||||
|
|
||||||
IProperty property = type switch
|
|
||||||
{
|
|
||||||
PropertyType.String => new StringProperty { Name = name, Value = r.GetRequiredAttribute("value") },
|
|
||||||
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.File => new FileProperty { Name = name, Value = r.GetRequiredAttribute("value") },
|
|
||||||
PropertyType.Object => new ObjectProperty { Name = name, Value = r.GetRequiredAttributeParseable<uint>("value") },
|
|
||||||
PropertyType.Class => ReadClassProperty(r, customTypeDefinitions),
|
|
||||||
_ => throw new XmlException("Invalid property type")
|
|
||||||
};
|
|
||||||
return (name, property);
|
|
||||||
}).ToDictionary(x => x.name, x => x.property);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static ClassProperty ReadClassProperty(
|
|
||||||
XmlReader reader,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
|
||||||
var name = reader.GetRequiredAttribute("name");
|
|
||||||
var propertyType = reader.GetRequiredAttribute("propertytype");
|
|
||||||
|
|
||||||
var customTypeDef = customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == propertyType);
|
|
||||||
if (customTypeDef is CustomClassDefinition ccd)
|
|
||||||
{
|
|
||||||
reader.ReadStartElement("property");
|
|
||||||
var propsInType = CreateInstanceOfCustomClass(ccd);
|
|
||||||
var props = ReadProperties(reader, customTypeDefinitions);
|
|
||||||
|
|
||||||
var mergedProps = Helpers.MergeProperties(propsInType, props);
|
|
||||||
|
|
||||||
reader.ReadEndElement();
|
|
||||||
return new ClassProperty { Name = name, PropertyType = propertyType, Properties = mergedProps };
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new XmlException($"Unkonwn custom class definition: {propertyType}");
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static Dictionary<string, IProperty> CreateInstanceOfCustomClass(CustomClassDefinition customClassDefinition) =>
|
|
||||||
customClassDefinition.Members.ToDictionary(m => m.Name, m => m.Clone());
|
|
||||||
}
|
|
|
@ -1,157 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Xml;
|
|
||||||
using DotTiled.Model;
|
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmx;
|
|
||||||
|
|
||||||
internal partial class Tmx
|
|
||||||
{
|
|
||||||
internal static TileLayer ReadTileLayer(
|
|
||||||
XmlReader reader,
|
|
||||||
bool dataUsesChunks,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
|
||||||
var id = reader.GetRequiredAttributeParseable<uint>("id");
|
|
||||||
var name = reader.GetOptionalAttribute("name") ?? "";
|
|
||||||
var @class = reader.GetOptionalAttribute("class") ?? "";
|
|
||||||
var x = reader.GetOptionalAttributeParseable<uint>("x") ?? 0;
|
|
||||||
var y = reader.GetOptionalAttributeParseable<uint>("y") ?? 0;
|
|
||||||
var width = reader.GetRequiredAttributeParseable<uint>("width");
|
|
||||||
var height = reader.GetRequiredAttributeParseable<uint>("height");
|
|
||||||
var opacity = reader.GetOptionalAttributeParseable<float>("opacity") ?? 1.0f;
|
|
||||||
var visible = reader.GetOptionalAttributeParseable<bool>("visible") ?? true;
|
|
||||||
var tintColor = reader.GetOptionalAttributeClass<Color>("tintcolor");
|
|
||||||
var offsetX = reader.GetOptionalAttributeParseable<float>("offsetx") ?? 0.0f;
|
|
||||||
var offsetY = reader.GetOptionalAttributeParseable<float>("offsety") ?? 0.0f;
|
|
||||||
var parallaxX = reader.GetOptionalAttributeParseable<float>("parallaxx") ?? 1.0f;
|
|
||||||
var parallaxY = reader.GetOptionalAttributeParseable<float>("parallaxy") ?? 1.0f;
|
|
||||||
|
|
||||||
Dictionary<string, IProperty>? properties = null;
|
|
||||||
Data? data = null;
|
|
||||||
|
|
||||||
reader.ProcessChildren("layer", (r, elementName) => elementName switch
|
|
||||||
{
|
|
||||||
"data" => () => Helpers.SetAtMostOnce(ref data, ReadData(r, dataUsesChunks), "Data"),
|
|
||||||
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"),
|
|
||||||
_ => r.Skip
|
|
||||||
});
|
|
||||||
|
|
||||||
return new TileLayer
|
|
||||||
{
|
|
||||||
ID = id,
|
|
||||||
Name = name,
|
|
||||||
Class = @class,
|
|
||||||
X = x,
|
|
||||||
Y = y,
|
|
||||||
Width = width,
|
|
||||||
Height = height,
|
|
||||||
Opacity = opacity,
|
|
||||||
Visible = visible,
|
|
||||||
TintColor = tintColor,
|
|
||||||
OffsetX = offsetX,
|
|
||||||
OffsetY = offsetY,
|
|
||||||
ParallaxX = parallaxX,
|
|
||||||
ParallaxY = parallaxY,
|
|
||||||
Data = data,
|
|
||||||
Properties = properties
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static ImageLayer ReadImageLayer(
|
|
||||||
XmlReader reader,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
|
||||||
var id = reader.GetRequiredAttributeParseable<uint>("id");
|
|
||||||
var name = reader.GetOptionalAttribute("name") ?? "";
|
|
||||||
var @class = reader.GetOptionalAttribute("class") ?? "";
|
|
||||||
var x = reader.GetOptionalAttributeParseable<uint>("x") ?? 0;
|
|
||||||
var y = reader.GetOptionalAttributeParseable<uint>("y") ?? 0;
|
|
||||||
var opacity = reader.GetOptionalAttributeParseable<float>("opacity") ?? 1.0f;
|
|
||||||
var visible = reader.GetOptionalAttributeParseable<bool>("visible") ?? true;
|
|
||||||
var tintColor = reader.GetOptionalAttributeClass<Color>("tintcolor");
|
|
||||||
var offsetX = reader.GetOptionalAttributeParseable<float>("offsetx") ?? 0.0f;
|
|
||||||
var offsetY = reader.GetOptionalAttributeParseable<float>("offsety") ?? 0.0f;
|
|
||||||
var parallaxX = reader.GetOptionalAttributeParseable<float>("parallaxx") ?? 1.0f;
|
|
||||||
var parallaxY = reader.GetOptionalAttributeParseable<float>("parallaxy") ?? 1.0f;
|
|
||||||
var repeatX = (reader.GetOptionalAttributeParseable<uint>("repeatx") ?? 0) == 1;
|
|
||||||
var repeatY = (reader.GetOptionalAttributeParseable<uint>("repeaty") ?? 0) == 1;
|
|
||||||
|
|
||||||
Dictionary<string, IProperty>? properties = null;
|
|
||||||
Image? image = null;
|
|
||||||
|
|
||||||
reader.ProcessChildren("imagelayer", (r, elementName) => elementName switch
|
|
||||||
{
|
|
||||||
"image" => () => Helpers.SetAtMostOnce(ref image, ReadImage(r), "Image"),
|
|
||||||
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"),
|
|
||||||
_ => r.Skip
|
|
||||||
});
|
|
||||||
|
|
||||||
return new ImageLayer
|
|
||||||
{
|
|
||||||
ID = id,
|
|
||||||
Name = name,
|
|
||||||
Class = @class,
|
|
||||||
X = x,
|
|
||||||
Y = y,
|
|
||||||
Opacity = opacity,
|
|
||||||
Visible = visible,
|
|
||||||
TintColor = tintColor,
|
|
||||||
OffsetX = offsetX,
|
|
||||||
OffsetY = offsetY,
|
|
||||||
ParallaxX = parallaxX,
|
|
||||||
ParallaxY = parallaxY,
|
|
||||||
Properties = properties,
|
|
||||||
Image = image,
|
|
||||||
RepeatX = repeatX,
|
|
||||||
RepeatY = repeatY
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static Group ReadGroup(
|
|
||||||
XmlReader reader,
|
|
||||||
Func<string, Template> externalTemplateResolver,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
|
||||||
var id = reader.GetRequiredAttributeParseable<uint>("id");
|
|
||||||
var name = reader.GetOptionalAttribute("name") ?? "";
|
|
||||||
var @class = reader.GetOptionalAttribute("class") ?? "";
|
|
||||||
var opacity = reader.GetOptionalAttributeParseable<float>("opacity") ?? 1.0f;
|
|
||||||
var visible = reader.GetOptionalAttributeParseable<bool>("visible") ?? true;
|
|
||||||
var tintColor = reader.GetOptionalAttributeClass<Color>("tintcolor");
|
|
||||||
var offsetX = reader.GetOptionalAttributeParseable<float>("offsetx") ?? 0.0f;
|
|
||||||
var offsetY = reader.GetOptionalAttributeParseable<float>("offsety") ?? 0.0f;
|
|
||||||
var parallaxX = reader.GetOptionalAttributeParseable<float>("parallaxx") ?? 1.0f;
|
|
||||||
var parallaxY = reader.GetOptionalAttributeParseable<float>("parallaxy") ?? 1.0f;
|
|
||||||
|
|
||||||
Dictionary<string, IProperty>? properties = null;
|
|
||||||
List<BaseLayer> layers = [];
|
|
||||||
|
|
||||||
reader.ProcessChildren("group", (r, elementName) => elementName switch
|
|
||||||
{
|
|
||||||
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"),
|
|
||||||
"layer" => () => layers.Add(ReadTileLayer(r, false, customTypeDefinitions)),
|
|
||||||
"objectgroup" => () => layers.Add(ReadObjectLayer(r, externalTemplateResolver, customTypeDefinitions)),
|
|
||||||
"imagelayer" => () => layers.Add(ReadImageLayer(r, customTypeDefinitions)),
|
|
||||||
"group" => () => layers.Add(ReadGroup(r, externalTemplateResolver, customTypeDefinitions)),
|
|
||||||
_ => r.Skip
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Group
|
|
||||||
{
|
|
||||||
ID = id,
|
|
||||||
Name = name,
|
|
||||||
Class = @class,
|
|
||||||
Opacity = opacity,
|
|
||||||
Visible = visible,
|
|
||||||
TintColor = tintColor,
|
|
||||||
OffsetX = offsetX,
|
|
||||||
OffsetY = offsetY,
|
|
||||||
ParallaxX = parallaxX,
|
|
||||||
ParallaxY = parallaxY,
|
|
||||||
Properties = properties,
|
|
||||||
Layers = layers
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,350 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Xml;
|
|
||||||
using DotTiled.Model;
|
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmx;
|
|
||||||
|
|
||||||
internal partial class Tmx
|
|
||||||
{
|
|
||||||
internal static Tileset ReadTileset(
|
|
||||||
XmlReader reader,
|
|
||||||
Func<string, Tileset>? externalTilesetResolver,
|
|
||||||
Func<string, Template> externalTemplateResolver,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
|
||||||
// Attributes
|
|
||||||
var version = reader.GetOptionalAttribute("version");
|
|
||||||
var tiledVersion = reader.GetOptionalAttribute("tiledversion");
|
|
||||||
var firstGID = reader.GetOptionalAttributeParseable<uint>("firstgid");
|
|
||||||
var source = reader.GetOptionalAttribute("source");
|
|
||||||
var name = reader.GetOptionalAttribute("name");
|
|
||||||
var @class = reader.GetOptionalAttribute("class") ?? "";
|
|
||||||
var tileWidth = reader.GetOptionalAttributeParseable<uint>("tilewidth");
|
|
||||||
var tileHeight = reader.GetOptionalAttributeParseable<uint>("tileheight");
|
|
||||||
var spacing = reader.GetOptionalAttributeParseable<uint>("spacing") ?? 0;
|
|
||||||
var margin = reader.GetOptionalAttributeParseable<uint>("margin") ?? 0;
|
|
||||||
var tileCount = reader.GetOptionalAttributeParseable<uint>("tilecount");
|
|
||||||
var columns = reader.GetOptionalAttributeParseable<uint>("columns");
|
|
||||||
var objectAlignment = reader.GetOptionalAttributeEnum<ObjectAlignment>("objectalignment", s => s switch
|
|
||||||
{
|
|
||||||
"unspecified" => ObjectAlignment.Unspecified,
|
|
||||||
"topleft" => ObjectAlignment.TopLeft,
|
|
||||||
"top" => ObjectAlignment.Top,
|
|
||||||
"topright" => ObjectAlignment.TopRight,
|
|
||||||
"left" => ObjectAlignment.Left,
|
|
||||||
"center" => ObjectAlignment.Center,
|
|
||||||
"right" => ObjectAlignment.Right,
|
|
||||||
"bottomleft" => ObjectAlignment.BottomLeft,
|
|
||||||
"bottom" => ObjectAlignment.Bottom,
|
|
||||||
"bottomright" => ObjectAlignment.BottomRight,
|
|
||||||
_ => throw new InvalidOperationException($"Unknown object alignment '{s}'")
|
|
||||||
}) ?? ObjectAlignment.Unspecified;
|
|
||||||
var renderSize = reader.GetOptionalAttributeEnum<TileRenderSize>("rendersize", s => s switch
|
|
||||||
{
|
|
||||||
"tile" => TileRenderSize.Tile,
|
|
||||||
"grid" => TileRenderSize.Grid,
|
|
||||||
_ => throw new InvalidOperationException($"Unknown render size '{s}'")
|
|
||||||
}) ?? TileRenderSize.Tile;
|
|
||||||
var fillMode = reader.GetOptionalAttributeEnum<FillMode>("fillmode", s => s switch
|
|
||||||
{
|
|
||||||
"stretch" => FillMode.Stretch,
|
|
||||||
"preserve-aspect-fit" => FillMode.PreserveAspectFit,
|
|
||||||
_ => throw new InvalidOperationException($"Unknown fill mode '{s}'")
|
|
||||||
}) ?? FillMode.Stretch;
|
|
||||||
|
|
||||||
// Elements
|
|
||||||
Image? image = null;
|
|
||||||
TileOffset? tileOffset = null;
|
|
||||||
Grid? grid = null;
|
|
||||||
Dictionary<string, IProperty>? properties = null;
|
|
||||||
List<Wangset>? wangsets = null;
|
|
||||||
Transformations? transformations = null;
|
|
||||||
List<Tile> tiles = [];
|
|
||||||
|
|
||||||
reader.ProcessChildren("tileset", (r, elementName) => elementName switch
|
|
||||||
{
|
|
||||||
"image" => () => Helpers.SetAtMostOnce(ref image, ReadImage(r), "Image"),
|
|
||||||
"tileoffset" => () => Helpers.SetAtMostOnce(ref tileOffset, ReadTileOffset(r), "TileOffset"),
|
|
||||||
"grid" => () => Helpers.SetAtMostOnce(ref grid, ReadGrid(r), "Grid"),
|
|
||||||
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"),
|
|
||||||
"wangsets" => () => Helpers.SetAtMostOnce(ref wangsets, ReadWangsets(r, customTypeDefinitions), "Wangsets"),
|
|
||||||
"transformations" => () => Helpers.SetAtMostOnce(ref transformations, ReadTransformations(r), "Transformations"),
|
|
||||||
"tile" => () => tiles.Add(ReadTile(r, externalTemplateResolver, customTypeDefinitions)),
|
|
||||||
_ => r.Skip
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if tileset is referring to external file
|
|
||||||
if (source is not null)
|
|
||||||
{
|
|
||||||
if (externalTilesetResolver is null)
|
|
||||||
throw new InvalidOperationException("External tileset resolver is required to resolve external tilesets.");
|
|
||||||
|
|
||||||
var resolvedTileset = externalTilesetResolver(source);
|
|
||||||
resolvedTileset.FirstGID = firstGID;
|
|
||||||
resolvedTileset.Source = source;
|
|
||||||
return resolvedTileset;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Tileset
|
|
||||||
{
|
|
||||||
Version = version,
|
|
||||||
TiledVersion = tiledVersion,
|
|
||||||
FirstGID = firstGID,
|
|
||||||
Source = source,
|
|
||||||
Name = name,
|
|
||||||
Class = @class,
|
|
||||||
TileWidth = tileWidth,
|
|
||||||
TileHeight = tileHeight,
|
|
||||||
Spacing = spacing,
|
|
||||||
Margin = margin,
|
|
||||||
TileCount = tileCount,
|
|
||||||
Columns = columns,
|
|
||||||
ObjectAlignment = objectAlignment,
|
|
||||||
RenderSize = renderSize,
|
|
||||||
FillMode = fillMode,
|
|
||||||
Image = image,
|
|
||||||
TileOffset = tileOffset,
|
|
||||||
Grid = grid,
|
|
||||||
Properties = properties,
|
|
||||||
Wangsets = wangsets,
|
|
||||||
Transformations = transformations,
|
|
||||||
Tiles = tiles
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static Image ReadImage(XmlReader reader)
|
|
||||||
{
|
|
||||||
// Attributes
|
|
||||||
var format = reader.GetOptionalAttributeEnum<ImageFormat>("format", s => s switch
|
|
||||||
{
|
|
||||||
"png" => ImageFormat.Png,
|
|
||||||
"jpg" => ImageFormat.Jpg,
|
|
||||||
"bmp" => ImageFormat.Bmp,
|
|
||||||
"gif" => ImageFormat.Gif,
|
|
||||||
_ => throw new InvalidOperationException($"Unknown image format '{s}'")
|
|
||||||
});
|
|
||||||
var source = reader.GetOptionalAttribute("source");
|
|
||||||
var transparentColor = reader.GetOptionalAttributeClass<Color>("trans");
|
|
||||||
var width = reader.GetOptionalAttributeParseable<uint>("width");
|
|
||||||
var height = reader.GetOptionalAttributeParseable<uint>("height");
|
|
||||||
|
|
||||||
reader.ProcessChildren("image", (r, elementName) => elementName switch
|
|
||||||
{
|
|
||||||
"data" => throw new NotSupportedException("Embedded image data is not supported."),
|
|
||||||
_ => r.Skip
|
|
||||||
});
|
|
||||||
|
|
||||||
if (format is null && source is not null)
|
|
||||||
format = ParseImageFormatFromSource(source);
|
|
||||||
|
|
||||||
return new Image
|
|
||||||
{
|
|
||||||
Format = format,
|
|
||||||
Source = source,
|
|
||||||
TransparentColor = transparentColor,
|
|
||||||
Width = width,
|
|
||||||
Height = height,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static ImageFormat ParseImageFormatFromSource(string source)
|
|
||||||
{
|
|
||||||
var extension = Path.GetExtension(source).ToLowerInvariant();
|
|
||||||
return extension switch
|
|
||||||
{
|
|
||||||
".png" => ImageFormat.Png,
|
|
||||||
".gif" => ImageFormat.Gif,
|
|
||||||
".jpg" => ImageFormat.Jpg,
|
|
||||||
".jpeg" => ImageFormat.Jpg,
|
|
||||||
".bmp" => ImageFormat.Bmp,
|
|
||||||
_ => throw new XmlException($"Unsupported image format '{extension}'")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static TileOffset ReadTileOffset(XmlReader reader)
|
|
||||||
{
|
|
||||||
// Attributes
|
|
||||||
var x = reader.GetOptionalAttributeParseable<float>("x") ?? 0f;
|
|
||||||
var y = reader.GetOptionalAttributeParseable<float>("y") ?? 0f;
|
|
||||||
|
|
||||||
reader.ReadStartElement("tileoffset");
|
|
||||||
return new TileOffset { X = x, Y = y };
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static Grid ReadGrid(XmlReader reader)
|
|
||||||
{
|
|
||||||
// Attributes
|
|
||||||
var orientation = reader.GetOptionalAttributeEnum<GridOrientation>("orientation", s => s switch
|
|
||||||
{
|
|
||||||
"orthogonal" => GridOrientation.Orthogonal,
|
|
||||||
"isometric" => GridOrientation.Isometric,
|
|
||||||
_ => throw new InvalidOperationException($"Unknown orientation '{s}'")
|
|
||||||
}) ?? GridOrientation.Orthogonal;
|
|
||||||
var width = reader.GetRequiredAttributeParseable<uint>("width");
|
|
||||||
var height = reader.GetRequiredAttributeParseable<uint>("height");
|
|
||||||
|
|
||||||
reader.ReadStartElement("grid");
|
|
||||||
return new Grid { Orientation = orientation, Width = width, Height = height };
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static Transformations ReadTransformations(XmlReader reader)
|
|
||||||
{
|
|
||||||
// Attributes
|
|
||||||
var hFlip = (reader.GetOptionalAttributeParseable<uint>("hflip") ?? 0) == 1;
|
|
||||||
var vFlip = (reader.GetOptionalAttributeParseable<uint>("vflip") ?? 0) == 1;
|
|
||||||
var rotate = (reader.GetOptionalAttributeParseable<uint>("rotate") ?? 0) == 1;
|
|
||||||
var preferUntransformed = (reader.GetOptionalAttributeParseable<uint>("preferuntransformed") ?? 0) == 1;
|
|
||||||
|
|
||||||
reader.ReadStartElement("transformations");
|
|
||||||
return new Transformations { HFlip = hFlip, VFlip = vFlip, Rotate = rotate, PreferUntransformed = preferUntransformed };
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static Tile ReadTile(
|
|
||||||
XmlReader reader,
|
|
||||||
Func<string, Template> externalTemplateResolver,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
|
||||||
// Attributes
|
|
||||||
var id = reader.GetRequiredAttributeParseable<uint>("id");
|
|
||||||
var type = reader.GetOptionalAttribute("type") ?? "";
|
|
||||||
var probability = reader.GetOptionalAttributeParseable<float>("probability") ?? 0f;
|
|
||||||
var x = reader.GetOptionalAttributeParseable<uint>("x") ?? 0;
|
|
||||||
var y = reader.GetOptionalAttributeParseable<uint>("y") ?? 0;
|
|
||||||
var width = reader.GetOptionalAttributeParseable<uint>("width");
|
|
||||||
var height = reader.GetOptionalAttributeParseable<uint>("height");
|
|
||||||
|
|
||||||
// Elements
|
|
||||||
Dictionary<string, IProperty>? properties = null;
|
|
||||||
Image? image = null;
|
|
||||||
ObjectLayer? objectLayer = null;
|
|
||||||
List<Frame>? animation = null;
|
|
||||||
|
|
||||||
reader.ProcessChildren("tile", (r, elementName) => elementName switch
|
|
||||||
{
|
|
||||||
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"),
|
|
||||||
"image" => () => Helpers.SetAtMostOnce(ref image, ReadImage(r), "Image"),
|
|
||||||
"objectgroup" => () => Helpers.SetAtMostOnce(ref objectLayer, ReadObjectLayer(r, externalTemplateResolver, customTypeDefinitions), "ObjectLayer"),
|
|
||||||
"animation" => () => Helpers.SetAtMostOnce(ref animation, r.ReadList<Frame>("animation", "frame", (ar) =>
|
|
||||||
{
|
|
||||||
var tileID = ar.GetRequiredAttributeParseable<uint>("tileid");
|
|
||||||
var duration = ar.GetRequiredAttributeParseable<uint>("duration");
|
|
||||||
return new Frame { TileID = tileID, Duration = duration };
|
|
||||||
}), "Animation"),
|
|
||||||
_ => r.Skip
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Tile
|
|
||||||
{
|
|
||||||
ID = id,
|
|
||||||
Type = type,
|
|
||||||
Probability = probability,
|
|
||||||
X = x,
|
|
||||||
Y = y,
|
|
||||||
Width = width ?? image?.Width ?? 0,
|
|
||||||
Height = height ?? image?.Height ?? 0,
|
|
||||||
Properties = properties,
|
|
||||||
Image = image,
|
|
||||||
ObjectLayer = objectLayer,
|
|
||||||
Animation = animation
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static List<Wangset> ReadWangsets(
|
|
||||||
XmlReader reader,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions) =>
|
|
||||||
reader.ReadList<Wangset>("wangsets", "wangset", r => ReadWangset(r, customTypeDefinitions));
|
|
||||||
|
|
||||||
internal static Wangset ReadWangset(
|
|
||||||
XmlReader reader,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
|
||||||
// Attributes
|
|
||||||
var name = reader.GetRequiredAttribute("name");
|
|
||||||
var @class = reader.GetOptionalAttribute("class") ?? "";
|
|
||||||
var tile = reader.GetRequiredAttributeParseable<int>("tile");
|
|
||||||
|
|
||||||
// Elements
|
|
||||||
Dictionary<string, IProperty>? properties = null;
|
|
||||||
List<WangColor> wangColors = [];
|
|
||||||
List<WangTile> wangTiles = [];
|
|
||||||
|
|
||||||
reader.ProcessChildren("wangset", (r, elementName) => elementName switch
|
|
||||||
{
|
|
||||||
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"),
|
|
||||||
"wangcolor" => () => wangColors.Add(ReadWangColor(r, customTypeDefinitions)),
|
|
||||||
"wangtile" => () => wangTiles.Add(ReadWangTile(r)),
|
|
||||||
_ => r.Skip
|
|
||||||
});
|
|
||||||
|
|
||||||
if (wangColors.Count > 254)
|
|
||||||
throw new ArgumentException("Wangset can have at most 254 Wang colors.");
|
|
||||||
|
|
||||||
return new Wangset
|
|
||||||
{
|
|
||||||
Name = name,
|
|
||||||
Class = @class,
|
|
||||||
Tile = tile,
|
|
||||||
Properties = properties,
|
|
||||||
WangColors = wangColors,
|
|
||||||
WangTiles = wangTiles
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static WangColor ReadWangColor(
|
|
||||||
XmlReader reader,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
|
||||||
// Attributes
|
|
||||||
var name = reader.GetRequiredAttribute("name");
|
|
||||||
var @class = reader.GetOptionalAttribute("class") ?? "";
|
|
||||||
var color = reader.GetRequiredAttributeParseable<Color>("color");
|
|
||||||
var tile = reader.GetRequiredAttributeParseable<int>("tile");
|
|
||||||
var probability = reader.GetOptionalAttributeParseable<float>("probability") ?? 0f;
|
|
||||||
|
|
||||||
// Elements
|
|
||||||
Dictionary<string, IProperty>? properties = null;
|
|
||||||
|
|
||||||
reader.ProcessChildren("wangcolor", (r, elementName) => elementName switch
|
|
||||||
{
|
|
||||||
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"),
|
|
||||||
_ => r.Skip
|
|
||||||
});
|
|
||||||
|
|
||||||
return new WangColor
|
|
||||||
{
|
|
||||||
Name = name,
|
|
||||||
Class = @class,
|
|
||||||
Color = color,
|
|
||||||
Tile = tile,
|
|
||||||
Probability = probability,
|
|
||||||
Properties = properties
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static WangTile ReadWangTile(XmlReader reader)
|
|
||||||
{
|
|
||||||
// Attributes
|
|
||||||
var tileID = reader.GetRequiredAttributeParseable<uint>("tileid");
|
|
||||||
var wangID = reader.GetRequiredAttributeParseable<byte[]>("wangid", s =>
|
|
||||||
{
|
|
||||||
// Comma-separated list of indices (0-254)
|
|
||||||
var indices = s.Split(',').Select(i => byte.Parse(i, CultureInfo.InvariantCulture)).ToArray();
|
|
||||||
if (indices.Length > 8)
|
|
||||||
throw new ArgumentException("Wang ID can have at most 8 indices.");
|
|
||||||
return indices;
|
|
||||||
});
|
|
||||||
|
|
||||||
reader.ReadStartElement("wangtile");
|
|
||||||
|
|
||||||
return new WangTile
|
|
||||||
{
|
|
||||||
TileID = tileID,
|
|
||||||
WangID = wangID
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using DotTiled.Model;
|
using DotTiled.Model;
|
||||||
|
|
||||||
|
@ -8,72 +7,20 @@ namespace DotTiled.Serialization.Tmx;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A map reader for the Tiled XML format.
|
/// A map reader for the Tiled XML format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TmxMapReader : IMapReader
|
public class TmxMapReader : TmxReaderBase, IMapReader
|
||||||
{
|
{
|
||||||
// External resolvers
|
|
||||||
private readonly Func<string, Tileset> _externalTilesetResolver;
|
|
||||||
private readonly Func<string, Template> _externalTemplateResolver;
|
|
||||||
|
|
||||||
private readonly XmlReader _reader;
|
|
||||||
private bool disposedValue;
|
|
||||||
|
|
||||||
private readonly IReadOnlyCollection<CustomTypeDefinition> _customTypeDefinitions;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs a new <see cref="TmxMapReader"/>.
|
/// Constructs a new <see cref="TmxMapReader"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="reader">An XML reader for reading a Tiled map in the Tiled XML format.</param>
|
/// <inheritdoc />
|
||||||
/// <param name="externalTilesetResolver">A function that resolves external tilesets given their source.</param>
|
|
||||||
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
|
|
||||||
/// <param name="customTypeDefinitions">A collection of custom type definitions that can be used to resolve custom types when encountering <see cref="ClassProperty"/>.</param>
|
|
||||||
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
|
|
||||||
public TmxMapReader(
|
public TmxMapReader(
|
||||||
XmlReader reader,
|
XmlReader reader,
|
||||||
Func<string, Tileset> externalTilesetResolver,
|
Func<string, Tileset> externalTilesetResolver,
|
||||||
Func<string, Template> externalTemplateResolver,
|
Func<string, Template> externalTemplateResolver,
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||||
{
|
reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||||
_reader = reader ?? throw new ArgumentNullException(nameof(reader));
|
{ }
|
||||||
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
|
||||||
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
|
||||||
_customTypeDefinitions = customTypeDefinitions ?? throw new ArgumentNullException(nameof(customTypeDefinitions));
|
|
||||||
|
|
||||||
// Prepare reader
|
|
||||||
_ = _reader.MoveToContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Map ReadMap() => Tmx.ReadMap(_reader, _externalTilesetResolver, _externalTemplateResolver, _customTypeDefinitions);
|
public new Map ReadMap() => base.ReadMap();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!disposedValue)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
// TODO: dispose managed state (managed objects)
|
|
||||||
_reader.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
|
||||||
// TODO: set large fields to null
|
|
||||||
disposedValue = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
|
||||||
// ~TmxTiledMapReader()
|
|
||||||
// {
|
|
||||||
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
||||||
// Dispose(disposing: false);
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
||||||
Dispose(disposing: true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,26 @@
|
||||||
using System.Xml;
|
|
||||||
using DotTiled.Model;
|
using DotTiled.Model;
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmx;
|
namespace DotTiled.Serialization.Tmx;
|
||||||
|
|
||||||
internal partial class Tmx
|
public abstract partial class TmxReaderBase
|
||||||
{
|
{
|
||||||
internal static Chunk ReadChunk(XmlReader reader, DataEncoding? encoding, DataCompression? compression)
|
internal Chunk ReadChunk(DataEncoding? encoding, DataCompression? compression)
|
||||||
{
|
{
|
||||||
var x = reader.GetRequiredAttributeParseable<int>("x");
|
var x = _reader.GetRequiredAttributeParseable<int>("x");
|
||||||
var y = reader.GetRequiredAttributeParseable<int>("y");
|
var y = _reader.GetRequiredAttributeParseable<int>("y");
|
||||||
var width = reader.GetRequiredAttributeParseable<uint>("width");
|
var width = _reader.GetRequiredAttributeParseable<uint>("width");
|
||||||
var height = reader.GetRequiredAttributeParseable<uint>("height");
|
var height = _reader.GetRequiredAttributeParseable<uint>("height");
|
||||||
|
|
||||||
var usesTileChildrenInsteadOfRawData = encoding is null;
|
var usesTileChildrenInsteadOfRawData = encoding is null;
|
||||||
if (usesTileChildrenInsteadOfRawData)
|
if (usesTileChildrenInsteadOfRawData)
|
||||||
{
|
{
|
||||||
var globalTileIDsWithFlippingFlags = ReadTileChildrenInWrapper("chunk", reader);
|
var globalTileIDsWithFlippingFlags = ReadTileChildrenInWrapper("chunk", _reader);
|
||||||
var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(globalTileIDsWithFlippingFlags);
|
var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(globalTileIDsWithFlippingFlags);
|
||||||
return new Chunk { X = x, Y = y, Width = width, Height = height, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags };
|
return new Chunk { X = x, Y = y, Width = width, Height = height, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags };
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var globalTileIDsWithFlippingFlags = ReadRawData(reader, encoding!.Value, compression);
|
var globalTileIDsWithFlippingFlags = ReadRawData(_reader, encoding!.Value, compression);
|
||||||
var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(globalTileIDsWithFlippingFlags);
|
var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(globalTileIDsWithFlippingFlags);
|
||||||
return new Chunk { X = x, Y = y, Width = width, Height = height, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags };
|
return new Chunk { X = x, Y = y, Width = width, Height = height, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags };
|
||||||
}
|
}
|
|
@ -8,17 +8,17 @@ using DotTiled.Model;
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmx;
|
namespace DotTiled.Serialization.Tmx;
|
||||||
|
|
||||||
internal partial class Tmx
|
public abstract partial class TmxReaderBase
|
||||||
{
|
{
|
||||||
internal static Data ReadData(XmlReader reader, bool usesChunks)
|
internal Data ReadData(bool usesChunks)
|
||||||
{
|
{
|
||||||
var encoding = reader.GetOptionalAttributeEnum<DataEncoding>("encoding", e => e switch
|
var encoding = _reader.GetOptionalAttributeEnum<DataEncoding>("encoding", e => e switch
|
||||||
{
|
{
|
||||||
"csv" => DataEncoding.Csv,
|
"csv" => DataEncoding.Csv,
|
||||||
"base64" => DataEncoding.Base64,
|
"base64" => DataEncoding.Base64,
|
||||||
_ => throw new XmlException("Invalid encoding")
|
_ => throw new XmlException("Invalid encoding")
|
||||||
});
|
});
|
||||||
var compression = reader.GetOptionalAttributeEnum<DataCompression>("compression", c => c switch
|
var compression = _reader.GetOptionalAttributeEnum<DataCompression>("compression", c => c switch
|
||||||
{
|
{
|
||||||
"gzip" => DataCompression.GZip,
|
"gzip" => DataCompression.GZip,
|
||||||
"zlib" => DataCompression.ZLib,
|
"zlib" => DataCompression.ZLib,
|
||||||
|
@ -28,8 +28,8 @@ internal partial class Tmx
|
||||||
|
|
||||||
if (usesChunks)
|
if (usesChunks)
|
||||||
{
|
{
|
||||||
var chunks = reader
|
var chunks = _reader
|
||||||
.ReadList("data", "chunk", (r) => ReadChunk(r, encoding, compression))
|
.ReadList("data", "chunk", (r) => ReadChunk(encoding, compression))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = null, Chunks = chunks };
|
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = null, Chunks = chunks };
|
||||||
}
|
}
|
||||||
|
@ -37,12 +37,12 @@ internal partial class Tmx
|
||||||
var usesTileChildrenInsteadOfRawData = encoding is null && compression is null;
|
var usesTileChildrenInsteadOfRawData = encoding is null && compression is null;
|
||||||
if (usesTileChildrenInsteadOfRawData)
|
if (usesTileChildrenInsteadOfRawData)
|
||||||
{
|
{
|
||||||
var tileChildrenGlobalTileIDsWithFlippingFlags = ReadTileChildrenInWrapper("data", reader);
|
var tileChildrenGlobalTileIDsWithFlippingFlags = ReadTileChildrenInWrapper("data", _reader);
|
||||||
var (tileChildrenGlobalTileIDs, tileChildrenFlippingFlags) = ReadAndClearFlippingFlagsFromGIDs(tileChildrenGlobalTileIDsWithFlippingFlags);
|
var (tileChildrenGlobalTileIDs, tileChildrenFlippingFlags) = ReadAndClearFlippingFlagsFromGIDs(tileChildrenGlobalTileIDsWithFlippingFlags);
|
||||||
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = tileChildrenGlobalTileIDs, FlippingFlags = tileChildrenFlippingFlags, Chunks = null };
|
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = tileChildrenGlobalTileIDs, FlippingFlags = tileChildrenFlippingFlags, Chunks = null };
|
||||||
}
|
}
|
||||||
|
|
||||||
var rawDataGlobalTileIDsWithFlippingFlags = ReadRawData(reader, encoding!.Value, compression);
|
var rawDataGlobalTileIDsWithFlippingFlags = ReadRawData(_reader, encoding!.Value, compression);
|
||||||
var (rawDataGlobalTileIDs, rawDataFlippingFlags) = ReadAndClearFlippingFlagsFromGIDs(rawDataGlobalTileIDsWithFlippingFlags);
|
var (rawDataGlobalTileIDs, rawDataFlippingFlags) = ReadAndClearFlippingFlagsFromGIDs(rawDataGlobalTileIDsWithFlippingFlags);
|
||||||
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = rawDataGlobalTileIDs, FlippingFlags = rawDataFlippingFlags, Chunks = null };
|
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = rawDataGlobalTileIDs, FlippingFlags = rawDataFlippingFlags, Chunks = null };
|
||||||
}
|
}
|
104
src/DotTiled/Serialization/Tmx/TmxReaderBase.Map.cs
Normal file
104
src/DotTiled/Serialization/Tmx/TmxReaderBase.Map.cs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using DotTiled.Model;
|
||||||
|
|
||||||
|
namespace DotTiled.Serialization.Tmx;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for Tiled XML format readers.
|
||||||
|
/// </summary>
|
||||||
|
public abstract partial class TmxReaderBase
|
||||||
|
{
|
||||||
|
internal Map ReadMap()
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var version = _reader.GetRequiredAttribute("version");
|
||||||
|
var tiledVersion = _reader.GetRequiredAttribute("tiledversion");
|
||||||
|
var @class = _reader.GetOptionalAttribute("class") ?? "";
|
||||||
|
var orientation = _reader.GetRequiredAttributeEnum<MapOrientation>("orientation", s => s switch
|
||||||
|
{
|
||||||
|
"orthogonal" => MapOrientation.Orthogonal,
|
||||||
|
"isometric" => MapOrientation.Isometric,
|
||||||
|
"staggered" => MapOrientation.Staggered,
|
||||||
|
"hexagonal" => MapOrientation.Hexagonal,
|
||||||
|
_ => throw new InvalidOperationException($"Unknown orientation '{s}'")
|
||||||
|
});
|
||||||
|
var renderOrder = _reader.GetOptionalAttributeEnum<RenderOrder>("renderorder", s => s switch
|
||||||
|
{
|
||||||
|
"right-down" => RenderOrder.RightDown,
|
||||||
|
"right-up" => RenderOrder.RightUp,
|
||||||
|
"left-down" => RenderOrder.LeftDown,
|
||||||
|
"left-up" => RenderOrder.LeftUp,
|
||||||
|
_ => throw new InvalidOperationException($"Unknown render order '{s}'")
|
||||||
|
}) ?? RenderOrder.RightDown;
|
||||||
|
var compressionLevel = _reader.GetOptionalAttributeParseable<int>("compressionlevel") ?? -1;
|
||||||
|
var width = _reader.GetRequiredAttributeParseable<uint>("width");
|
||||||
|
var height = _reader.GetRequiredAttributeParseable<uint>("height");
|
||||||
|
var tileWidth = _reader.GetRequiredAttributeParseable<uint>("tilewidth");
|
||||||
|
var tileHeight = _reader.GetRequiredAttributeParseable<uint>("tileheight");
|
||||||
|
var hexSideLength = _reader.GetOptionalAttributeParseable<uint>("hexsidelength");
|
||||||
|
var staggerAxis = _reader.GetOptionalAttributeEnum<StaggerAxis>("staggeraxis", s => s switch
|
||||||
|
{
|
||||||
|
"x" => StaggerAxis.X,
|
||||||
|
"y" => StaggerAxis.Y,
|
||||||
|
_ => throw new InvalidOperationException($"Unknown stagger axis '{s}'")
|
||||||
|
});
|
||||||
|
var staggerIndex = _reader.GetOptionalAttributeEnum<StaggerIndex>("staggerindex", s => s switch
|
||||||
|
{
|
||||||
|
"odd" => StaggerIndex.Odd,
|
||||||
|
"even" => StaggerIndex.Even,
|
||||||
|
_ => throw new InvalidOperationException($"Unknown stagger index '{s}'")
|
||||||
|
});
|
||||||
|
var parallaxOriginX = _reader.GetOptionalAttributeParseable<float>("parallaxoriginx") ?? 0.0f;
|
||||||
|
var parallaxOriginY = _reader.GetOptionalAttributeParseable<float>("parallaxoriginy") ?? 0.0f;
|
||||||
|
var backgroundColor = _reader.GetOptionalAttributeClass<Color>("backgroundcolor") ?? Color.Parse("#00000000", CultureInfo.InvariantCulture);
|
||||||
|
var nextLayerID = _reader.GetRequiredAttributeParseable<uint>("nextlayerid");
|
||||||
|
var nextObjectID = _reader.GetRequiredAttributeParseable<uint>("nextobjectid");
|
||||||
|
var infinite = (_reader.GetOptionalAttributeParseable<uint>("infinite") ?? 0) == 1;
|
||||||
|
|
||||||
|
// At most one of
|
||||||
|
List<IProperty>? properties = null;
|
||||||
|
|
||||||
|
// Any number of
|
||||||
|
List<BaseLayer> layers = [];
|
||||||
|
List<Tileset> tilesets = [];
|
||||||
|
|
||||||
|
_reader.ProcessChildren("map", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(), "Properties"),
|
||||||
|
"tileset" => () => tilesets.Add(ReadTileset()),
|
||||||
|
"layer" => () => layers.Add(ReadTileLayer(infinite)),
|
||||||
|
"objectgroup" => () => layers.Add(ReadObjectLayer()),
|
||||||
|
"imagelayer" => () => layers.Add(ReadImageLayer()),
|
||||||
|
"group" => () => layers.Add(ReadGroup()),
|
||||||
|
_ => r.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Map
|
||||||
|
{
|
||||||
|
Version = version,
|
||||||
|
TiledVersion = tiledVersion,
|
||||||
|
Class = @class,
|
||||||
|
Orientation = orientation,
|
||||||
|
RenderOrder = renderOrder,
|
||||||
|
CompressionLevel = compressionLevel,
|
||||||
|
Width = width,
|
||||||
|
Height = height,
|
||||||
|
TileWidth = tileWidth,
|
||||||
|
TileHeight = tileHeight,
|
||||||
|
HexSideLength = hexSideLength,
|
||||||
|
StaggerAxis = staggerAxis,
|
||||||
|
StaggerIndex = staggerIndex,
|
||||||
|
ParallaxOriginX = parallaxOriginX,
|
||||||
|
ParallaxOriginY = parallaxOriginY,
|
||||||
|
BackgroundColor = backgroundColor,
|
||||||
|
NextLayerID = nextLayerID,
|
||||||
|
NextObjectID = nextObjectID,
|
||||||
|
Infinite = infinite,
|
||||||
|
Properties = properties ?? [],
|
||||||
|
Tilesets = tilesets,
|
||||||
|
Layers = layers
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,35 +3,31 @@ using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Xml;
|
|
||||||
using DotTiled.Model;
|
using DotTiled.Model;
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmx;
|
namespace DotTiled.Serialization.Tmx;
|
||||||
|
|
||||||
internal partial class Tmx
|
public abstract partial class TmxReaderBase
|
||||||
{
|
{
|
||||||
internal static ObjectLayer ReadObjectLayer(
|
internal ObjectLayer ReadObjectLayer()
|
||||||
XmlReader reader,
|
|
||||||
Func<string, Template> externalTemplateResolver,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
{
|
||||||
// Attributes
|
// Attributes
|
||||||
var id = reader.GetRequiredAttributeParseable<uint>("id");
|
var id = _reader.GetRequiredAttributeParseable<uint>("id");
|
||||||
var name = reader.GetOptionalAttribute("name") ?? "";
|
var name = _reader.GetOptionalAttribute("name") ?? "";
|
||||||
var @class = reader.GetOptionalAttribute("class") ?? "";
|
var @class = _reader.GetOptionalAttribute("class") ?? "";
|
||||||
var x = reader.GetOptionalAttributeParseable<uint>("x") ?? 0;
|
var x = _reader.GetOptionalAttributeParseable<uint>("x") ?? 0;
|
||||||
var y = reader.GetOptionalAttributeParseable<uint>("y") ?? 0;
|
var y = _reader.GetOptionalAttributeParseable<uint>("y") ?? 0;
|
||||||
var width = reader.GetOptionalAttributeParseable<uint>("width");
|
var width = _reader.GetOptionalAttributeParseable<uint>("width");
|
||||||
var height = reader.GetOptionalAttributeParseable<uint>("height");
|
var height = _reader.GetOptionalAttributeParseable<uint>("height");
|
||||||
var opacity = reader.GetOptionalAttributeParseable<float>("opacity") ?? 1.0f;
|
var opacity = _reader.GetOptionalAttributeParseable<float>("opacity") ?? 1.0f;
|
||||||
var visible = reader.GetOptionalAttributeParseable<bool>("visible") ?? true;
|
var visible = _reader.GetOptionalAttributeParseable<bool>("visible") ?? true;
|
||||||
var tintColor = reader.GetOptionalAttributeClass<Color>("tintcolor");
|
var tintColor = _reader.GetOptionalAttributeClass<Color>("tintcolor");
|
||||||
var offsetX = reader.GetOptionalAttributeParseable<float>("offsetx") ?? 0.0f;
|
var offsetX = _reader.GetOptionalAttributeParseable<float>("offsetx") ?? 0.0f;
|
||||||
var offsetY = reader.GetOptionalAttributeParseable<float>("offsety") ?? 0.0f;
|
var offsetY = _reader.GetOptionalAttributeParseable<float>("offsety") ?? 0.0f;
|
||||||
var parallaxX = reader.GetOptionalAttributeParseable<float>("parallaxx") ?? 1.0f;
|
var parallaxX = _reader.GetOptionalAttributeParseable<float>("parallaxx") ?? 1.0f;
|
||||||
var parallaxY = reader.GetOptionalAttributeParseable<float>("parallaxy") ?? 1.0f;
|
var parallaxY = _reader.GetOptionalAttributeParseable<float>("parallaxy") ?? 1.0f;
|
||||||
var color = reader.GetOptionalAttributeClass<Color>("color");
|
var color = _reader.GetOptionalAttributeClass<Color>("color");
|
||||||
var drawOrder = reader.GetOptionalAttributeEnum<DrawOrder>("draworder", s => s switch
|
var drawOrder = _reader.GetOptionalAttributeEnum<DrawOrder>("draworder", s => s switch
|
||||||
{
|
{
|
||||||
"topdown" => DrawOrder.TopDown,
|
"topdown" => DrawOrder.TopDown,
|
||||||
"index" => DrawOrder.Index,
|
"index" => DrawOrder.Index,
|
||||||
|
@ -39,13 +35,13 @@ internal partial class Tmx
|
||||||
}) ?? DrawOrder.TopDown;
|
}) ?? DrawOrder.TopDown;
|
||||||
|
|
||||||
// Elements
|
// Elements
|
||||||
Dictionary<string, IProperty>? properties = null;
|
List<IProperty>? properties = null;
|
||||||
List<Model.Object> objects = [];
|
List<Model.Object> objects = [];
|
||||||
|
|
||||||
reader.ProcessChildren("objectgroup", (r, elementName) => elementName switch
|
_reader.ProcessChildren("objectgroup", (r, elementName) => elementName switch
|
||||||
{
|
{
|
||||||
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"),
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(), "Properties"),
|
||||||
"object" => () => objects.Add(ReadObject(r, externalTemplateResolver, customTypeDefinitions)),
|
"object" => () => objects.Add(ReadObject()),
|
||||||
_ => r.Skip
|
_ => r.Skip
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -66,22 +62,19 @@ internal partial class Tmx
|
||||||
ParallaxX = parallaxX,
|
ParallaxX = parallaxX,
|
||||||
ParallaxY = parallaxY,
|
ParallaxY = parallaxY,
|
||||||
Color = color,
|
Color = color,
|
||||||
Properties = properties,
|
Properties = properties ?? [],
|
||||||
DrawOrder = drawOrder,
|
DrawOrder = drawOrder,
|
||||||
Objects = objects
|
Objects = objects
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Model.Object ReadObject(
|
internal Model.Object ReadObject()
|
||||||
XmlReader reader,
|
|
||||||
Func<string, Template> externalTemplateResolver,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
{
|
||||||
// Attributes
|
// Attributes
|
||||||
var template = reader.GetOptionalAttribute("template");
|
var template = _reader.GetOptionalAttribute("template");
|
||||||
Model.Object? obj = null;
|
Model.Object? obj = null;
|
||||||
if (template is not null)
|
if (template is not null)
|
||||||
obj = externalTemplateResolver(template).Object;
|
obj = _externalTemplateResolver(template).Object;
|
||||||
|
|
||||||
uint? idDefault = obj?.ID ?? null;
|
uint? idDefault = obj?.ID ?? null;
|
||||||
string nameDefault = obj?.Name ?? "";
|
string nameDefault = obj?.Name ?? "";
|
||||||
|
@ -93,32 +86,32 @@ internal partial class Tmx
|
||||||
float rotationDefault = obj?.Rotation ?? 0f;
|
float rotationDefault = obj?.Rotation ?? 0f;
|
||||||
uint? gidDefault = obj is TileObject tileObj ? tileObj.GID : null;
|
uint? gidDefault = obj is TileObject tileObj ? tileObj.GID : null;
|
||||||
bool visibleDefault = obj?.Visible ?? true;
|
bool visibleDefault = obj?.Visible ?? true;
|
||||||
Dictionary<string, IProperty>? propertiesDefault = obj?.Properties ?? null;
|
List<IProperty>? propertiesDefault = obj?.Properties ?? null;
|
||||||
|
|
||||||
var id = reader.GetOptionalAttributeParseable<uint>("id") ?? idDefault;
|
var id = _reader.GetOptionalAttributeParseable<uint>("id") ?? idDefault;
|
||||||
var name = reader.GetOptionalAttribute("name") ?? nameDefault;
|
var name = _reader.GetOptionalAttribute("name") ?? nameDefault;
|
||||||
var type = reader.GetOptionalAttribute("type") ?? typeDefault;
|
var type = _reader.GetOptionalAttribute("type") ?? typeDefault;
|
||||||
var x = reader.GetOptionalAttributeParseable<float>("x") ?? xDefault;
|
var x = _reader.GetOptionalAttributeParseable<float>("x") ?? xDefault;
|
||||||
var y = reader.GetOptionalAttributeParseable<float>("y") ?? yDefault;
|
var y = _reader.GetOptionalAttributeParseable<float>("y") ?? yDefault;
|
||||||
var width = reader.GetOptionalAttributeParseable<float>("width") ?? widthDefault;
|
var width = _reader.GetOptionalAttributeParseable<float>("width") ?? widthDefault;
|
||||||
var height = reader.GetOptionalAttributeParseable<float>("height") ?? heightDefault;
|
var height = _reader.GetOptionalAttributeParseable<float>("height") ?? heightDefault;
|
||||||
var rotation = reader.GetOptionalAttributeParseable<float>("rotation") ?? rotationDefault;
|
var rotation = _reader.GetOptionalAttributeParseable<float>("rotation") ?? rotationDefault;
|
||||||
var gid = reader.GetOptionalAttributeParseable<uint>("gid") ?? gidDefault;
|
var gid = _reader.GetOptionalAttributeParseable<uint>("gid") ?? gidDefault;
|
||||||
var visible = reader.GetOptionalAttributeParseable<bool>("visible") ?? visibleDefault;
|
var visible = _reader.GetOptionalAttributeParseable<bool>("visible") ?? visibleDefault;
|
||||||
|
|
||||||
// Elements
|
// Elements
|
||||||
Model.Object? foundObject = null;
|
Model.Object? foundObject = null;
|
||||||
int propertiesCounter = 0;
|
int propertiesCounter = 0;
|
||||||
Dictionary<string, IProperty>? properties = propertiesDefault;
|
List<IProperty>? properties = propertiesDefault;
|
||||||
|
|
||||||
reader.ProcessChildren("object", (r, elementName) => elementName switch
|
_reader.ProcessChildren("object", (r, elementName) => elementName switch
|
||||||
{
|
{
|
||||||
"properties" => () => Helpers.SetAtMostOnceUsingCounter(ref properties, Helpers.MergeProperties(properties, ReadProperties(r, customTypeDefinitions)), "Properties", ref propertiesCounter),
|
"properties" => () => Helpers.SetAtMostOnceUsingCounter(ref properties, Helpers.MergeProperties(properties, ReadProperties()).ToList(), "Properties", ref propertiesCounter),
|
||||||
"ellipse" => () => Helpers.SetAtMostOnce(ref foundObject, ReadEllipseObject(r), "Object marker"),
|
"ellipse" => () => Helpers.SetAtMostOnce(ref foundObject, ReadEllipseObject(), "Object marker"),
|
||||||
"point" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPointObject(r), "Object marker"),
|
"point" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPointObject(), "Object marker"),
|
||||||
"polygon" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPolygonObject(r), "Object marker"),
|
"polygon" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPolygonObject(), "Object marker"),
|
||||||
"polyline" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPolylineObject(r), "Object marker"),
|
"polyline" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPolylineObject(), "Object marker"),
|
||||||
"text" => () => Helpers.SetAtMostOnce(ref foundObject, ReadTextObject(r), "Object marker"),
|
"text" => () => Helpers.SetAtMostOnce(ref foundObject, ReadTextObject(), "Object marker"),
|
||||||
_ => throw new InvalidOperationException($"Unknown object marker '{elementName}'")
|
_ => throw new InvalidOperationException($"Unknown object marker '{elementName}'")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -139,7 +132,7 @@ internal partial class Tmx
|
||||||
foundObject.Height = height;
|
foundObject.Height = height;
|
||||||
foundObject.Rotation = rotation;
|
foundObject.Rotation = rotation;
|
||||||
foundObject.Visible = visible;
|
foundObject.Visible = visible;
|
||||||
foundObject.Properties = properties;
|
foundObject.Properties = properties ?? [];
|
||||||
foundObject.Template = template;
|
foundObject.Template = template;
|
||||||
|
|
||||||
return OverrideObject(obj, foundObject);
|
return OverrideObject(obj, foundObject);
|
||||||
|
@ -161,7 +154,7 @@ internal partial class Tmx
|
||||||
obj.Height = foundObject.Height;
|
obj.Height = foundObject.Height;
|
||||||
obj.Rotation = foundObject.Rotation;
|
obj.Rotation = foundObject.Rotation;
|
||||||
obj.Visible = foundObject.Visible;
|
obj.Visible = foundObject.Visible;
|
||||||
obj.Properties = Helpers.MergeProperties(obj.Properties, foundObject.Properties);
|
obj.Properties = Helpers.MergeProperties(obj.Properties, foundObject.Properties).ToList();
|
||||||
obj.Template = foundObject.Template;
|
obj.Template = foundObject.Template;
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
@ -169,26 +162,26 @@ internal partial class Tmx
|
||||||
return OverrideObject((dynamic)obj, (dynamic)foundObject);
|
return OverrideObject((dynamic)obj, (dynamic)foundObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static EllipseObject ReadEllipseObject(XmlReader reader)
|
internal EllipseObject ReadEllipseObject()
|
||||||
{
|
{
|
||||||
reader.Skip();
|
_reader.Skip();
|
||||||
return new EllipseObject { };
|
return new EllipseObject { };
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static EllipseObject OverrideObject(EllipseObject obj, EllipseObject _) => obj;
|
internal static EllipseObject OverrideObject(EllipseObject obj, EllipseObject _) => obj;
|
||||||
|
|
||||||
internal static PointObject ReadPointObject(XmlReader reader)
|
internal PointObject ReadPointObject()
|
||||||
{
|
{
|
||||||
reader.Skip();
|
_reader.Skip();
|
||||||
return new PointObject { };
|
return new PointObject { };
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static PointObject OverrideObject(PointObject obj, PointObject _) => obj;
|
internal static PointObject OverrideObject(PointObject obj, PointObject _) => obj;
|
||||||
|
|
||||||
internal static PolygonObject ReadPolygonObject(XmlReader reader)
|
internal PolygonObject ReadPolygonObject()
|
||||||
{
|
{
|
||||||
// Attributes
|
// Attributes
|
||||||
var points = reader.GetRequiredAttributeParseable<List<Vector2>>("points", s =>
|
var points = _reader.GetRequiredAttributeParseable<List<Vector2>>("points", s =>
|
||||||
{
|
{
|
||||||
// Takes on format "x1,y1 x2,y2 x3,y3 ..."
|
// Takes on format "x1,y1 x2,y2 x3,y3 ..."
|
||||||
var coords = s.Split(' ');
|
var coords = s.Split(' ');
|
||||||
|
@ -199,7 +192,7 @@ internal partial class Tmx
|
||||||
}).ToList();
|
}).ToList();
|
||||||
});
|
});
|
||||||
|
|
||||||
reader.ReadStartElement("polygon");
|
_reader.ReadStartElement("polygon");
|
||||||
return new PolygonObject { Points = points };
|
return new PolygonObject { Points = points };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,10 +202,10 @@ internal partial class Tmx
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static PolylineObject ReadPolylineObject(XmlReader reader)
|
internal PolylineObject ReadPolylineObject()
|
||||||
{
|
{
|
||||||
// Attributes
|
// Attributes
|
||||||
var points = reader.GetRequiredAttributeParseable<List<Vector2>>("points", s =>
|
var points = _reader.GetRequiredAttributeParseable<List<Vector2>>("points", s =>
|
||||||
{
|
{
|
||||||
// Takes on format "x1,y1 x2,y2 x3,y3 ..."
|
// Takes on format "x1,y1 x2,y2 x3,y3 ..."
|
||||||
var coords = s.Split(' ');
|
var coords = s.Split(' ');
|
||||||
|
@ -223,7 +216,7 @@ internal partial class Tmx
|
||||||
}).ToList();
|
}).ToList();
|
||||||
});
|
});
|
||||||
|
|
||||||
reader.ReadStartElement("polyline");
|
_reader.ReadStartElement("polyline");
|
||||||
return new PolylineObject { Points = points };
|
return new PolylineObject { Points = points };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,19 +226,19 @@ internal partial class Tmx
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static TextObject ReadTextObject(XmlReader reader)
|
internal TextObject ReadTextObject()
|
||||||
{
|
{
|
||||||
// Attributes
|
// Attributes
|
||||||
var fontFamily = reader.GetOptionalAttribute("fontfamily") ?? "sans-serif";
|
var fontFamily = _reader.GetOptionalAttribute("fontfamily") ?? "sans-serif";
|
||||||
var pixelSize = reader.GetOptionalAttributeParseable<int>("pixelsize") ?? 16;
|
var pixelSize = _reader.GetOptionalAttributeParseable<int>("pixelsize") ?? 16;
|
||||||
var wrap = reader.GetOptionalAttributeParseable<bool>("wrap") ?? false;
|
var wrap = _reader.GetOptionalAttributeParseable<bool>("wrap") ?? false;
|
||||||
var color = reader.GetOptionalAttributeClass<Color>("color") ?? Color.Parse("#000000", CultureInfo.InvariantCulture);
|
var color = _reader.GetOptionalAttributeClass<Color>("color") ?? Color.Parse("#000000", CultureInfo.InvariantCulture);
|
||||||
var bold = reader.GetOptionalAttributeParseable<bool>("bold") ?? false;
|
var bold = _reader.GetOptionalAttributeParseable<bool>("bold") ?? false;
|
||||||
var italic = reader.GetOptionalAttributeParseable<bool>("italic") ?? false;
|
var italic = _reader.GetOptionalAttributeParseable<bool>("italic") ?? false;
|
||||||
var underline = reader.GetOptionalAttributeParseable<bool>("underline") ?? false;
|
var underline = _reader.GetOptionalAttributeParseable<bool>("underline") ?? false;
|
||||||
var strikeout = reader.GetOptionalAttributeParseable<bool>("strikeout") ?? false;
|
var strikeout = _reader.GetOptionalAttributeParseable<bool>("strikeout") ?? false;
|
||||||
var kerning = reader.GetOptionalAttributeParseable<bool>("kerning") ?? true;
|
var kerning = _reader.GetOptionalAttributeParseable<bool>("kerning") ?? true;
|
||||||
var hAlign = reader.GetOptionalAttributeEnum<TextHorizontalAlignment>("halign", s => s switch
|
var hAlign = _reader.GetOptionalAttributeEnum<TextHorizontalAlignment>("halign", s => s switch
|
||||||
{
|
{
|
||||||
"left" => TextHorizontalAlignment.Left,
|
"left" => TextHorizontalAlignment.Left,
|
||||||
"center" => TextHorizontalAlignment.Center,
|
"center" => TextHorizontalAlignment.Center,
|
||||||
|
@ -253,7 +246,7 @@ internal partial class Tmx
|
||||||
"justify" => TextHorizontalAlignment.Justify,
|
"justify" => TextHorizontalAlignment.Justify,
|
||||||
_ => throw new InvalidOperationException($"Unknown horizontal alignment '{s}'")
|
_ => throw new InvalidOperationException($"Unknown horizontal alignment '{s}'")
|
||||||
}) ?? TextHorizontalAlignment.Left;
|
}) ?? TextHorizontalAlignment.Left;
|
||||||
var vAlign = reader.GetOptionalAttributeEnum<TextVerticalAlignment>("valign", s => s switch
|
var vAlign = _reader.GetOptionalAttributeEnum<TextVerticalAlignment>("valign", s => s switch
|
||||||
{
|
{
|
||||||
"top" => TextVerticalAlignment.Top,
|
"top" => TextVerticalAlignment.Top,
|
||||||
"center" => TextVerticalAlignment.Center,
|
"center" => TextVerticalAlignment.Center,
|
||||||
|
@ -262,7 +255,7 @@ internal partial class Tmx
|
||||||
}) ?? TextVerticalAlignment.Top;
|
}) ?? TextVerticalAlignment.Top;
|
||||||
|
|
||||||
// Elements
|
// Elements
|
||||||
var text = reader.ReadElementContentAsString("text", "");
|
var text = _reader.ReadElementContentAsString("text", "");
|
||||||
|
|
||||||
return new TextObject
|
return new TextObject
|
||||||
{
|
{
|
||||||
|
@ -304,11 +297,7 @@ internal partial class Tmx
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Template ReadTemplate(
|
internal Template ReadTemplate()
|
||||||
XmlReader reader,
|
|
||||||
Func<string, Tileset> externalTilesetResolver,
|
|
||||||
Func<string, Template> externalTemplateResolver,
|
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
{
|
||||||
// No attributes
|
// No attributes
|
||||||
|
|
||||||
|
@ -318,10 +307,10 @@ internal partial class Tmx
|
||||||
// Should contain exactly one of
|
// Should contain exactly one of
|
||||||
Model.Object? obj = null;
|
Model.Object? obj = null;
|
||||||
|
|
||||||
reader.ProcessChildren("template", (r, elementName) => elementName switch
|
_reader.ProcessChildren("template", (r, elementName) => elementName switch
|
||||||
{
|
{
|
||||||
"tileset" => () => Helpers.SetAtMostOnce(ref tileset, ReadTileset(r, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions), "Tileset"),
|
"tileset" => () => Helpers.SetAtMostOnce(ref tileset, ReadTileset(), "Tileset"),
|
||||||
"object" => () => Helpers.SetAtMostOnce(ref obj, ReadObject(r, externalTemplateResolver, customTypeDefinitions), "Object"),
|
"object" => () => Helpers.SetAtMostOnce(ref obj, ReadObject(), "Object"),
|
||||||
_ => r.Skip
|
_ => r.Skip
|
||||||
});
|
});
|
||||||
|
|
150
src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs
Normal file
150
src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Xml;
|
||||||
|
using DotTiled.Model;
|
||||||
|
|
||||||
|
namespace DotTiled.Serialization.Tmx;
|
||||||
|
|
||||||
|
public abstract partial class TmxReaderBase
|
||||||
|
{
|
||||||
|
internal List<IProperty> ReadProperties()
|
||||||
|
{
|
||||||
|
if (!_reader.IsStartElement("properties"))
|
||||||
|
return [];
|
||||||
|
|
||||||
|
return _reader.ReadList("properties", "property", (r) =>
|
||||||
|
{
|
||||||
|
var name = r.GetRequiredAttribute("name");
|
||||||
|
var type = r.GetOptionalAttributeEnum<PropertyType>("type", (s) => s switch
|
||||||
|
{
|
||||||
|
"string" => PropertyType.String,
|
||||||
|
"int" => PropertyType.Int,
|
||||||
|
"float" => PropertyType.Float,
|
||||||
|
"bool" => PropertyType.Bool,
|
||||||
|
"color" => PropertyType.Color,
|
||||||
|
"file" => PropertyType.File,
|
||||||
|
"object" => PropertyType.Object,
|
||||||
|
"class" => PropertyType.Class,
|
||||||
|
_ => throw new XmlException("Invalid property type")
|
||||||
|
}) ?? PropertyType.String;
|
||||||
|
var propertyType = r.GetOptionalAttribute("propertytype");
|
||||||
|
if (propertyType is not null)
|
||||||
|
{
|
||||||
|
return ReadPropertyWithCustomType();
|
||||||
|
}
|
||||||
|
|
||||||
|
IProperty property = type switch
|
||||||
|
{
|
||||||
|
PropertyType.String => new StringProperty { Name = name, Value = r.GetRequiredAttribute("value") },
|
||||||
|
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.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"),
|
||||||
|
PropertyType.Enum => throw new XmlException("Enum property must have a property type"),
|
||||||
|
_ => throw new XmlException("Invalid property type")
|
||||||
|
};
|
||||||
|
return property;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
internal IProperty ReadPropertyWithCustomType()
|
||||||
|
{
|
||||||
|
var isClass = _reader.GetOptionalAttribute("type") == "class";
|
||||||
|
if (isClass)
|
||||||
|
{
|
||||||
|
return ReadClassProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ReadEnumProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ClassProperty ReadClassProperty()
|
||||||
|
{
|
||||||
|
var name = _reader.GetRequiredAttribute("name");
|
||||||
|
var propertyType = _reader.GetRequiredAttribute("propertytype");
|
||||||
|
var customTypeDef = _customTypeResolver(propertyType);
|
||||||
|
|
||||||
|
if (customTypeDef is CustomClassDefinition ccd)
|
||||||
|
{
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new XmlException($"Unkonwn custom class definition: {propertyType}");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal EnumProperty ReadEnumProperty()
|
||||||
|
{
|
||||||
|
var name = _reader.GetRequiredAttribute("name");
|
||||||
|
var propertyType = _reader.GetRequiredAttribute("propertytype");
|
||||||
|
var typeInXml = _reader.GetOptionalAttributeEnum<PropertyType>("type", (s) => s switch
|
||||||
|
{
|
||||||
|
"string" => PropertyType.String,
|
||||||
|
"int" => PropertyType.Int,
|
||||||
|
_ => throw new XmlException("Invalid property type")
|
||||||
|
}) ?? 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 (ced.StorageType == CustomEnumStorageType.String)
|
||||||
|
{
|
||||||
|
var value = _reader.GetRequiredAttribute("value");
|
||||||
|
if (value.Contains(',') && !ced.ValueAsFlags)
|
||||||
|
throw new XmlException("Enum value must not contain ',' if not ValueAsFlags is set to true.");
|
||||||
|
|
||||||
|
if (ced.ValueAsFlags)
|
||||||
|
{
|
||||||
|
var values = value.Split(',').Select(v => v.Trim()).ToHashSet();
|
||||||
|
return new EnumProperty { Name = name, PropertyType = propertyType, Value = values };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new EnumProperty { Name = name, PropertyType = propertyType, Value = new HashSet<string> { value } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ced.StorageType == CustomEnumStorageType.Int)
|
||||||
|
{
|
||||||
|
var value = _reader.GetRequiredAttributeParseable<int>("value");
|
||||||
|
if (ced.ValueAsFlags)
|
||||||
|
{
|
||||||
|
var allValues = ced.Values;
|
||||||
|
var enumValues = new HashSet<string>();
|
||||||
|
for (var i = 0; i < allValues.Count; i++)
|
||||||
|
{
|
||||||
|
var mask = 1 << i;
|
||||||
|
if ((value & mask) == mask)
|
||||||
|
{
|
||||||
|
var enumValue = allValues[i];
|
||||||
|
_ = enumValues.Add(enumValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new EnumProperty { Name = name, PropertyType = propertyType, Value = enumValues };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var allValues = ced.Values;
|
||||||
|
var enumValue = allValues[value];
|
||||||
|
return new EnumProperty { Name = name, PropertyType = propertyType, Value = new HashSet<string> { enumValue } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new XmlException($"Unknown custom enum storage type: {ced.StorageType}");
|
||||||
|
}
|
||||||
|
}
|
147
src/DotTiled/Serialization/Tmx/TmxReaderBase.TileLayer.cs
Normal file
147
src/DotTiled/Serialization/Tmx/TmxReaderBase.TileLayer.cs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using DotTiled.Model;
|
||||||
|
|
||||||
|
namespace DotTiled.Serialization.Tmx;
|
||||||
|
|
||||||
|
public abstract partial class TmxReaderBase
|
||||||
|
{
|
||||||
|
internal TileLayer ReadTileLayer(bool dataUsesChunks)
|
||||||
|
{
|
||||||
|
var id = _reader.GetRequiredAttributeParseable<uint>("id");
|
||||||
|
var name = _reader.GetOptionalAttribute("name") ?? "";
|
||||||
|
var @class = _reader.GetOptionalAttribute("class") ?? "";
|
||||||
|
var x = _reader.GetOptionalAttributeParseable<uint>("x") ?? 0;
|
||||||
|
var y = _reader.GetOptionalAttributeParseable<uint>("y") ?? 0;
|
||||||
|
var width = _reader.GetRequiredAttributeParseable<uint>("width");
|
||||||
|
var height = _reader.GetRequiredAttributeParseable<uint>("height");
|
||||||
|
var opacity = _reader.GetOptionalAttributeParseable<float>("opacity") ?? 1.0f;
|
||||||
|
var visible = _reader.GetOptionalAttributeParseable<bool>("visible") ?? true;
|
||||||
|
var tintColor = _reader.GetOptionalAttributeClass<Color>("tintcolor");
|
||||||
|
var offsetX = _reader.GetOptionalAttributeParseable<float>("offsetx") ?? 0.0f;
|
||||||
|
var offsetY = _reader.GetOptionalAttributeParseable<float>("offsety") ?? 0.0f;
|
||||||
|
var parallaxX = _reader.GetOptionalAttributeParseable<float>("parallaxx") ?? 1.0f;
|
||||||
|
var parallaxY = _reader.GetOptionalAttributeParseable<float>("parallaxy") ?? 1.0f;
|
||||||
|
|
||||||
|
List<IProperty>? properties = null;
|
||||||
|
Data? data = null;
|
||||||
|
|
||||||
|
_reader.ProcessChildren("layer", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"data" => () => Helpers.SetAtMostOnce(ref data, ReadData(dataUsesChunks), "Data"),
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(), "Properties"),
|
||||||
|
_ => r.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
return new TileLayer
|
||||||
|
{
|
||||||
|
ID = id,
|
||||||
|
Name = name,
|
||||||
|
Class = @class,
|
||||||
|
X = x,
|
||||||
|
Y = y,
|
||||||
|
Width = width,
|
||||||
|
Height = height,
|
||||||
|
Opacity = opacity,
|
||||||
|
Visible = visible,
|
||||||
|
TintColor = tintColor,
|
||||||
|
OffsetX = offsetX,
|
||||||
|
OffsetY = offsetY,
|
||||||
|
ParallaxX = parallaxX,
|
||||||
|
ParallaxY = parallaxY,
|
||||||
|
Data = data,
|
||||||
|
Properties = properties ?? []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ImageLayer ReadImageLayer()
|
||||||
|
{
|
||||||
|
var id = _reader.GetRequiredAttributeParseable<uint>("id");
|
||||||
|
var name = _reader.GetOptionalAttribute("name") ?? "";
|
||||||
|
var @class = _reader.GetOptionalAttribute("class") ?? "";
|
||||||
|
var x = _reader.GetOptionalAttributeParseable<uint>("x") ?? 0;
|
||||||
|
var y = _reader.GetOptionalAttributeParseable<uint>("y") ?? 0;
|
||||||
|
var opacity = _reader.GetOptionalAttributeParseable<float>("opacity") ?? 1.0f;
|
||||||
|
var visible = _reader.GetOptionalAttributeParseable<bool>("visible") ?? true;
|
||||||
|
var tintColor = _reader.GetOptionalAttributeClass<Color>("tintcolor");
|
||||||
|
var offsetX = _reader.GetOptionalAttributeParseable<float>("offsetx") ?? 0.0f;
|
||||||
|
var offsetY = _reader.GetOptionalAttributeParseable<float>("offsety") ?? 0.0f;
|
||||||
|
var parallaxX = _reader.GetOptionalAttributeParseable<float>("parallaxx") ?? 1.0f;
|
||||||
|
var parallaxY = _reader.GetOptionalAttributeParseable<float>("parallaxy") ?? 1.0f;
|
||||||
|
var repeatX = (_reader.GetOptionalAttributeParseable<uint>("repeatx") ?? 0) == 1;
|
||||||
|
var repeatY = (_reader.GetOptionalAttributeParseable<uint>("repeaty") ?? 0) == 1;
|
||||||
|
|
||||||
|
List<IProperty>? properties = null;
|
||||||
|
Image? image = null;
|
||||||
|
|
||||||
|
_reader.ProcessChildren("imagelayer", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"image" => () => Helpers.SetAtMostOnce(ref image, ReadImage(), "Image"),
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(), "Properties"),
|
||||||
|
_ => r.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
return new ImageLayer
|
||||||
|
{
|
||||||
|
ID = id,
|
||||||
|
Name = name,
|
||||||
|
Class = @class,
|
||||||
|
X = x,
|
||||||
|
Y = y,
|
||||||
|
Opacity = opacity,
|
||||||
|
Visible = visible,
|
||||||
|
TintColor = tintColor,
|
||||||
|
OffsetX = offsetX,
|
||||||
|
OffsetY = offsetY,
|
||||||
|
ParallaxX = parallaxX,
|
||||||
|
ParallaxY = parallaxY,
|
||||||
|
Properties = properties ?? [],
|
||||||
|
Image = image,
|
||||||
|
RepeatX = repeatX,
|
||||||
|
RepeatY = repeatY
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Group ReadGroup()
|
||||||
|
{
|
||||||
|
var id = _reader.GetRequiredAttributeParseable<uint>("id");
|
||||||
|
var name = _reader.GetOptionalAttribute("name") ?? "";
|
||||||
|
var @class = _reader.GetOptionalAttribute("class") ?? "";
|
||||||
|
var opacity = _reader.GetOptionalAttributeParseable<float>("opacity") ?? 1.0f;
|
||||||
|
var visible = _reader.GetOptionalAttributeParseable<bool>("visible") ?? true;
|
||||||
|
var tintColor = _reader.GetOptionalAttributeClass<Color>("tintcolor");
|
||||||
|
var offsetX = _reader.GetOptionalAttributeParseable<float>("offsetx") ?? 0.0f;
|
||||||
|
var offsetY = _reader.GetOptionalAttributeParseable<float>("offsety") ?? 0.0f;
|
||||||
|
var parallaxX = _reader.GetOptionalAttributeParseable<float>("parallaxx") ?? 1.0f;
|
||||||
|
var parallaxY = _reader.GetOptionalAttributeParseable<float>("parallaxy") ?? 1.0f;
|
||||||
|
|
||||||
|
List<IProperty>? properties = null;
|
||||||
|
List<BaseLayer> layers = [];
|
||||||
|
|
||||||
|
_reader.ProcessChildren("group", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(), "Properties"),
|
||||||
|
"layer" => () => layers.Add(ReadTileLayer(false)),
|
||||||
|
"objectgroup" => () => layers.Add(ReadObjectLayer()),
|
||||||
|
"imagelayer" => () => layers.Add(ReadImageLayer()),
|
||||||
|
"group" => () => layers.Add(ReadGroup()),
|
||||||
|
_ => r.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Group
|
||||||
|
{
|
||||||
|
ID = id,
|
||||||
|
Name = name,
|
||||||
|
Class = @class,
|
||||||
|
Opacity = opacity,
|
||||||
|
Visible = visible,
|
||||||
|
TintColor = tintColor,
|
||||||
|
OffsetX = offsetX,
|
||||||
|
OffsetY = offsetY,
|
||||||
|
ParallaxX = parallaxX,
|
||||||
|
ParallaxY = parallaxY,
|
||||||
|
Properties = properties ?? [],
|
||||||
|
Layers = layers
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
317
src/DotTiled/Serialization/Tmx/TmxReaderBase.Tileset.cs
Normal file
317
src/DotTiled/Serialization/Tmx/TmxReaderBase.Tileset.cs
Normal file
|
@ -0,0 +1,317 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using DotTiled.Model;
|
||||||
|
|
||||||
|
namespace DotTiled.Serialization.Tmx;
|
||||||
|
|
||||||
|
public abstract partial class TmxReaderBase
|
||||||
|
{
|
||||||
|
internal Tileset ReadTileset()
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var version = _reader.GetOptionalAttribute("version");
|
||||||
|
var tiledVersion = _reader.GetOptionalAttribute("tiledversion");
|
||||||
|
var firstGID = _reader.GetOptionalAttributeParseable<uint>("firstgid");
|
||||||
|
var source = _reader.GetOptionalAttribute("source");
|
||||||
|
var name = _reader.GetOptionalAttribute("name");
|
||||||
|
var @class = _reader.GetOptionalAttribute("class") ?? "";
|
||||||
|
var tileWidth = _reader.GetOptionalAttributeParseable<uint>("tilewidth");
|
||||||
|
var tileHeight = _reader.GetOptionalAttributeParseable<uint>("tileheight");
|
||||||
|
var spacing = _reader.GetOptionalAttributeParseable<uint>("spacing") ?? 0;
|
||||||
|
var margin = _reader.GetOptionalAttributeParseable<uint>("margin") ?? 0;
|
||||||
|
var tileCount = _reader.GetOptionalAttributeParseable<uint>("tilecount");
|
||||||
|
var columns = _reader.GetOptionalAttributeParseable<uint>("columns");
|
||||||
|
var objectAlignment = _reader.GetOptionalAttributeEnum<ObjectAlignment>("objectalignment", s => s switch
|
||||||
|
{
|
||||||
|
"unspecified" => ObjectAlignment.Unspecified,
|
||||||
|
"topleft" => ObjectAlignment.TopLeft,
|
||||||
|
"top" => ObjectAlignment.Top,
|
||||||
|
"topright" => ObjectAlignment.TopRight,
|
||||||
|
"left" => ObjectAlignment.Left,
|
||||||
|
"center" => ObjectAlignment.Center,
|
||||||
|
"right" => ObjectAlignment.Right,
|
||||||
|
"bottomleft" => ObjectAlignment.BottomLeft,
|
||||||
|
"bottom" => ObjectAlignment.Bottom,
|
||||||
|
"bottomright" => ObjectAlignment.BottomRight,
|
||||||
|
_ => throw new InvalidOperationException($"Unknown object alignment '{s}'")
|
||||||
|
}) ?? ObjectAlignment.Unspecified;
|
||||||
|
var renderSize = _reader.GetOptionalAttributeEnum<TileRenderSize>("rendersize", s => s switch
|
||||||
|
{
|
||||||
|
"tile" => TileRenderSize.Tile,
|
||||||
|
"grid" => TileRenderSize.Grid,
|
||||||
|
_ => throw new InvalidOperationException($"Unknown render size '{s}'")
|
||||||
|
}) ?? TileRenderSize.Tile;
|
||||||
|
var fillMode = _reader.GetOptionalAttributeEnum<FillMode>("fillmode", s => s switch
|
||||||
|
{
|
||||||
|
"stretch" => FillMode.Stretch,
|
||||||
|
"preserve-aspect-fit" => FillMode.PreserveAspectFit,
|
||||||
|
_ => throw new InvalidOperationException($"Unknown fill mode '{s}'")
|
||||||
|
}) ?? FillMode.Stretch;
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
Image? image = null;
|
||||||
|
TileOffset? tileOffset = null;
|
||||||
|
Grid? grid = null;
|
||||||
|
List<IProperty>? properties = null;
|
||||||
|
List<Wangset>? wangsets = null;
|
||||||
|
Transformations? transformations = null;
|
||||||
|
List<Tile> tiles = [];
|
||||||
|
|
||||||
|
_reader.ProcessChildren("tileset", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"image" => () => Helpers.SetAtMostOnce(ref image, ReadImage(), "Image"),
|
||||||
|
"tileoffset" => () => Helpers.SetAtMostOnce(ref tileOffset, ReadTileOffset(), "TileOffset"),
|
||||||
|
"grid" => () => Helpers.SetAtMostOnce(ref grid, ReadGrid(), "Grid"),
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(), "Properties"),
|
||||||
|
"wangsets" => () => Helpers.SetAtMostOnce(ref wangsets, ReadWangsets(), "Wangsets"),
|
||||||
|
"transformations" => () => Helpers.SetAtMostOnce(ref transformations, ReadTransformations(), "Transformations"),
|
||||||
|
"tile" => () => tiles.Add(ReadTile()),
|
||||||
|
_ => r.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if tileset is referring to external file
|
||||||
|
if (source is not null)
|
||||||
|
{
|
||||||
|
var resolvedTileset = _externalTilesetResolver(source);
|
||||||
|
resolvedTileset.FirstGID = firstGID;
|
||||||
|
resolvedTileset.Source = source;
|
||||||
|
return resolvedTileset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Tileset
|
||||||
|
{
|
||||||
|
Version = version,
|
||||||
|
TiledVersion = tiledVersion,
|
||||||
|
FirstGID = firstGID,
|
||||||
|
Source = source,
|
||||||
|
Name = name,
|
||||||
|
Class = @class,
|
||||||
|
TileWidth = tileWidth,
|
||||||
|
TileHeight = tileHeight,
|
||||||
|
Spacing = spacing,
|
||||||
|
Margin = margin,
|
||||||
|
TileCount = tileCount,
|
||||||
|
Columns = columns,
|
||||||
|
ObjectAlignment = objectAlignment,
|
||||||
|
RenderSize = renderSize,
|
||||||
|
FillMode = fillMode,
|
||||||
|
Image = image,
|
||||||
|
TileOffset = tileOffset,
|
||||||
|
Grid = grid,
|
||||||
|
Properties = properties ?? [],
|
||||||
|
Wangsets = wangsets,
|
||||||
|
Transformations = transformations,
|
||||||
|
Tiles = tiles
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Image ReadImage()
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var format = _reader.GetOptionalAttributeEnum<ImageFormat>("format", s => s switch
|
||||||
|
{
|
||||||
|
"png" => ImageFormat.Png,
|
||||||
|
"jpg" => ImageFormat.Jpg,
|
||||||
|
"bmp" => ImageFormat.Bmp,
|
||||||
|
"gif" => ImageFormat.Gif,
|
||||||
|
_ => throw new InvalidOperationException($"Unknown image format '{s}'")
|
||||||
|
});
|
||||||
|
var source = _reader.GetOptionalAttribute("source");
|
||||||
|
var transparentColor = _reader.GetOptionalAttributeClass<Color>("trans");
|
||||||
|
var width = _reader.GetOptionalAttributeParseable<uint>("width");
|
||||||
|
var height = _reader.GetOptionalAttributeParseable<uint>("height");
|
||||||
|
|
||||||
|
_reader.ProcessChildren("image", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"data" => throw new NotSupportedException("Embedded image data is not supported."),
|
||||||
|
_ => r.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
if (format is null && source is not null)
|
||||||
|
format = Helpers.ParseImageFormatFromSource(source);
|
||||||
|
|
||||||
|
return new Image
|
||||||
|
{
|
||||||
|
Format = format,
|
||||||
|
Source = source,
|
||||||
|
TransparentColor = transparentColor,
|
||||||
|
Width = width,
|
||||||
|
Height = height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal TileOffset ReadTileOffset()
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var x = _reader.GetOptionalAttributeParseable<float>("x") ?? 0f;
|
||||||
|
var y = _reader.GetOptionalAttributeParseable<float>("y") ?? 0f;
|
||||||
|
|
||||||
|
_reader.ReadStartElement("tileoffset");
|
||||||
|
return new TileOffset { X = x, Y = y };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Grid ReadGrid()
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var orientation = _reader.GetOptionalAttributeEnum<GridOrientation>("orientation", s => s switch
|
||||||
|
{
|
||||||
|
"orthogonal" => GridOrientation.Orthogonal,
|
||||||
|
"isometric" => GridOrientation.Isometric,
|
||||||
|
_ => throw new InvalidOperationException($"Unknown orientation '{s}'")
|
||||||
|
}) ?? GridOrientation.Orthogonal;
|
||||||
|
var width = _reader.GetRequiredAttributeParseable<uint>("width");
|
||||||
|
var height = _reader.GetRequiredAttributeParseable<uint>("height");
|
||||||
|
|
||||||
|
_reader.ReadStartElement("grid");
|
||||||
|
return new Grid { Orientation = orientation, Width = width, Height = height };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Transformations ReadTransformations()
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var hFlip = (_reader.GetOptionalAttributeParseable<uint>("hflip") ?? 0) == 1;
|
||||||
|
var vFlip = (_reader.GetOptionalAttributeParseable<uint>("vflip") ?? 0) == 1;
|
||||||
|
var rotate = (_reader.GetOptionalAttributeParseable<uint>("rotate") ?? 0) == 1;
|
||||||
|
var preferUntransformed = (_reader.GetOptionalAttributeParseable<uint>("preferuntransformed") ?? 0) == 1;
|
||||||
|
|
||||||
|
_reader.ReadStartElement("transformations");
|
||||||
|
return new Transformations { HFlip = hFlip, VFlip = vFlip, Rotate = rotate, PreferUntransformed = preferUntransformed };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Tile ReadTile()
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var id = _reader.GetRequiredAttributeParseable<uint>("id");
|
||||||
|
var type = _reader.GetOptionalAttribute("type") ?? "";
|
||||||
|
var probability = _reader.GetOptionalAttributeParseable<float>("probability") ?? 0f;
|
||||||
|
var x = _reader.GetOptionalAttributeParseable<uint>("x") ?? 0;
|
||||||
|
var y = _reader.GetOptionalAttributeParseable<uint>("y") ?? 0;
|
||||||
|
var width = _reader.GetOptionalAttributeParseable<uint>("width");
|
||||||
|
var height = _reader.GetOptionalAttributeParseable<uint>("height");
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
List<IProperty>? properties = null;
|
||||||
|
Image? image = null;
|
||||||
|
ObjectLayer? objectLayer = null;
|
||||||
|
List<Frame>? animation = null;
|
||||||
|
|
||||||
|
_reader.ProcessChildren("tile", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(), "Properties"),
|
||||||
|
"image" => () => Helpers.SetAtMostOnce(ref image, ReadImage(), "Image"),
|
||||||
|
"objectgroup" => () => Helpers.SetAtMostOnce(ref objectLayer, ReadObjectLayer(), "ObjectLayer"),
|
||||||
|
"animation" => () => Helpers.SetAtMostOnce(ref animation, r.ReadList<Frame>("animation", "frame", (ar) =>
|
||||||
|
{
|
||||||
|
var tileID = ar.GetRequiredAttributeParseable<uint>("tileid");
|
||||||
|
var duration = ar.GetRequiredAttributeParseable<uint>("duration");
|
||||||
|
return new Frame { TileID = tileID, Duration = duration };
|
||||||
|
}), "Animation"),
|
||||||
|
_ => r.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Tile
|
||||||
|
{
|
||||||
|
ID = id,
|
||||||
|
Type = type,
|
||||||
|
Probability = probability,
|
||||||
|
X = x,
|
||||||
|
Y = y,
|
||||||
|
Width = width ?? image?.Width ?? 0,
|
||||||
|
Height = height ?? image?.Height ?? 0,
|
||||||
|
Properties = properties ?? [],
|
||||||
|
Image = image,
|
||||||
|
ObjectLayer = objectLayer,
|
||||||
|
Animation = animation
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal List<Wangset> ReadWangsets() =>
|
||||||
|
_reader.ReadList<Wangset>("wangsets", "wangset", r => ReadWangset());
|
||||||
|
|
||||||
|
internal Wangset ReadWangset()
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var name = _reader.GetRequiredAttribute("name");
|
||||||
|
var @class = _reader.GetOptionalAttribute("class") ?? "";
|
||||||
|
var tile = _reader.GetRequiredAttributeParseable<int>("tile");
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
List<IProperty>? properties = null;
|
||||||
|
List<WangColor> wangColors = [];
|
||||||
|
List<WangTile> wangTiles = [];
|
||||||
|
|
||||||
|
_reader.ProcessChildren("wangset", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(), "Properties"),
|
||||||
|
"wangcolor" => () => wangColors.Add(ReadWangColor()),
|
||||||
|
"wangtile" => () => wangTiles.Add(ReadWangTile()),
|
||||||
|
_ => r.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
if (wangColors.Count > 254)
|
||||||
|
throw new ArgumentException("Wangset can have at most 254 Wang colors.");
|
||||||
|
|
||||||
|
return new Wangset
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Class = @class,
|
||||||
|
Tile = tile,
|
||||||
|
Properties = properties ?? [],
|
||||||
|
WangColors = wangColors,
|
||||||
|
WangTiles = wangTiles
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal WangColor ReadWangColor()
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var name = _reader.GetRequiredAttribute("name");
|
||||||
|
var @class = _reader.GetOptionalAttribute("class") ?? "";
|
||||||
|
var color = _reader.GetRequiredAttributeParseable<Color>("color");
|
||||||
|
var tile = _reader.GetRequiredAttributeParseable<int>("tile");
|
||||||
|
var probability = _reader.GetOptionalAttributeParseable<float>("probability") ?? 0f;
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
List<IProperty>? properties = null;
|
||||||
|
|
||||||
|
_reader.ProcessChildren("wangcolor", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(), "Properties"),
|
||||||
|
_ => r.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
return new WangColor
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Class = @class,
|
||||||
|
Color = color,
|
||||||
|
Tile = tile,
|
||||||
|
Probability = probability,
|
||||||
|
Properties = properties ?? []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal WangTile ReadWangTile()
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var tileID = _reader.GetRequiredAttributeParseable<uint>("tileid");
|
||||||
|
var wangID = _reader.GetRequiredAttributeParseable<byte[]>("wangid", s =>
|
||||||
|
{
|
||||||
|
// Comma-separated list of indices (0-254)
|
||||||
|
var indices = s.Split(',').Select(i => byte.Parse(i, CultureInfo.InvariantCulture)).ToArray();
|
||||||
|
if (indices.Length > 8)
|
||||||
|
throw new ArgumentException("Wang ID can have at most 8 indices.");
|
||||||
|
return indices;
|
||||||
|
});
|
||||||
|
|
||||||
|
_reader.ReadStartElement("wangtile");
|
||||||
|
|
||||||
|
return new WangTile
|
||||||
|
{
|
||||||
|
TileID = tileID,
|
||||||
|
WangID = wangID
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
74
src/DotTiled/Serialization/Tmx/TmxReaderBase.cs
Normal file
74
src/DotTiled/Serialization/Tmx/TmxReaderBase.cs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
using System;
|
||||||
|
using System.Xml;
|
||||||
|
using DotTiled.Model;
|
||||||
|
|
||||||
|
namespace DotTiled.Serialization.Tmx;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for Tiled XML format readers.
|
||||||
|
/// </summary>
|
||||||
|
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 XmlReader _reader;
|
||||||
|
private bool disposedValue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a new <see cref="TmxReaderBase"/>, which is the base class for all Tiled XML format readers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reader">An XML reader for reading a Tiled map in the Tiled XML format.</param>
|
||||||
|
/// <param name="externalTilesetResolver">A function that resolves external tilesets given their source.</param>
|
||||||
|
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
|
||||||
|
/// <param name="customTypeResolver">A function that resolves custom types given their source.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
|
||||||
|
protected TmxReaderBase(
|
||||||
|
XmlReader reader,
|
||||||
|
Func<string, Tileset> externalTilesetResolver,
|
||||||
|
Func<string, Template> externalTemplateResolver,
|
||||||
|
Func<string, ICustomTypeDefinition> customTypeResolver)
|
||||||
|
{
|
||||||
|
_reader = reader ?? throw new ArgumentNullException(nameof(reader));
|
||||||
|
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
||||||
|
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
||||||
|
_customTypeResolver = customTypeResolver ?? throw new ArgumentNullException(nameof(customTypeResolver));
|
||||||
|
|
||||||
|
// Prepare reader
|
||||||
|
_ = _reader.MoveToContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposedValue)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
// TODO: dispose managed state (managed objects)
|
||||||
|
_reader.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
||||||
|
// TODO: set large fields to null
|
||||||
|
disposedValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||||
|
// ~TmxReaderBase()
|
||||||
|
// {
|
||||||
|
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
// Dispose(disposing: false);
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using DotTiled.Model;
|
using DotTiled.Model;
|
||||||
|
|
||||||
|
@ -8,68 +7,20 @@ namespace DotTiled.Serialization.Tmx;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A tileset reader for the Tiled XML format.
|
/// A tileset reader for the Tiled XML format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TsxTilesetReader : ITilesetReader
|
public class TsxTilesetReader : TmxReaderBase, ITilesetReader
|
||||||
{
|
{
|
||||||
// External resolvers
|
|
||||||
private readonly Func<string, Template> _externalTemplateResolver;
|
|
||||||
|
|
||||||
private readonly XmlReader _reader;
|
|
||||||
private bool disposedValue;
|
|
||||||
|
|
||||||
private readonly IReadOnlyCollection<CustomTypeDefinition> _customTypeDefinitions;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs a new <see cref="TsxTilesetReader"/>.
|
/// Constructs a new <see cref="TsxTilesetReader"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="reader">An XML reader for reading a Tiled tileset in the Tiled XML format.</param>
|
/// <inheritdoc />
|
||||||
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
|
|
||||||
/// <param name="customTypeDefinitions">A collection of custom type definitions that can be used to resolve custom types when encountering <see cref="ClassProperty"/>.</param>
|
|
||||||
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
|
|
||||||
public TsxTilesetReader(
|
public TsxTilesetReader(
|
||||||
XmlReader reader,
|
XmlReader reader,
|
||||||
|
Func<string, Tileset> externalTilesetResolver,
|
||||||
Func<string, Template> externalTemplateResolver,
|
Func<string, Template> externalTemplateResolver,
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||||
{
|
reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||||
_reader = reader ?? throw new ArgumentNullException(nameof(reader));
|
{ }
|
||||||
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
|
||||||
_customTypeDefinitions = customTypeDefinitions ?? throw new ArgumentNullException(nameof(customTypeDefinitions));
|
|
||||||
|
|
||||||
// Prepare reader
|
|
||||||
_ = _reader.MoveToContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Tileset ReadTileset() => Tmx.ReadTileset(_reader, null, _externalTemplateResolver, _customTypeDefinitions);
|
public new Tileset ReadTileset() => base.ReadTileset();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!disposedValue)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
// TODO: dispose managed state (managed objects)
|
|
||||||
_reader.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
|
||||||
// TODO: set large fields to null
|
|
||||||
disposedValue = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
|
||||||
// ~TsxTilesetReader()
|
|
||||||
// {
|
|
||||||
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
||||||
// Dispose(disposing: false);
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
||||||
Dispose(disposing: true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using DotTiled.Model;
|
using DotTiled.Model;
|
||||||
|
|
||||||
|
@ -8,72 +7,20 @@ namespace DotTiled.Serialization.Tmx;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A template reader for the Tiled XML format.
|
/// A template reader for the Tiled XML format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TxTemplateReader : ITemplateReader
|
public class TxTemplateReader : TmxReaderBase, ITemplateReader
|
||||||
{
|
{
|
||||||
// Resolvers
|
|
||||||
private readonly Func<string, Tileset> _externalTilesetResolver;
|
|
||||||
private readonly Func<string, Template> _externalTemplateResolver;
|
|
||||||
|
|
||||||
private readonly XmlReader _reader;
|
|
||||||
private bool disposedValue;
|
|
||||||
|
|
||||||
private readonly IReadOnlyCollection<CustomTypeDefinition> _customTypeDefinitions;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs a new <see cref="TxTemplateReader"/>.
|
/// Constructs a new <see cref="TxTemplateReader"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="reader">An XML reader for reading a Tiled template in the Tiled XML format.</param>
|
/// <inheritdoc />
|
||||||
/// <param name="externalTilesetResolver">A function that resolves external tilesets given their source.</param>
|
|
||||||
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
|
|
||||||
/// <param name="customTypeDefinitions">A collection of custom type definitions that can be used to resolve custom types when encountering <see cref="ClassProperty"/>.</param>
|
|
||||||
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
|
|
||||||
public TxTemplateReader(
|
public TxTemplateReader(
|
||||||
XmlReader reader,
|
XmlReader reader,
|
||||||
Func<string, Tileset> externalTilesetResolver,
|
Func<string, Tileset> externalTilesetResolver,
|
||||||
Func<string, Template> externalTemplateResolver,
|
Func<string, Template> externalTemplateResolver,
|
||||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||||
{
|
reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||||
_reader = reader ?? throw new ArgumentNullException(nameof(reader));
|
{ }
|
||||||
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
|
||||||
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
|
||||||
_customTypeDefinitions = customTypeDefinitions ?? throw new ArgumentNullException(nameof(customTypeDefinitions));
|
|
||||||
|
|
||||||
// Prepare reader
|
|
||||||
_ = _reader.MoveToContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Template ReadTemplate() => Tmx.ReadTemplate(_reader, _externalTilesetResolver, _externalTemplateResolver, _customTypeDefinitions);
|
public new Template ReadTemplate() => base.ReadTemplate();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!disposedValue)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
// TODO: dispose managed state (managed objects)
|
|
||||||
_reader.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
|
||||||
// TODO: set large fields to null
|
|
||||||
disposedValue = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
|
||||||
// ~TxTemplateReader()
|
|
||||||
// {
|
|
||||||
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
||||||
// Dispose(disposing: false);
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
||||||
Dispose(disposing: true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue