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 analyzers --verify-no-changes src/DotTiled.sln
|
||||
|
||||
BENCHMARK_SOURCES = DotTiled.Benchmark/Program.cs DotTiled.Benchmark/DotTiled.Benchmark.csproj
|
||||
BENCHMARK_OUTPUTDIR = DotTiled.Benchmark/BenchmarkDotNet.Artifacts
|
||||
BENCHMARK_SOURCES = src/DotTiled.Benchmark/Program.cs src/DotTiled.Benchmark/DotTiled.Benchmark.csproj
|
||||
BENCHMARK_OUTPUTDIR = src/DotTiled.Benchmark/BenchmarkDotNet.Artifacts
|
||||
.PHONY: benchmark
|
||||
benchmark: $(BENCHMARK_OUTPUTDIR)/results/MyBenchmarks.MapLoading-report-github.md
|
||||
|
||||
$(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
|
||||
- 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 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();
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ namespace DotTiled.Benchmark
|
|||
[Benchmark(Baseline = true, Description = "DotTiled")]
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ public static partial class DotTiledAssert
|
|||
AssertEqual(expected.NextObjectID, actual.NextObjectID, nameof(Map.NextObjectID));
|
||||
AssertEqual(expected.Infinite, actual.Infinite, nameof(Map.Infinite));
|
||||
|
||||
AssertProperties(actual.Properties, expected.Properties);
|
||||
AssertProperties(expected.Properties, actual.Properties);
|
||||
|
||||
Assert.NotNull(actual.Tilesets);
|
||||
AssertEqual(expected.Tilesets.Count, actual.Tilesets.Count, "Tilesets.Count");
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace DotTiled.Tests;
|
|||
|
||||
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)
|
||||
{
|
||||
|
@ -14,18 +14,16 @@ public static partial class DotTiledAssert
|
|||
|
||||
Assert.NotNull(actual);
|
||||
AssertEqual(expected.Count, actual.Count, "Properties.Count");
|
||||
foreach (var kvp in expected)
|
||||
foreach (var prop in expected)
|
||||
{
|
||||
Assert.Contains(kvp.Key, actual.Keys);
|
||||
AssertProperty((dynamic)kvp.Value, (dynamic)actual[kvp.Key]);
|
||||
}
|
||||
}
|
||||
Assert.Contains(actual, p => p.Name == prop.Name);
|
||||
|
||||
private static void AssertProperty(IProperty expected, IProperty actual)
|
||||
{
|
||||
AssertEqual(expected.Type, actual.Type, "Property.Type");
|
||||
AssertEqual(expected.Name, actual.Name, "Property.Name");
|
||||
AssertProperties((dynamic)actual, (dynamic)expected);
|
||||
var actualProp = actual.First(p => p.Name == prop.Name);
|
||||
AssertEqual(prop.Type, actualProp.Type, "Property.Type");
|
||||
AssertEqual(prop.Name, actualProp.Name, "Property.Name");
|
||||
|
||||
AssertProperty((dynamic)prop, (dynamic)actualProp);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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));
|
||||
|
||||
// Elements
|
||||
AssertProperties(actual.Properties, expected.Properties);
|
||||
AssertImage(actual.Image, expected.Image);
|
||||
AssertLayer((BaseLayer?)actual.ObjectLayer, (BaseLayer?)expected.ObjectLayer);
|
||||
AssertProperties(expected.Properties, actual.Properties);
|
||||
AssertImage(expected.Image, actual.Image);
|
||||
AssertLayer((BaseLayer?)expected.ObjectLayer, (BaseLayer?)actual.ObjectLayer);
|
||||
if (expected.Animation is not null)
|
||||
{
|
||||
Assert.NotNull(actual.Animation);
|
||||
|
|
|
@ -32,14 +32,15 @@ public static partial class TestData
|
|||
|
||||
public static IEnumerable<object[]> MapTests =>
|
||||
[
|
||||
["Serialization/TestData/Map/default_map/default-map", (string f) => DefaultMap(), Array.Empty<CustomTypeDefinition>()],
|
||||
["Serialization/TestData/Map/map_with_common_props/map-with-common-props", (string f) => MapWithCommonProps(), 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<ICustomTypeDefinition>()],
|
||||
["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_external_tileset/map-with-external-tileset", (string f) => MapWithExternalTileset(f), Array.Empty<CustomTypeDefinition>()],
|
||||
["Serialization/TestData/Map/map_with_flippingflags/map-with-flippingflags", (string f) => MapWithFlippingFlags(f), Array.Empty<CustomTypeDefinition>()],
|
||||
["Serialization/TestData/Map/map_external_tileset_multi/map-external-tileset-multi", (string f) => MapExternalTilesetMulti(f), Array.Empty<CustomTypeDefinition>()],
|
||||
["Serialization/TestData/Map/map_external_tileset_wangset/map-external-tileset-wangset", (string f) => MapExternalTilesetWangset(f), Array.Empty<CustomTypeDefinition>()],
|
||||
["Serialization/TestData/Map/map_with_many_layers/map-with-many-layers", (string f) => MapWithManyLayers(f), 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<ICustomTypeDefinition>()],
|
||||
["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<ICustomTypeDefinition>()],
|
||||
["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<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,
|
||||
Height = 1
|
||||
},
|
||||
Properties = new Dictionary<string, IProperty>
|
||||
{
|
||||
["tilesetbool"] = new BoolProperty { Name = "tilesetbool", Value = true },
|
||||
["tilesetcolor"] = new ColorProperty { Name = "tilesetcolor", Value = Color.Parse("#ffff0000", CultureInfo.InvariantCulture) },
|
||||
["tilesetfile"] = new FileProperty { Name = "tilesetfile", Value = "" },
|
||||
["tilesetfloat"] = new FloatProperty { Name = "tilesetfloat", Value = 5.2f },
|
||||
["tilesetint"] = new IntProperty { Name = "tilesetint", Value = 9 },
|
||||
["tilesetobject"] = new ObjectProperty { Name = "tilesetobject", Value = 0 },
|
||||
["tilesetstring"] = new StringProperty { Name = "tilesetstring", Value = "hello world!" }
|
||||
},
|
||||
Properties = [
|
||||
new BoolProperty { Name = "tilesetbool", Value = true },
|
||||
new ColorProperty { Name = "tilesetcolor", Value = Color.Parse("#ffff0000", CultureInfo.InvariantCulture) },
|
||||
new FileProperty { Name = "tilesetfile", Value = "" },
|
||||
new FloatProperty { Name = "tilesetfloat", Value = 5.2f },
|
||||
new IntProperty { Name = "tilesetint", Value = 9 },
|
||||
new ObjectProperty { Name = "tilesetobject", Value = 0 },
|
||||
new StringProperty { Name = "tilesetstring", Value = "hello world!" }
|
||||
],
|
||||
Tiles = [
|
||||
new Tile
|
||||
{
|
||||
|
|
|
@ -55,15 +55,15 @@ public partial class TestData
|
|||
}
|
||||
}
|
||||
],
|
||||
Properties = new Dictionary<string, IProperty>
|
||||
{
|
||||
["boolprop"] = new BoolProperty { Name = "boolprop", Value = true },
|
||||
["colorprop"] = new ColorProperty { Name = "colorprop", Value = Color.Parse("#ff55ffff", CultureInfo.InvariantCulture) },
|
||||
["fileprop"] = new FileProperty { Name = "fileprop", Value = "file.txt" },
|
||||
["floatprop"] = new FloatProperty { Name = "floatprop", Value = 4.2f },
|
||||
["intprop"] = new IntProperty { Name = "intprop", Value = 8 },
|
||||
["objectprop"] = new ObjectProperty { Name = "objectprop", Value = 5 },
|
||||
["stringprop"] = new StringProperty { Name = "stringprop", Value = "This is a string, hello world!" }
|
||||
}
|
||||
Properties =
|
||||
[
|
||||
new BoolProperty { Name = "boolprop", Value = true },
|
||||
new ColorProperty { Name = "colorprop", Value = Color.Parse("#ff55ffff", CultureInfo.InvariantCulture) },
|
||||
new FileProperty { Name = "fileprop", Value = "file.txt" },
|
||||
new FloatProperty { Name = "floatprop", Value = 4.2f },
|
||||
new IntProperty { Name = "intprop", Value = 8 },
|
||||
new ObjectProperty { Name = "objectprop", Value = 5 },
|
||||
new StringProperty { Name = "stringprop", Value = "This is a string, hello world!" }
|
||||
]
|
||||
};
|
||||
}
|
||||
|
|
|
@ -55,28 +55,50 @@ public partial class TestData
|
|||
}
|
||||
}
|
||||
],
|
||||
Properties = new Dictionary<string, IProperty>
|
||||
{
|
||||
["customclassprop"] = new ClassProperty
|
||||
Properties = [
|
||||
new ClassProperty
|
||||
{
|
||||
Name = "customclassprop",
|
||||
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 },
|
||||
["colorinclass"] = new ColorProperty { Name = "colorinclass", Value = Color.Parse("#000000ff", CultureInfo.InvariantCulture) },
|
||||
["fileinclass"] = new FileProperty { Name = "fileinclass", Value = "" },
|
||||
["floatinclass"] = new FloatProperty { Name = "floatinclass", Value = 13.37f },
|
||||
["intinclass"] = new IntProperty { Name = "intinclass", Value = 0 },
|
||||
["objectinclass"] = new ObjectProperty { Name = "objectinclass", Value = 0 },
|
||||
["stringinclass"] = new StringProperty { Name = "stringinclass", Value = "This is a set string" }
|
||||
}
|
||||
}
|
||||
Name = "customenumstringprop",
|
||||
PropertyType = "CustomEnumString",
|
||||
Value = new HashSet<string> { "CustomEnumString_2" }
|
||||
},
|
||||
new EnumProperty
|
||||
{
|
||||
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
|
||||
public static IReadOnlyCollection<CustomTypeDefinition> MapWithCustomTypePropsCustomTypeDefinitions() => [
|
||||
public static IReadOnlyCollection<ICustomTypeDefinition> MapWithCustomTypePropsCustomTypeDefinitions() => [
|
||||
new CustomClassDefinition
|
||||
{
|
||||
Name = "CustomClass",
|
||||
|
@ -118,6 +140,50 @@ public partial class TestData
|
|||
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,
|
||||
"stringinclass":"This is a set string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"customenumintflagsprop",
|
||||
"propertytype":"CustomEnumIntFlags",
|
||||
"type":"int",
|
||||
"value":6
|
||||
},
|
||||
{
|
||||
"name":"customenumintprop",
|
||||
"propertytype":"CustomEnumInt",
|
||||
"type":"int",
|
||||
"value":3
|
||||
},
|
||||
{
|
||||
"name":"customenumstringflagsprop",
|
||||
"propertytype":"CustomEnumStringFlags",
|
||||
"type":"string",
|
||||
"value":"CustomEnumStringFlags_1,CustomEnumStringFlags_2"
|
||||
},
|
||||
{
|
||||
"name":"customenumstringprop",
|
||||
"propertytype":"CustomEnumString",
|
||||
"type":"string",
|
||||
"value":"CustomEnumString_2"
|
||||
}],
|
||||
"renderorder":"right-down",
|
||||
"tiledversion":"1.11.0",
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
<property name="stringinclass" value="This is a set string"/>
|
||||
</properties>
|
||||
</property>
|
||||
<property name="customenumintflagsprop" type="int" propertytype="CustomEnumIntFlags" value="6"/>
|
||||
<property name="customenumintprop" type="int" propertytype="CustomEnumInt" value="3"/>
|
||||
<property name="customenumstringflagsprop" propertytype="CustomEnumStringFlags" value="CustomEnumStringFlags_1,CustomEnumStringFlags_2"/>
|
||||
<property name="customenumstringprop" propertytype="CustomEnumString" value="CustomEnumString_2"/>
|
||||
</properties>
|
||||
<layer id="1" name="Tile Layer 1" width="5" height="5">
|
||||
<data encoding="csv">
|
||||
|
|
|
@ -0,0 +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)
|
||||
],
|
||||
Template = fileExt == "tmx" ? "poly.tx" : "poly.tj",
|
||||
Properties = new Dictionary<string, IProperty>
|
||||
{
|
||||
["templateprop"] = new StringProperty { Name = "templateprop", Value = "helo there" }
|
||||
}
|
||||
Properties = [
|
||||
new StringProperty { Name = "templateprop", Value = "helo there" }
|
||||
]
|
||||
},
|
||||
new TileObject
|
||||
{
|
||||
|
|
|
@ -11,7 +11,7 @@ public partial class TmjMapReaderTests
|
|||
public void TmxMapReaderReadMap_ValidTmjExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(
|
||||
string testDataFile,
|
||||
Func<string, Map> expectedMap,
|
||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
||||
{
|
||||
// Arrange
|
||||
testDataFile += ".tmj";
|
||||
|
@ -20,16 +20,20 @@ public partial class TmjMapReaderTests
|
|||
Template ResolveTemplate(string 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();
|
||||
}
|
||||
Tileset ResolveTileset(string 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();
|
||||
}
|
||||
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
|
||||
var map = mapReader.ReadMap();
|
||||
|
|
|
@ -11,7 +11,7 @@ public partial class TmxMapReaderTests
|
|||
public void TmxMapReaderReadMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(
|
||||
string testDataFile,
|
||||
Func<string, Map> expectedMap,
|
||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
||||
{
|
||||
// Arrange
|
||||
testDataFile += ".tmx";
|
||||
|
@ -20,16 +20,20 @@ public partial class TmxMapReaderTests
|
|||
Template ResolveTemplate(string 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();
|
||||
}
|
||||
Tileset ResolveTileset(string 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();
|
||||
}
|
||||
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
|
||||
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>,
|
||||
/// or some other mechanism to determine the type of the layer at runtime.
|
||||
/// </summary>
|
||||
public abstract class BaseLayer
|
||||
public abstract class BaseLayer : HasPropertiesBase
|
||||
{
|
||||
/// <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.
|
||||
|
@ -62,5 +62,8 @@ public abstract class BaseLayer
|
|||
/// <summary>
|
||||
/// Layer properties.
|
||||
/// </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>
|
||||
/// Base class for objects in object layers.
|
||||
/// </summary>
|
||||
public abstract class Object
|
||||
public abstract class Object : HasPropertiesBase
|
||||
{
|
||||
/// <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.
|
||||
|
@ -60,5 +60,8 @@ public abstract class Object
|
|||
/// <summary>
|
||||
/// Object properties.
|
||||
/// </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>
|
||||
/// Represents a Tiled map.
|
||||
/// </summary>
|
||||
public class Map
|
||||
public class Map : HasPropertiesBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The TMX format version. Is incremented to match minor Tiled releases.
|
||||
|
@ -191,7 +191,10 @@ public class Map
|
|||
/// <summary>
|
||||
/// Map properties.
|
||||
/// </summary>
|
||||
public Dictionary<string, IProperty>? Properties { get; set; }
|
||||
public List<IProperty> Properties { get; set; } = [];
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IList<IProperty> GetProperties() => Properties;
|
||||
|
||||
/// <summary>
|
||||
/// List of tilesets used by the map.
|
||||
|
|
|
@ -3,7 +3,7 @@ namespace DotTiled.Model;
|
|||
/// <summary>
|
||||
/// Represents a boolean property.
|
||||
/// </summary>
|
||||
public class BoolProperty : IProperty
|
||||
public class BoolProperty : IProperty<bool>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public required string Name { get; set; }
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace DotTiled.Model;
|
||||
|
@ -6,7 +8,7 @@ namespace DotTiled.Model;
|
|||
/// <summary>
|
||||
/// Represents a class property.
|
||||
/// </summary>
|
||||
public class ClassProperty : IProperty
|
||||
public class ClassProperty : IHasProperties, IProperty<IList<IProperty>>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public required string Name { get; set; }
|
||||
|
@ -23,13 +25,41 @@ public class ClassProperty : IProperty
|
|||
/// <summary>
|
||||
/// The properties of the class property.
|
||||
/// </summary>
|
||||
public required Dictionary<string, IProperty> Properties { get; set; }
|
||||
public required IList<IProperty> Value { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IProperty Clone() => new ClassProperty
|
||||
{
|
||||
Name = Name,
|
||||
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>
|
||||
/// Represents a color property.
|
||||
/// </summary>
|
||||
public class ColorProperty : IProperty
|
||||
public class ColorProperty : IProperty<Color>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public required string Name { get; set; }
|
||||
|
|
|
@ -65,8 +65,14 @@ public enum CustomClassUseAs
|
|||
/// 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>.
|
||||
/// </summary>
|
||||
public class CustomClassDefinition : CustomTypeDefinition
|
||||
public class CustomClassDefinition : HasPropertiesBase, ICustomTypeDefinition
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public uint ID { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The color of the custom class inside the Tiled editor.
|
||||
/// </summary>
|
||||
|
@ -86,4 +92,7 @@ public class CustomClassDefinition : CustomTypeDefinition
|
|||
/// The members of the custom class, with their names, types and default values.
|
||||
/// </summary>
|
||||
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
|
||||
/// <see href="https://doc.mapeditor.org/en/stable/manual/custom-properties/#custom-types">documentation of custom types to understand how they work</see>.
|
||||
/// </summary>
|
||||
public class CustomEnumDefinition : CustomTypeDefinition
|
||||
public class CustomEnumDefinition : ICustomTypeDefinition
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public uint ID { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The storage type of the custom enum.
|
||||
/// </summary>
|
||||
|
|
|
@ -3,7 +3,7 @@ namespace DotTiled.Model;
|
|||
/// <summary>
|
||||
/// Base class for custom type definitions.
|
||||
/// </summary>
|
||||
public abstract class CustomTypeDefinition
|
||||
public interface ICustomTypeDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID of the custom type.
|
||||
|
@ -13,5 +13,5 @@ public abstract class CustomTypeDefinition
|
|||
/// <summary>
|
||||
/// The name of the custom type.
|
||||
/// </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>
|
||||
/// Represents a file property.
|
||||
/// </summary>
|
||||
public class FileProperty : IProperty
|
||||
public class FileProperty : IProperty<string>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public required string Name { get; set; }
|
||||
|
|
|
@ -3,7 +3,7 @@ namespace DotTiled.Model;
|
|||
/// <summary>
|
||||
/// Represents a float property.
|
||||
/// </summary>
|
||||
public class FloatProperty : IProperty
|
||||
public class FloatProperty : IProperty<float>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
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>
|
||||
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>
|
||||
/// Represents an integer property.
|
||||
/// </summary>
|
||||
public class IntProperty : IProperty
|
||||
public class IntProperty : IProperty<int>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public required string Name { get; set; }
|
||||
|
|
|
@ -3,7 +3,7 @@ namespace DotTiled.Model;
|
|||
/// <summary>
|
||||
/// Represents an object property.
|
||||
/// </summary>
|
||||
public class ObjectProperty : IProperty
|
||||
public class ObjectProperty : IProperty<uint>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public required string Name { get; set; }
|
||||
|
|
|
@ -43,5 +43,10 @@ public enum PropertyType
|
|||
/// <summary>
|
||||
/// A class property.
|
||||
/// </summary>
|
||||
Class
|
||||
Class,
|
||||
|
||||
/// <summary>
|
||||
/// An enum property.
|
||||
/// </summary>
|
||||
Enum
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ namespace DotTiled.Model;
|
|||
/// <summary>
|
||||
/// Represents a string property.
|
||||
/// </summary>
|
||||
public class StringProperty : IProperty
|
||||
public class StringProperty : IProperty<string>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
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.
|
||||
/// <see href="https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#tile">Tiled documentation for Tileset tiles</see>
|
||||
/// </summary>
|
||||
public class Tile
|
||||
public class Tile : HasPropertiesBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The local tile ID within its tileset.
|
||||
|
@ -46,7 +46,10 @@ public class Tile
|
|||
/// <summary>
|
||||
/// Tile properties.
|
||||
/// </summary>
|
||||
public Dictionary<string, IProperty>? Properties { get; set; }
|
||||
public List<IProperty> Properties { get; set; } = [];
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IList<IProperty> GetProperties() => Properties;
|
||||
|
||||
/// <summary>
|
||||
/// The image representing this tile. Only used for tilesets that composed of a collection of images.
|
||||
|
|
|
@ -93,7 +93,7 @@ public enum FillMode
|
|||
/// <summary>
|
||||
/// A tileset is a collection of tiles that can be used in a tile layer, or by tile objects.
|
||||
/// </summary>
|
||||
public class Tileset
|
||||
public class Tileset : HasPropertiesBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The TMX format version. Is incremented to match minor Tiled releases.
|
||||
|
@ -188,7 +188,10 @@ public class Tileset
|
|||
/// <summary>
|
||||
/// Tileset properties.
|
||||
/// </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
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ namespace DotTiled.Model;
|
|||
/// <summary>
|
||||
/// Represents a Wang color in a Wang set.
|
||||
/// </summary>
|
||||
public class WangColor
|
||||
public class WangColor : HasPropertiesBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of this color.
|
||||
|
@ -35,5 +35,8 @@ public class WangColor
|
|||
/// <summary>
|
||||
/// The Wang color properties.
|
||||
/// </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>
|
||||
/// Defines a list of colors and any number of Wang tiles using these colors.
|
||||
/// </summary>
|
||||
public class Wangset
|
||||
public class Wangset : HasPropertiesBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the Wang set.
|
||||
|
@ -25,7 +25,10 @@ public class Wangset
|
|||
/// <summary>
|
||||
/// The Wang set properties.
|
||||
/// </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
|
||||
/// <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)
|
||||
return overrideProperties ?? new Dictionary<string, IProperty>();
|
||||
return overrideProperties ?? [];
|
||||
|
||||
if (overrideProperties is null)
|
||||
return baseProperties;
|
||||
|
||||
var result = baseProperties.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Clone());
|
||||
foreach (var (key, value) in overrideProperties)
|
||||
var result = baseProperties.Select(x => x.Clone()).ToList();
|
||||
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;
|
||||
}
|
||||
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
|
||||
{
|
||||
result[key] = value;
|
||||
ReplacePropertyInList(result, overrideProp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -105,6 +126,15 @@ internal static partial class Helpers
|
|||
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)
|
||||
{
|
||||
if (field is not null)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DotTiled.Model;
|
||||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
@ -7,73 +6,24 @@ namespace DotTiled.Serialization.Tmj;
|
|||
/// <summary>
|
||||
/// A template reader for reading Tiled JSON templates.
|
||||
/// </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>
|
||||
/// Constructs a new <see cref="TjTemplateReader"/>.
|
||||
/// </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="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>
|
||||
public TjTemplateReader(
|
||||
string jsonString,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
||||
{
|
||||
_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));
|
||||
}
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||
jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Template ReadTemplate()
|
||||
{
|
||||
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);
|
||||
}
|
||||
public Template ReadTemplate() => ReadTemplate(RootElement);
|
||||
}
|
||||
|
|
|
@ -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.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using DotTiled.Model;
|
||||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
@ -8,73 +6,24 @@ namespace DotTiled.Serialization.Tmj;
|
|||
/// <summary>
|
||||
/// A map reader for reading Tiled JSON maps.
|
||||
/// </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>
|
||||
/// 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="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>
|
||||
public TmjMapReader(
|
||||
string jsonString,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
||||
{
|
||||
_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));
|
||||
}
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||
jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Map ReadMap()
|
||||
{
|
||||
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);
|
||||
}
|
||||
public Map ReadMap() => ReadMap(RootElement);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ using DotTiled.Model;
|
|||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
||||
internal partial class Tmj
|
||||
public abstract partial class TmjReaderBase
|
||||
{
|
||||
internal static Data ReadDataAsChunks(JsonElement element, DataCompression? compression, DataEncoding encoding)
|
||||
{
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
|
@ -6,12 +5,9 @@ using DotTiled.Model;
|
|||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
||||
internal partial class Tmj
|
||||
public abstract partial class TmjReaderBase
|
||||
{
|
||||
internal static Group ReadGroup(
|
||||
JsonElement element,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
||||
internal Group ReadGroup(JsonElement element)
|
||||
{
|
||||
var id = element.GetRequiredProperty<uint>("id");
|
||||
var name = element.GetRequiredProperty<string>("name");
|
||||
|
@ -23,8 +19,8 @@ internal partial class Tmj
|
|||
var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f);
|
||||
var parallaxX = element.GetOptionalProperty<float>("parallaxx", 1.0f);
|
||||
var parallaxY = element.GetOptionalProperty<float>("parallaxy", 1.0f);
|
||||
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null);
|
||||
var layers = element.GetOptionalPropertyCustom<List<BaseLayer>>("layers", e => e.GetValueAsList<BaseLayer>(el => ReadLayer(el, externalTemplateResolver, customTypeDefinitions)), []);
|
||||
var properties = element.GetOptionalPropertyCustom("properties", ReadProperties, []);
|
||||
var layers = element.GetOptionalPropertyCustom<List<BaseLayer>>("layers", e => e.GetValueAsList<BaseLayer>(ReadLayer), []);
|
||||
|
||||
return new Group
|
||||
{
|
|
@ -1,15 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using DotTiled.Model;
|
||||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
||||
internal partial class Tmj
|
||||
public abstract partial class TmjReaderBase
|
||||
{
|
||||
internal static ImageLayer ReadImageLayer(
|
||||
JsonElement element,
|
||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
||||
internal ImageLayer ReadImageLayer(JsonElement element)
|
||||
{
|
||||
var id = element.GetRequiredProperty<uint>("id");
|
||||
var name = element.GetRequiredProperty<string>("name");
|
||||
|
@ -21,7 +18,7 @@ internal partial class Tmj
|
|||
var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f);
|
||||
var parallaxX = element.GetOptionalProperty<float>("parallaxx", 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 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.Globalization;
|
||||
using System.Text.Json;
|
||||
|
@ -6,13 +5,9 @@ using DotTiled.Model;
|
|||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
||||
internal partial class Tmj
|
||||
public abstract partial class TmjReaderBase
|
||||
{
|
||||
internal static Map ReadMap(
|
||||
JsonElement element,
|
||||
Func<string, Tileset>? externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
||||
internal Map ReadMap(JsonElement element)
|
||||
{
|
||||
var version = element.GetRequiredProperty<string>("version");
|
||||
var tiledVersion = element.GetRequiredProperty<string>("tiledversion");
|
||||
|
@ -58,10 +53,10 @@ internal partial class Tmj
|
|||
var nextObjectID = element.GetRequiredProperty<uint>("nextobjectid");
|
||||
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<Tileset> tilesets = element.GetOptionalPropertyCustom<List<Tileset>>("tilesets", e => e.GetValueAsList<Tileset>(el => ReadTileset(el, externalTilesetResolver, 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)), []);
|
||||
|
||||
return new Map
|
||||
{
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
|
@ -7,12 +6,9 @@ using DotTiled.Model;
|
|||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
||||
internal partial class Tmj
|
||||
public abstract partial class TmjReaderBase
|
||||
{
|
||||
internal static ObjectLayer ReadObjectLayer(
|
||||
JsonElement element,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
||||
internal ObjectLayer ReadObjectLayer(JsonElement element)
|
||||
{
|
||||
var id = element.GetRequiredProperty<uint>("id");
|
||||
var name = element.GetRequiredProperty<string>("name");
|
||||
|
@ -24,7 +20,7 @@ internal partial class Tmj
|
|||
var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f);
|
||||
var parallaxX = element.GetOptionalProperty<float>("parallaxx", 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 y = element.GetOptionalProperty<uint>("y", 0);
|
||||
|
@ -38,7 +34,7 @@ internal partial class Tmj
|
|||
_ => throw new JsonException($"Unknown draw order '{s}'.")
|
||||
}, 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
|
||||
{
|
||||
|
@ -63,10 +59,7 @@ internal partial class Tmj
|
|||
};
|
||||
}
|
||||
|
||||
internal static Model.Object ReadObject(
|
||||
JsonElement element,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
||||
internal Model.Object ReadObject(JsonElement element)
|
||||
{
|
||||
uint? idDefault = null;
|
||||
string nameDefault = "";
|
||||
|
@ -82,12 +75,12 @@ internal partial class Tmj
|
|||
bool pointDefault = false;
|
||||
List<Vector2>? polygonDefault = null;
|
||||
List<Vector2>? polylineDefault = null;
|
||||
Dictionary<string, IProperty>? propertiesDefault = null;
|
||||
List<IProperty> propertiesDefault = [];
|
||||
|
||||
var template = element.GetOptionalProperty<string?>("template", null);
|
||||
if (template is not null)
|
||||
{
|
||||
var resolvedTemplate = externalTemplateResolver(template);
|
||||
var resolvedTemplate = _externalTemplateResolver(template);
|
||||
var templObj = resolvedTemplate.Object;
|
||||
|
||||
idDefault = templObj.ID;
|
||||
|
@ -114,7 +107,7 @@ internal partial class Tmj
|
|||
var point = element.GetOptionalProperty<bool>("point", pointDefault);
|
||||
var polygon = element.GetOptionalPropertyCustom<List<Vector2>?>("polygon", ReadPoints, polygonDefault);
|
||||
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 text = element.GetOptionalPropertyCustom<TextObject?>("text", ReadText, null);
|
||||
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.Text.Json;
|
||||
using DotTiled.Model;
|
||||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
||||
internal partial class Tmj
|
||||
public abstract partial class TmjReaderBase
|
||||
{
|
||||
internal static TileLayer ReadTileLayer(
|
||||
JsonElement element,
|
||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
||||
internal TileLayer ReadTileLayer(JsonElement element)
|
||||
{
|
||||
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 parallaxx = element.GetOptionalProperty<float>("parallaxx", 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 repeatY = element.GetOptionalProperty<bool>("repeaty", false);
|
||||
var startX = element.GetOptionalProperty<int>("startx", 0);
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
|
@ -6,13 +5,9 @@ using DotTiled.Model;
|
|||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
||||
internal partial class Tmj
|
||||
public abstract partial class TmjReaderBase
|
||||
{
|
||||
internal static Tileset ReadTileset(
|
||||
JsonElement element,
|
||||
Func<string, Tileset>? externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
||||
internal Tileset ReadTileset(JsonElement element)
|
||||
{
|
||||
var backgroundColor = element.GetOptionalPropertyParseable<Color?>("backgroundcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
|
||||
var @class = element.GetOptionalProperty<string>("class", "");
|
||||
|
@ -44,7 +39,7 @@ internal partial class Tmj
|
|||
"bottomright" => ObjectAlignment.BottomRight,
|
||||
_ => throw new JsonException($"Unknown object alignment '{s}'")
|
||||
}, 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 spacing = element.GetOptionalProperty<uint?>("spacing", null);
|
||||
var tileCount = element.GetOptionalProperty<uint?>("tilecount", null);
|
||||
|
@ -57,20 +52,17 @@ internal partial class Tmj
|
|||
"grid" => TileRenderSize.Grid,
|
||||
_ => throw new JsonException($"Unknown tile render size '{s}'")
|
||||
}, 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 transparentColor = element.GetOptionalPropertyParseable<Color?>("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
|
||||
var type = element.GetOptionalProperty<string?>("type", null);
|
||||
var version = element.GetOptionalProperty<string?>("version", 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 (externalTilesetResolver is null)
|
||||
throw new JsonException("External tileset resolver is required to resolve external tilesets.");
|
||||
|
||||
var resolvedTileset = externalTilesetResolver(source);
|
||||
var resolvedTileset = _externalTilesetResolver(source);
|
||||
resolvedTileset.FirstGID = firstGID;
|
||||
resolvedTileset.Source = source;
|
||||
return resolvedTileset;
|
||||
|
@ -159,10 +151,7 @@ internal partial class Tmj
|
|||
};
|
||||
}
|
||||
|
||||
internal static List<Tile> ReadTiles(
|
||||
JsonElement element,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions) =>
|
||||
internal List<Tile> ReadTiles(JsonElement element) =>
|
||||
element.GetValueAsList<Tile>(e =>
|
||||
{
|
||||
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 width = e.GetOptionalProperty<uint>("width", imageWidth ?? 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 properties = e.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", el => ReadProperties(el, customTypeDefinitions), null);
|
||||
var properties = e.GetOptionalPropertyCustom("properties", ReadProperties, []);
|
||||
// var terrain, replaced by wangsets
|
||||
var type = e.GetOptionalProperty<string>("type", "");
|
||||
|
||||
|
@ -216,14 +205,12 @@ internal partial class Tmj
|
|||
};
|
||||
}
|
||||
|
||||
internal static Wangset ReadWangset(
|
||||
JsonElement element,
|
||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
||||
internal Wangset ReadWangset(JsonElement element)
|
||||
{
|
||||
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 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 type = element.GetOptionalProperty<string>("type", "");
|
||||
var wangTiles = element.GetOptionalPropertyCustom<List<WangTile>>("wangtiles", e => e.GetValueAsList<WangTile>(ReadWangTile), []);
|
||||
|
@ -239,15 +226,13 @@ internal partial class Tmj
|
|||
};
|
||||
}
|
||||
|
||||
internal static WangColor ReadWangColor(
|
||||
JsonElement element,
|
||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
||||
internal WangColor ReadWangColor(JsonElement element)
|
||||
{
|
||||
var @class = element.GetOptionalProperty<string>("class", "");
|
||||
var color = element.GetRequiredPropertyParseable<Color>("color", s => Color.Parse(s, CultureInfo.InvariantCulture));
|
||||
var name = element.GetRequiredProperty<string>("name");
|
||||
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);
|
||||
|
||||
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.Collections.Generic;
|
||||
using DotTiled.Model;
|
||||
|
||||
namespace DotTiled.Serialization.Tmj;
|
||||
|
@ -7,73 +6,24 @@ namespace DotTiled.Serialization.Tmj;
|
|||
/// <summary>
|
||||
/// A tileset reader for the Tiled JSON format.
|
||||
/// </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>
|
||||
/// Constructs a new <see cref="TsjTilesetReader"/>.
|
||||
/// </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="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>
|
||||
public TsjTilesetReader(
|
||||
string jsonString,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
||||
{
|
||||
_jsonString = jsonString ?? throw new ArgumentNullException(nameof(jsonString));
|
||||
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
||||
_customTypeDefinitions = customTypeDefinitions ?? throw new ArgumentNullException(nameof(customTypeDefinitions));
|
||||
}
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||
jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Tileset ReadTileset()
|
||||
{
|
||||
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);
|
||||
}
|
||||
public Tileset ReadTileset() => ReadTileset(RootElement);
|
||||
}
|
||||
|
|
|
@ -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.Collections.Generic;
|
||||
using System.Xml;
|
||||
using DotTiled.Model;
|
||||
|
||||
|
@ -8,72 +7,20 @@ namespace DotTiled.Serialization.Tmx;
|
|||
/// <summary>
|
||||
/// A map reader for the Tiled XML format.
|
||||
/// </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>
|
||||
/// Constructs a new <see cref="TmxMapReader"/>.
|
||||
/// </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="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>
|
||||
/// <inheritdoc />
|
||||
public TmxMapReader(
|
||||
XmlReader reader,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
||||
{
|
||||
_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();
|
||||
}
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||
reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Map ReadMap() => Tmx.ReadMap(_reader, _externalTilesetResolver, _externalTemplateResolver, _customTypeDefinitions);
|
||||
|
||||
/// <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);
|
||||
}
|
||||
public new Map ReadMap() => base.ReadMap();
|
||||
}
|
||||
|
|
|
@ -1,27 +1,26 @@
|
|||
using System.Xml;
|
||||
using DotTiled.Model;
|
||||
|
||||
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 y = reader.GetRequiredAttributeParseable<int>("y");
|
||||
var width = reader.GetRequiredAttributeParseable<uint>("width");
|
||||
var height = reader.GetRequiredAttributeParseable<uint>("height");
|
||||
var x = _reader.GetRequiredAttributeParseable<int>("x");
|
||||
var y = _reader.GetRequiredAttributeParseable<int>("y");
|
||||
var width = _reader.GetRequiredAttributeParseable<uint>("width");
|
||||
var height = _reader.GetRequiredAttributeParseable<uint>("height");
|
||||
|
||||
var usesTileChildrenInsteadOfRawData = encoding is null;
|
||||
if (usesTileChildrenInsteadOfRawData)
|
||||
{
|
||||
var globalTileIDsWithFlippingFlags = ReadTileChildrenInWrapper("chunk", reader);
|
||||
var globalTileIDsWithFlippingFlags = ReadTileChildrenInWrapper("chunk", _reader);
|
||||
var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(globalTileIDsWithFlippingFlags);
|
||||
return new Chunk { X = x, Y = y, Width = width, Height = height, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags };
|
||||
}
|
||||
else
|
||||
{
|
||||
var globalTileIDsWithFlippingFlags = ReadRawData(reader, encoding!.Value, compression);
|
||||
var globalTileIDsWithFlippingFlags = ReadRawData(_reader, encoding!.Value, compression);
|
||||
var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(globalTileIDsWithFlippingFlags);
|
||||
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;
|
||||
|
||||
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,
|
||||
"base64" => DataEncoding.Base64,
|
||||
_ => 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,
|
||||
"zlib" => DataCompression.ZLib,
|
||||
|
@ -28,8 +28,8 @@ internal partial class Tmx
|
|||
|
||||
if (usesChunks)
|
||||
{
|
||||
var chunks = reader
|
||||
.ReadList("data", "chunk", (r) => ReadChunk(r, encoding, compression))
|
||||
var chunks = _reader
|
||||
.ReadList("data", "chunk", (r) => ReadChunk(encoding, compression))
|
||||
.ToArray();
|
||||
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;
|
||||
if (usesTileChildrenInsteadOfRawData)
|
||||
{
|
||||
var tileChildrenGlobalTileIDsWithFlippingFlags = ReadTileChildrenInWrapper("data", reader);
|
||||
var tileChildrenGlobalTileIDsWithFlippingFlags = ReadTileChildrenInWrapper("data", _reader);
|
||||
var (tileChildrenGlobalTileIDs, tileChildrenFlippingFlags) = ReadAndClearFlippingFlagsFromGIDs(tileChildrenGlobalTileIDsWithFlippingFlags);
|
||||
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);
|
||||
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.Linq;
|
||||
using System.Numerics;
|
||||
using System.Xml;
|
||||
using DotTiled.Model;
|
||||
|
||||
namespace DotTiled.Serialization.Tmx;
|
||||
|
||||
internal partial class Tmx
|
||||
public abstract partial class TmxReaderBase
|
||||
{
|
||||
internal static ObjectLayer ReadObjectLayer(
|
||||
XmlReader reader,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
||||
internal ObjectLayer ReadObjectLayer()
|
||||
{
|
||||
// Attributes
|
||||
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.GetOptionalAttributeParseable<uint>("width");
|
||||
var height = reader.GetOptionalAttributeParseable<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;
|
||||
var color = reader.GetOptionalAttributeClass<Color>("color");
|
||||
var drawOrder = reader.GetOptionalAttributeEnum<DrawOrder>("draworder", s => s switch
|
||||
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.GetOptionalAttributeParseable<uint>("width");
|
||||
var height = _reader.GetOptionalAttributeParseable<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;
|
||||
var color = _reader.GetOptionalAttributeClass<Color>("color");
|
||||
var drawOrder = _reader.GetOptionalAttributeEnum<DrawOrder>("draworder", s => s switch
|
||||
{
|
||||
"topdown" => DrawOrder.TopDown,
|
||||
"index" => DrawOrder.Index,
|
||||
|
@ -39,13 +35,13 @@ internal partial class Tmx
|
|||
}) ?? DrawOrder.TopDown;
|
||||
|
||||
// Elements
|
||||
Dictionary<string, IProperty>? properties = null;
|
||||
List<IProperty>? properties = null;
|
||||
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"),
|
||||
"object" => () => objects.Add(ReadObject(r, externalTemplateResolver, customTypeDefinitions)),
|
||||
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(), "Properties"),
|
||||
"object" => () => objects.Add(ReadObject()),
|
||||
_ => r.Skip
|
||||
});
|
||||
|
||||
|
@ -66,22 +62,19 @@ internal partial class Tmx
|
|||
ParallaxX = parallaxX,
|
||||
ParallaxY = parallaxY,
|
||||
Color = color,
|
||||
Properties = properties,
|
||||
Properties = properties ?? [],
|
||||
DrawOrder = drawOrder,
|
||||
Objects = objects
|
||||
};
|
||||
}
|
||||
|
||||
internal static Model.Object ReadObject(
|
||||
XmlReader reader,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
||||
internal Model.Object ReadObject()
|
||||
{
|
||||
// Attributes
|
||||
var template = reader.GetOptionalAttribute("template");
|
||||
var template = _reader.GetOptionalAttribute("template");
|
||||
Model.Object? obj = null;
|
||||
if (template is not null)
|
||||
obj = externalTemplateResolver(template).Object;
|
||||
obj = _externalTemplateResolver(template).Object;
|
||||
|
||||
uint? idDefault = obj?.ID ?? null;
|
||||
string nameDefault = obj?.Name ?? "";
|
||||
|
@ -93,32 +86,32 @@ internal partial class Tmx
|
|||
float rotationDefault = obj?.Rotation ?? 0f;
|
||||
uint? gidDefault = obj is TileObject tileObj ? tileObj.GID : null;
|
||||
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 name = reader.GetOptionalAttribute("name") ?? nameDefault;
|
||||
var type = reader.GetOptionalAttribute("type") ?? typeDefault;
|
||||
var x = reader.GetOptionalAttributeParseable<float>("x") ?? xDefault;
|
||||
var y = reader.GetOptionalAttributeParseable<float>("y") ?? yDefault;
|
||||
var width = reader.GetOptionalAttributeParseable<float>("width") ?? widthDefault;
|
||||
var height = reader.GetOptionalAttributeParseable<float>("height") ?? heightDefault;
|
||||
var rotation = reader.GetOptionalAttributeParseable<float>("rotation") ?? rotationDefault;
|
||||
var gid = reader.GetOptionalAttributeParseable<uint>("gid") ?? gidDefault;
|
||||
var visible = reader.GetOptionalAttributeParseable<bool>("visible") ?? visibleDefault;
|
||||
var id = _reader.GetOptionalAttributeParseable<uint>("id") ?? idDefault;
|
||||
var name = _reader.GetOptionalAttribute("name") ?? nameDefault;
|
||||
var type = _reader.GetOptionalAttribute("type") ?? typeDefault;
|
||||
var x = _reader.GetOptionalAttributeParseable<float>("x") ?? xDefault;
|
||||
var y = _reader.GetOptionalAttributeParseable<float>("y") ?? yDefault;
|
||||
var width = _reader.GetOptionalAttributeParseable<float>("width") ?? widthDefault;
|
||||
var height = _reader.GetOptionalAttributeParseable<float>("height") ?? heightDefault;
|
||||
var rotation = _reader.GetOptionalAttributeParseable<float>("rotation") ?? rotationDefault;
|
||||
var gid = _reader.GetOptionalAttributeParseable<uint>("gid") ?? gidDefault;
|
||||
var visible = _reader.GetOptionalAttributeParseable<bool>("visible") ?? visibleDefault;
|
||||
|
||||
// Elements
|
||||
Model.Object? foundObject = null;
|
||||
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),
|
||||
"ellipse" => () => Helpers.SetAtMostOnce(ref foundObject, ReadEllipseObject(r), "Object marker"),
|
||||
"point" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPointObject(r), "Object marker"),
|
||||
"polygon" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPolygonObject(r), "Object marker"),
|
||||
"polyline" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPolylineObject(r), "Object marker"),
|
||||
"text" => () => Helpers.SetAtMostOnce(ref foundObject, ReadTextObject(r), "Object marker"),
|
||||
"properties" => () => Helpers.SetAtMostOnceUsingCounter(ref properties, Helpers.MergeProperties(properties, ReadProperties()).ToList(), "Properties", ref propertiesCounter),
|
||||
"ellipse" => () => Helpers.SetAtMostOnce(ref foundObject, ReadEllipseObject(), "Object marker"),
|
||||
"point" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPointObject(), "Object marker"),
|
||||
"polygon" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPolygonObject(), "Object marker"),
|
||||
"polyline" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPolylineObject(), "Object marker"),
|
||||
"text" => () => Helpers.SetAtMostOnce(ref foundObject, ReadTextObject(), "Object marker"),
|
||||
_ => throw new InvalidOperationException($"Unknown object marker '{elementName}'")
|
||||
});
|
||||
|
||||
|
@ -139,7 +132,7 @@ internal partial class Tmx
|
|||
foundObject.Height = height;
|
||||
foundObject.Rotation = rotation;
|
||||
foundObject.Visible = visible;
|
||||
foundObject.Properties = properties;
|
||||
foundObject.Properties = properties ?? [];
|
||||
foundObject.Template = template;
|
||||
|
||||
return OverrideObject(obj, foundObject);
|
||||
|
@ -161,7 +154,7 @@ internal partial class Tmx
|
|||
obj.Height = foundObject.Height;
|
||||
obj.Rotation = foundObject.Rotation;
|
||||
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;
|
||||
return obj;
|
||||
}
|
||||
|
@ -169,26 +162,26 @@ internal partial class Tmx
|
|||
return OverrideObject((dynamic)obj, (dynamic)foundObject);
|
||||
}
|
||||
|
||||
internal static EllipseObject ReadEllipseObject(XmlReader reader)
|
||||
internal EllipseObject ReadEllipseObject()
|
||||
{
|
||||
reader.Skip();
|
||||
_reader.Skip();
|
||||
return new EllipseObject { };
|
||||
}
|
||||
|
||||
internal static EllipseObject OverrideObject(EllipseObject obj, EllipseObject _) => obj;
|
||||
|
||||
internal static PointObject ReadPointObject(XmlReader reader)
|
||||
internal PointObject ReadPointObject()
|
||||
{
|
||||
reader.Skip();
|
||||
_reader.Skip();
|
||||
return new PointObject { };
|
||||
}
|
||||
|
||||
internal static PointObject OverrideObject(PointObject obj, PointObject _) => obj;
|
||||
|
||||
internal static PolygonObject ReadPolygonObject(XmlReader reader)
|
||||
internal PolygonObject ReadPolygonObject()
|
||||
{
|
||||
// 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 ..."
|
||||
var coords = s.Split(' ');
|
||||
|
@ -199,7 +192,7 @@ internal partial class Tmx
|
|||
}).ToList();
|
||||
});
|
||||
|
||||
reader.ReadStartElement("polygon");
|
||||
_reader.ReadStartElement("polygon");
|
||||
return new PolygonObject { Points = points };
|
||||
}
|
||||
|
||||
|
@ -209,10 +202,10 @@ internal partial class Tmx
|
|||
return obj;
|
||||
}
|
||||
|
||||
internal static PolylineObject ReadPolylineObject(XmlReader reader)
|
||||
internal PolylineObject ReadPolylineObject()
|
||||
{
|
||||
// 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 ..."
|
||||
var coords = s.Split(' ');
|
||||
|
@ -223,7 +216,7 @@ internal partial class Tmx
|
|||
}).ToList();
|
||||
});
|
||||
|
||||
reader.ReadStartElement("polyline");
|
||||
_reader.ReadStartElement("polyline");
|
||||
return new PolylineObject { Points = points };
|
||||
}
|
||||
|
||||
|
@ -233,19 +226,19 @@ internal partial class Tmx
|
|||
return obj;
|
||||
}
|
||||
|
||||
internal static TextObject ReadTextObject(XmlReader reader)
|
||||
internal TextObject ReadTextObject()
|
||||
{
|
||||
// Attributes
|
||||
var fontFamily = reader.GetOptionalAttribute("fontfamily") ?? "sans-serif";
|
||||
var pixelSize = reader.GetOptionalAttributeParseable<int>("pixelsize") ?? 16;
|
||||
var wrap = reader.GetOptionalAttributeParseable<bool>("wrap") ?? false;
|
||||
var color = reader.GetOptionalAttributeClass<Color>("color") ?? Color.Parse("#000000", CultureInfo.InvariantCulture);
|
||||
var bold = reader.GetOptionalAttributeParseable<bool>("bold") ?? false;
|
||||
var italic = reader.GetOptionalAttributeParseable<bool>("italic") ?? false;
|
||||
var underline = reader.GetOptionalAttributeParseable<bool>("underline") ?? false;
|
||||
var strikeout = reader.GetOptionalAttributeParseable<bool>("strikeout") ?? false;
|
||||
var kerning = reader.GetOptionalAttributeParseable<bool>("kerning") ?? true;
|
||||
var hAlign = reader.GetOptionalAttributeEnum<TextHorizontalAlignment>("halign", s => s switch
|
||||
var fontFamily = _reader.GetOptionalAttribute("fontfamily") ?? "sans-serif";
|
||||
var pixelSize = _reader.GetOptionalAttributeParseable<int>("pixelsize") ?? 16;
|
||||
var wrap = _reader.GetOptionalAttributeParseable<bool>("wrap") ?? false;
|
||||
var color = _reader.GetOptionalAttributeClass<Color>("color") ?? Color.Parse("#000000", CultureInfo.InvariantCulture);
|
||||
var bold = _reader.GetOptionalAttributeParseable<bool>("bold") ?? false;
|
||||
var italic = _reader.GetOptionalAttributeParseable<bool>("italic") ?? false;
|
||||
var underline = _reader.GetOptionalAttributeParseable<bool>("underline") ?? false;
|
||||
var strikeout = _reader.GetOptionalAttributeParseable<bool>("strikeout") ?? false;
|
||||
var kerning = _reader.GetOptionalAttributeParseable<bool>("kerning") ?? true;
|
||||
var hAlign = _reader.GetOptionalAttributeEnum<TextHorizontalAlignment>("halign", s => s switch
|
||||
{
|
||||
"left" => TextHorizontalAlignment.Left,
|
||||
"center" => TextHorizontalAlignment.Center,
|
||||
|
@ -253,7 +246,7 @@ internal partial class Tmx
|
|||
"justify" => TextHorizontalAlignment.Justify,
|
||||
_ => throw new InvalidOperationException($"Unknown horizontal alignment '{s}'")
|
||||
}) ?? TextHorizontalAlignment.Left;
|
||||
var vAlign = reader.GetOptionalAttributeEnum<TextVerticalAlignment>("valign", s => s switch
|
||||
var vAlign = _reader.GetOptionalAttributeEnum<TextVerticalAlignment>("valign", s => s switch
|
||||
{
|
||||
"top" => TextVerticalAlignment.Top,
|
||||
"center" => TextVerticalAlignment.Center,
|
||||
|
@ -262,7 +255,7 @@ internal partial class Tmx
|
|||
}) ?? TextVerticalAlignment.Top;
|
||||
|
||||
// Elements
|
||||
var text = reader.ReadElementContentAsString("text", "");
|
||||
var text = _reader.ReadElementContentAsString("text", "");
|
||||
|
||||
return new TextObject
|
||||
{
|
||||
|
@ -304,11 +297,7 @@ internal partial class Tmx
|
|||
return obj;
|
||||
}
|
||||
|
||||
internal static Template ReadTemplate(
|
||||
XmlReader reader,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
||||
internal Template ReadTemplate()
|
||||
{
|
||||
// No attributes
|
||||
|
||||
|
@ -318,10 +307,10 @@ internal partial class Tmx
|
|||
// Should contain exactly one of
|
||||
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"),
|
||||
"object" => () => Helpers.SetAtMostOnce(ref obj, ReadObject(r, externalTemplateResolver, customTypeDefinitions), "Object"),
|
||||
"tileset" => () => Helpers.SetAtMostOnce(ref tileset, ReadTileset(), "Tileset"),
|
||||
"object" => () => Helpers.SetAtMostOnce(ref obj, ReadObject(), "Object"),
|
||||
_ => 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.Collections.Generic;
|
||||
using System.Xml;
|
||||
using DotTiled.Model;
|
||||
|
||||
|
@ -8,68 +7,20 @@ namespace DotTiled.Serialization.Tmx;
|
|||
/// <summary>
|
||||
/// A tileset reader for the Tiled XML format.
|
||||
/// </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>
|
||||
/// Constructs a new <see cref="TsxTilesetReader"/>.
|
||||
/// </summary>
|
||||
/// <param name="reader">An XML reader for reading a Tiled tileset in the Tiled XML format.</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>
|
||||
/// <inheritdoc />
|
||||
public TsxTilesetReader(
|
||||
XmlReader reader,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
||||
{
|
||||
_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();
|
||||
}
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||
reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Tileset ReadTileset() => Tmx.ReadTileset(_reader, null, _externalTemplateResolver, _customTypeDefinitions);
|
||||
|
||||
/// <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);
|
||||
}
|
||||
public new Tileset ReadTileset() => base.ReadTileset();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
using DotTiled.Model;
|
||||
|
||||
|
@ -8,72 +7,20 @@ namespace DotTiled.Serialization.Tmx;
|
|||
/// <summary>
|
||||
/// A template reader for the Tiled XML format.
|
||||
/// </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>
|
||||
/// Constructs a new <see cref="TxTemplateReader"/>.
|
||||
/// </summary>
|
||||
/// <param name="reader">An XML reader for reading a Tiled template 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="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>
|
||||
/// <inheritdoc />
|
||||
public TxTemplateReader(
|
||||
XmlReader reader,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
|
||||
{
|
||||
_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();
|
||||
}
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||
reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Template ReadTemplate() => Tmx.ReadTemplate(_reader, _externalTilesetResolver, _externalTemplateResolver, _customTypeDefinitions);
|
||||
|
||||
/// <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);
|
||||
}
|
||||
public new Template ReadTemplate() => base.ReadTemplate();
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue