mirror of
https://github.com/dcronqvist/DotTiled.git
synced 2025-02-05 17:02:49 +02:00
Tmx reader is now a base abstract class
This commit is contained in:
parent
eda3fbe308
commit
11f1ef783e
25 changed files with 1059 additions and 960 deletions
|
@ -39,7 +39,7 @@ namespace DotTiled.Benchmark
|
||||||
{
|
{
|
||||||
using var stringReader = new StringReader(_tmxContents);
|
using var stringReader = new StringReader(_tmxContents);
|
||||||
using var xmlReader = XmlReader.Create(stringReader);
|
using var xmlReader = XmlReader.Create(stringReader);
|
||||||
using var mapReader = new DotTiled.Serialization.Tmx.TmxMapReader(xmlReader, _ => throw new NotSupportedException(), _ => throw new NotSupportedException(), []);
|
using var mapReader = new DotTiled.Serialization.Tmx.TmxMapReader(xmlReader, _ => throw new NotSupportedException(), _ => throw new NotSupportedException(), _ => throw new NotSupportedException());
|
||||||
return mapReader.ReadMap();
|
return mapReader.ReadMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,7 +91,7 @@ public static partial class DotTiledAssert
|
||||||
AssertEqual(expected.NextObjectID, actual.NextObjectID, nameof(Map.NextObjectID));
|
AssertEqual(expected.NextObjectID, actual.NextObjectID, nameof(Map.NextObjectID));
|
||||||
AssertEqual(expected.Infinite, actual.Infinite, nameof(Map.Infinite));
|
AssertEqual(expected.Infinite, actual.Infinite, nameof(Map.Infinite));
|
||||||
|
|
||||||
AssertProperties(actual.Properties, expected.Properties);
|
AssertProperties(expected.Properties, actual.Properties);
|
||||||
|
|
||||||
Assert.NotNull(actual.Tilesets);
|
Assert.NotNull(actual.Tilesets);
|
||||||
AssertEqual(expected.Tilesets.Count, actual.Tilesets.Count, "Tilesets.Count");
|
AssertEqual(expected.Tilesets.Count, actual.Tilesets.Count, "Tilesets.Count");
|
||||||
|
|
|
@ -45,4 +45,14 @@ public static partial class DotTiledAssert
|
||||||
AssertEqual(expected.PropertyType, actual.PropertyType, "ClassProperty.PropertyType");
|
AssertEqual(expected.PropertyType, actual.PropertyType, "ClassProperty.PropertyType");
|
||||||
AssertProperties(expected.Value, actual.Value);
|
AssertProperties(expected.Value, actual.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void AssertProperty(EnumProperty expected, EnumProperty actual)
|
||||||
|
{
|
||||||
|
AssertEqual(expected.PropertyType, actual.PropertyType, "EnumProperty.PropertyType");
|
||||||
|
AssertEqual(expected.Value.Count, actual.Value.Count, "EnumProperty.Value.Count");
|
||||||
|
foreach (var value in expected.Value)
|
||||||
|
{
|
||||||
|
Assert.Contains(actual.Value, v => v == value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,9 +141,9 @@ public static partial class DotTiledAssert
|
||||||
AssertEqual(expected.Height, actual.Height, nameof(Tile.Height));
|
AssertEqual(expected.Height, actual.Height, nameof(Tile.Height));
|
||||||
|
|
||||||
// Elements
|
// Elements
|
||||||
AssertProperties(actual.Properties, expected.Properties);
|
AssertProperties(expected.Properties, actual.Properties);
|
||||||
AssertImage(actual.Image, expected.Image);
|
AssertImage(expected.Image, actual.Image);
|
||||||
AssertLayer((BaseLayer?)actual.ObjectLayer, (BaseLayer?)expected.ObjectLayer);
|
AssertLayer((BaseLayer?)expected.ObjectLayer, (BaseLayer?)actual.ObjectLayer);
|
||||||
if (expected.Animation is not null)
|
if (expected.Animation is not null)
|
||||||
{
|
{
|
||||||
Assert.NotNull(actual.Animation);
|
Assert.NotNull(actual.Animation);
|
||||||
|
|
|
@ -69,6 +69,30 @@ public partial class TestData
|
||||||
new ObjectProperty { Name = "objectinclass", Value = 0 },
|
new ObjectProperty { Name = "objectinclass", Value = 0 },
|
||||||
new StringProperty { Name = "stringinclass", Value = "This is a set string" }
|
new StringProperty { Name = "stringinclass", Value = "This is a set string" }
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
new EnumProperty
|
||||||
|
{
|
||||||
|
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" }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
@ -116,6 +140,50 @@ public partial class TestData
|
||||||
Value = ""
|
Value = ""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
new CustomEnumDefinition
|
||||||
|
{
|
||||||
|
Name = "CustomEnumString",
|
||||||
|
StorageType = CustomEnumStorageType.String,
|
||||||
|
ValueAsFlags = false,
|
||||||
|
Values = [
|
||||||
|
"CustomEnumString_1",
|
||||||
|
"CustomEnumString_2",
|
||||||
|
"CustomEnumString_3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
new CustomEnumDefinition
|
||||||
|
{
|
||||||
|
Name = "CustomEnumStringFlags",
|
||||||
|
StorageType = CustomEnumStorageType.String,
|
||||||
|
ValueAsFlags = true,
|
||||||
|
Values = [
|
||||||
|
"CustomEnumStringFlags_1",
|
||||||
|
"CustomEnumStringFlags_2"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
new CustomEnumDefinition
|
||||||
|
{
|
||||||
|
Name = "CustomEnumInt",
|
||||||
|
StorageType = CustomEnumStorageType.Int,
|
||||||
|
ValueAsFlags = false,
|
||||||
|
Values = [
|
||||||
|
"CustomEnumInt_1",
|
||||||
|
"CustomEnumInt_2",
|
||||||
|
"CustomEnumInt_3",
|
||||||
|
"CustomEnumInt_4",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
new CustomEnumDefinition
|
||||||
|
{
|
||||||
|
Name = "CustomEnumIntFlags",
|
||||||
|
StorageType = CustomEnumStorageType.Int,
|
||||||
|
ValueAsFlags = true,
|
||||||
|
Values = [
|
||||||
|
"CustomEnumIntFlags_1",
|
||||||
|
"CustomEnumIntFlags_2",
|
||||||
|
"CustomEnumIntFlags_3"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,30 @@
|
||||||
"floatinclass":13.37,
|
"floatinclass":13.37,
|
||||||
"stringinclass":"This is a set string"
|
"stringinclass":"This is a set string"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"customenumintflagsprop",
|
||||||
|
"propertytype":"CustomEnumIntFlags",
|
||||||
|
"type":"int",
|
||||||
|
"value":6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"customenumintprop",
|
||||||
|
"propertytype":"CustomEnumInt",
|
||||||
|
"type":"int",
|
||||||
|
"value":3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"customenumstringflagsprop",
|
||||||
|
"propertytype":"CustomEnumStringFlags",
|
||||||
|
"type":"string",
|
||||||
|
"value":"CustomEnumStringFlags_1,CustomEnumStringFlags_2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"customenumstringprop",
|
||||||
|
"propertytype":"CustomEnumString",
|
||||||
|
"type":"string",
|
||||||
|
"value":"CustomEnumString_2"
|
||||||
}],
|
}],
|
||||||
"renderorder":"right-down",
|
"renderorder":"right-down",
|
||||||
"tiledversion":"1.11.0",
|
"tiledversion":"1.11.0",
|
||||||
|
|
|
@ -8,6 +8,10 @@
|
||||||
<property name="stringinclass" value="This is a set string"/>
|
<property name="stringinclass" value="This is a set string"/>
|
||||||
</properties>
|
</properties>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="customenumintflagsprop" type="int" propertytype="CustomEnumIntFlags" value="6"/>
|
||||||
|
<property name="customenumintprop" type="int" propertytype="CustomEnumInt" value="3"/>
|
||||||
|
<property name="customenumstringflagsprop" propertytype="CustomEnumStringFlags" value="CustomEnumStringFlags_1,CustomEnumStringFlags_2"/>
|
||||||
|
<property name="customenumstringprop" propertytype="CustomEnumString" value="CustomEnumString_2"/>
|
||||||
</properties>
|
</properties>
|
||||||
<layer id="1" name="Tile Layer 1" width="5" height="5">
|
<layer id="1" name="Tile Layer 1" width="5" height="5">
|
||||||
<data encoding="csv">
|
<data encoding="csv">
|
||||||
|
|
|
@ -20,16 +20,20 @@ public partial class TmxMapReaderTests
|
||||||
Template ResolveTemplate(string source)
|
Template ResolveTemplate(string source)
|
||||||
{
|
{
|
||||||
using var xmlTemplateReader = TestData.GetXmlReaderFor($"{fileDir}/{source}");
|
using var xmlTemplateReader = TestData.GetXmlReaderFor($"{fileDir}/{source}");
|
||||||
using var templateReader = new TxTemplateReader(xmlTemplateReader, ResolveTileset, ResolveTemplate, customTypeDefinitions);
|
using var templateReader = new TxTemplateReader(xmlTemplateReader, ResolveTileset, ResolveTemplate, ResolveCustomType);
|
||||||
return templateReader.ReadTemplate();
|
return templateReader.ReadTemplate();
|
||||||
}
|
}
|
||||||
Tileset ResolveTileset(string source)
|
Tileset ResolveTileset(string source)
|
||||||
{
|
{
|
||||||
using var xmlTilesetReader = TestData.GetXmlReaderFor($"{fileDir}/{source}");
|
using var xmlTilesetReader = TestData.GetXmlReaderFor($"{fileDir}/{source}");
|
||||||
using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTemplate, customTypeDefinitions);
|
using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTileset, ResolveTemplate, ResolveCustomType);
|
||||||
return tilesetReader.ReadTileset();
|
return tilesetReader.ReadTileset();
|
||||||
}
|
}
|
||||||
using var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate, customTypeDefinitions);
|
ICustomTypeDefinition ResolveCustomType(string name)
|
||||||
|
{
|
||||||
|
return customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name)!;
|
||||||
|
}
|
||||||
|
using var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate, ResolveCustomType);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var map = mapReader.ReadMap();
|
var map = mapReader.ReadMap();
|
||||||
|
|
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);
|
||||||
|
}
|
|
@ -43,5 +43,10 @@ public enum PropertyType
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A class property.
|
/// A class property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Class
|
Class,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An enum property.
|
||||||
|
/// </summary>
|
||||||
|
Enum
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<ICustomTypeDefinition> 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
|
|
||||||
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(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,67 +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 List<IProperty> ReadProperties(
|
|
||||||
XmlReader reader,
|
|
||||||
IReadOnlyCollection<ICustomTypeDefinition> 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 property;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static ClassProperty ReadClassProperty(
|
|
||||||
XmlReader reader,
|
|
||||||
IReadOnlyCollection<ICustomTypeDefinition> 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 = Helpers.CreateInstanceOfCustomClass(ccd);
|
|
||||||
var props = ReadProperties(reader, customTypeDefinitions);
|
|
||||||
var mergedProps = Helpers.MergeProperties(propsInType, props);
|
|
||||||
|
|
||||||
reader.ReadEndElement();
|
|
||||||
return new ClassProperty { Name = name, PropertyType = propertyType, Value = mergedProps };
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new XmlException($"Unkonwn custom class definition: {propertyType}");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<ICustomTypeDefinition> 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;
|
|
||||||
|
|
||||||
List<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<ICustomTypeDefinition> 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;
|
|
||||||
|
|
||||||
List<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<ICustomTypeDefinition> 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;
|
|
||||||
|
|
||||||
List<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<ICustomTypeDefinition> 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;
|
|
||||||
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(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<ICustomTypeDefinition> 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
|
|
||||||
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(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<ICustomTypeDefinition> customTypeDefinitions) =>
|
|
||||||
reader.ReadList<Wangset>("wangsets", "wangset", r => ReadWangset(r, customTypeDefinitions));
|
|
||||||
|
|
||||||
internal static Wangset ReadWangset(
|
|
||||||
XmlReader reader,
|
|
||||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
|
||||||
// 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(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<ICustomTypeDefinition> 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
|
|
||||||
List<IProperty>? properties = null;
|
|
||||||
|
|
||||||
reader.ProcessChildren("wangcolor", (r, elementName) => elementName switch
|
|
||||||
{
|
|
||||||
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"),
|
|
||||||
_ => r.Skip
|
|
||||||
});
|
|
||||||
|
|
||||||
return new WangColor
|
|
||||||
{
|
|
||||||
Name = name,
|
|
||||||
Class = @class,
|
|
||||||
Color = color,
|
|
||||||
Tile = tile,
|
|
||||||
Probability = probability,
|
|
||||||
Properties = properties ?? []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static WangTile ReadWangTile(XmlReader reader)
|
|
||||||
{
|
|
||||||
// Attributes
|
|
||||||
var tileID = reader.GetRequiredAttributeParseable<uint>("tileid");
|
|
||||||
var wangID = reader.GetRequiredAttributeParseable<byte[]>("wangid", s =>
|
|
||||||
{
|
|
||||||
// Comma-separated list of indices (0-254)
|
|
||||||
var indices = s.Split(',').Select(i => byte.Parse(i, CultureInfo.InvariantCulture)).ToArray();
|
|
||||||
if (indices.Length > 8)
|
|
||||||
throw new ArgumentException("Wang ID can have at most 8 indices.");
|
|
||||||
return indices;
|
|
||||||
});
|
|
||||||
|
|
||||||
reader.ReadStartElement("wangtile");
|
|
||||||
|
|
||||||
return new WangTile
|
|
||||||
{
|
|
||||||
TileID = tileID,
|
|
||||||
WangID = wangID
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using DotTiled.Model;
|
using DotTiled.Model;
|
||||||
|
|
||||||
|
@ -8,72 +7,20 @@ namespace DotTiled.Serialization.Tmx;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A map reader for the Tiled XML format.
|
/// A map reader for the Tiled XML format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TmxMapReader : IMapReader
|
public class TmxMapReader : TmxReaderBase, IMapReader
|
||||||
{
|
{
|
||||||
// External resolvers
|
|
||||||
private readonly Func<string, Tileset> _externalTilesetResolver;
|
|
||||||
private readonly Func<string, Template> _externalTemplateResolver;
|
|
||||||
|
|
||||||
private readonly XmlReader _reader;
|
|
||||||
private bool disposedValue;
|
|
||||||
|
|
||||||
private readonly IReadOnlyCollection<ICustomTypeDefinition> _customTypeDefinitions;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs a new <see cref="TmxMapReader"/>.
|
/// Constructs a new <see cref="TmxMapReader"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="reader">An XML reader for reading a Tiled map in the Tiled XML format.</param>
|
/// <inheritdoc />
|
||||||
/// <param name="externalTilesetResolver">A function that resolves external tilesets given their source.</param>
|
|
||||||
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
|
|
||||||
/// <param name="customTypeDefinitions">A collection of custom type definitions that can be used to resolve custom types when encountering <see cref="ClassProperty"/>.</param>
|
|
||||||
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
|
|
||||||
public TmxMapReader(
|
public TmxMapReader(
|
||||||
XmlReader reader,
|
XmlReader reader,
|
||||||
Func<string, Tileset> externalTilesetResolver,
|
Func<string, Tileset> externalTilesetResolver,
|
||||||
Func<string, Template> externalTemplateResolver,
|
Func<string, Template> externalTemplateResolver,
|
||||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||||
{
|
reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||||
_reader = reader ?? throw new ArgumentNullException(nameof(reader));
|
{ }
|
||||||
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
|
||||||
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
|
||||||
_customTypeDefinitions = customTypeDefinitions ?? throw new ArgumentNullException(nameof(customTypeDefinitions));
|
|
||||||
|
|
||||||
// Prepare reader
|
|
||||||
_ = _reader.MoveToContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Map ReadMap() => Tmx.ReadMap(_reader, _externalTilesetResolver, _externalTemplateResolver, _customTypeDefinitions);
|
public new Map ReadMap() => base.ReadMap();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!disposedValue)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
// TODO: dispose managed state (managed objects)
|
|
||||||
_reader.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
|
||||||
// TODO: set large fields to null
|
|
||||||
disposedValue = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
|
||||||
// ~TmxTiledMapReader()
|
|
||||||
// {
|
|
||||||
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
||||||
// Dispose(disposing: false);
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
||||||
Dispose(disposing: true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,26 @@
|
||||||
using System.Xml;
|
|
||||||
using DotTiled.Model;
|
using DotTiled.Model;
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmx;
|
namespace DotTiled.Serialization.Tmx;
|
||||||
|
|
||||||
internal partial class Tmx
|
public abstract partial class TmxReaderBase
|
||||||
{
|
{
|
||||||
internal static Chunk ReadChunk(XmlReader reader, DataEncoding? encoding, DataCompression? compression)
|
internal Chunk ReadChunk(DataEncoding? encoding, DataCompression? compression)
|
||||||
{
|
{
|
||||||
var x = reader.GetRequiredAttributeParseable<int>("x");
|
var x = _reader.GetRequiredAttributeParseable<int>("x");
|
||||||
var y = reader.GetRequiredAttributeParseable<int>("y");
|
var y = _reader.GetRequiredAttributeParseable<int>("y");
|
||||||
var width = reader.GetRequiredAttributeParseable<uint>("width");
|
var width = _reader.GetRequiredAttributeParseable<uint>("width");
|
||||||
var height = reader.GetRequiredAttributeParseable<uint>("height");
|
var height = _reader.GetRequiredAttributeParseable<uint>("height");
|
||||||
|
|
||||||
var usesTileChildrenInsteadOfRawData = encoding is null;
|
var usesTileChildrenInsteadOfRawData = encoding is null;
|
||||||
if (usesTileChildrenInsteadOfRawData)
|
if (usesTileChildrenInsteadOfRawData)
|
||||||
{
|
{
|
||||||
var globalTileIDsWithFlippingFlags = ReadTileChildrenInWrapper("chunk", reader);
|
var globalTileIDsWithFlippingFlags = ReadTileChildrenInWrapper("chunk", _reader);
|
||||||
var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(globalTileIDsWithFlippingFlags);
|
var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(globalTileIDsWithFlippingFlags);
|
||||||
return new Chunk { X = x, Y = y, Width = width, Height = height, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags };
|
return new Chunk { X = x, Y = y, Width = width, Height = height, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags };
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var globalTileIDsWithFlippingFlags = ReadRawData(reader, encoding!.Value, compression);
|
var globalTileIDsWithFlippingFlags = ReadRawData(_reader, encoding!.Value, compression);
|
||||||
var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(globalTileIDsWithFlippingFlags);
|
var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(globalTileIDsWithFlippingFlags);
|
||||||
return new Chunk { X = x, Y = y, Width = width, Height = height, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags };
|
return new Chunk { X = x, Y = y, Width = width, Height = height, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags };
|
||||||
}
|
}
|
|
@ -8,17 +8,17 @@ using DotTiled.Model;
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmx;
|
namespace DotTiled.Serialization.Tmx;
|
||||||
|
|
||||||
internal partial class Tmx
|
public abstract partial class TmxReaderBase
|
||||||
{
|
{
|
||||||
internal static Data ReadData(XmlReader reader, bool usesChunks)
|
internal Data ReadData(bool usesChunks)
|
||||||
{
|
{
|
||||||
var encoding = reader.GetOptionalAttributeEnum<DataEncoding>("encoding", e => e switch
|
var encoding = _reader.GetOptionalAttributeEnum<DataEncoding>("encoding", e => e switch
|
||||||
{
|
{
|
||||||
"csv" => DataEncoding.Csv,
|
"csv" => DataEncoding.Csv,
|
||||||
"base64" => DataEncoding.Base64,
|
"base64" => DataEncoding.Base64,
|
||||||
_ => throw new XmlException("Invalid encoding")
|
_ => throw new XmlException("Invalid encoding")
|
||||||
});
|
});
|
||||||
var compression = reader.GetOptionalAttributeEnum<DataCompression>("compression", c => c switch
|
var compression = _reader.GetOptionalAttributeEnum<DataCompression>("compression", c => c switch
|
||||||
{
|
{
|
||||||
"gzip" => DataCompression.GZip,
|
"gzip" => DataCompression.GZip,
|
||||||
"zlib" => DataCompression.ZLib,
|
"zlib" => DataCompression.ZLib,
|
||||||
|
@ -28,8 +28,8 @@ internal partial class Tmx
|
||||||
|
|
||||||
if (usesChunks)
|
if (usesChunks)
|
||||||
{
|
{
|
||||||
var chunks = reader
|
var chunks = _reader
|
||||||
.ReadList("data", "chunk", (r) => ReadChunk(r, encoding, compression))
|
.ReadList("data", "chunk", (r) => ReadChunk(encoding, compression))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = null, Chunks = chunks };
|
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = null, Chunks = chunks };
|
||||||
}
|
}
|
||||||
|
@ -37,12 +37,12 @@ internal partial class Tmx
|
||||||
var usesTileChildrenInsteadOfRawData = encoding is null && compression is null;
|
var usesTileChildrenInsteadOfRawData = encoding is null && compression is null;
|
||||||
if (usesTileChildrenInsteadOfRawData)
|
if (usesTileChildrenInsteadOfRawData)
|
||||||
{
|
{
|
||||||
var tileChildrenGlobalTileIDsWithFlippingFlags = ReadTileChildrenInWrapper("data", reader);
|
var tileChildrenGlobalTileIDsWithFlippingFlags = ReadTileChildrenInWrapper("data", _reader);
|
||||||
var (tileChildrenGlobalTileIDs, tileChildrenFlippingFlags) = ReadAndClearFlippingFlagsFromGIDs(tileChildrenGlobalTileIDsWithFlippingFlags);
|
var (tileChildrenGlobalTileIDs, tileChildrenFlippingFlags) = ReadAndClearFlippingFlagsFromGIDs(tileChildrenGlobalTileIDsWithFlippingFlags);
|
||||||
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = tileChildrenGlobalTileIDs, FlippingFlags = tileChildrenFlippingFlags, Chunks = null };
|
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = tileChildrenGlobalTileIDs, FlippingFlags = tileChildrenFlippingFlags, Chunks = null };
|
||||||
}
|
}
|
||||||
|
|
||||||
var rawDataGlobalTileIDsWithFlippingFlags = ReadRawData(reader, encoding!.Value, compression);
|
var rawDataGlobalTileIDsWithFlippingFlags = ReadRawData(_reader, encoding!.Value, compression);
|
||||||
var (rawDataGlobalTileIDs, rawDataFlippingFlags) = ReadAndClearFlippingFlagsFromGIDs(rawDataGlobalTileIDsWithFlippingFlags);
|
var (rawDataGlobalTileIDs, rawDataFlippingFlags) = ReadAndClearFlippingFlagsFromGIDs(rawDataGlobalTileIDsWithFlippingFlags);
|
||||||
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = rawDataGlobalTileIDs, FlippingFlags = rawDataFlippingFlags, Chunks = null };
|
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = rawDataGlobalTileIDs, FlippingFlags = rawDataFlippingFlags, Chunks = null };
|
||||||
}
|
}
|
104
src/DotTiled/Serialization/Tmx/TmxReaderBase.Map.cs
Normal file
104
src/DotTiled/Serialization/Tmx/TmxReaderBase.Map.cs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using DotTiled.Model;
|
||||||
|
|
||||||
|
namespace DotTiled.Serialization.Tmx;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for Tiled XML format readers.
|
||||||
|
/// </summary>
|
||||||
|
public abstract partial class TmxReaderBase
|
||||||
|
{
|
||||||
|
internal Map ReadMap()
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var version = _reader.GetRequiredAttribute("version");
|
||||||
|
var tiledVersion = _reader.GetRequiredAttribute("tiledversion");
|
||||||
|
var @class = _reader.GetOptionalAttribute("class") ?? "";
|
||||||
|
var orientation = _reader.GetRequiredAttributeEnum<MapOrientation>("orientation", s => s switch
|
||||||
|
{
|
||||||
|
"orthogonal" => MapOrientation.Orthogonal,
|
||||||
|
"isometric" => MapOrientation.Isometric,
|
||||||
|
"staggered" => MapOrientation.Staggered,
|
||||||
|
"hexagonal" => MapOrientation.Hexagonal,
|
||||||
|
_ => throw new InvalidOperationException($"Unknown orientation '{s}'")
|
||||||
|
});
|
||||||
|
var renderOrder = _reader.GetOptionalAttributeEnum<RenderOrder>("renderorder", s => s switch
|
||||||
|
{
|
||||||
|
"right-down" => RenderOrder.RightDown,
|
||||||
|
"right-up" => RenderOrder.RightUp,
|
||||||
|
"left-down" => RenderOrder.LeftDown,
|
||||||
|
"left-up" => RenderOrder.LeftUp,
|
||||||
|
_ => throw new InvalidOperationException($"Unknown render order '{s}'")
|
||||||
|
}) ?? RenderOrder.RightDown;
|
||||||
|
var compressionLevel = _reader.GetOptionalAttributeParseable<int>("compressionlevel") ?? -1;
|
||||||
|
var width = _reader.GetRequiredAttributeParseable<uint>("width");
|
||||||
|
var height = _reader.GetRequiredAttributeParseable<uint>("height");
|
||||||
|
var tileWidth = _reader.GetRequiredAttributeParseable<uint>("tilewidth");
|
||||||
|
var tileHeight = _reader.GetRequiredAttributeParseable<uint>("tileheight");
|
||||||
|
var hexSideLength = _reader.GetOptionalAttributeParseable<uint>("hexsidelength");
|
||||||
|
var staggerAxis = _reader.GetOptionalAttributeEnum<StaggerAxis>("staggeraxis", s => s switch
|
||||||
|
{
|
||||||
|
"x" => StaggerAxis.X,
|
||||||
|
"y" => StaggerAxis.Y,
|
||||||
|
_ => throw new InvalidOperationException($"Unknown stagger axis '{s}'")
|
||||||
|
});
|
||||||
|
var staggerIndex = _reader.GetOptionalAttributeEnum<StaggerIndex>("staggerindex", s => s switch
|
||||||
|
{
|
||||||
|
"odd" => StaggerIndex.Odd,
|
||||||
|
"even" => StaggerIndex.Even,
|
||||||
|
_ => throw new InvalidOperationException($"Unknown stagger index '{s}'")
|
||||||
|
});
|
||||||
|
var parallaxOriginX = _reader.GetOptionalAttributeParseable<float>("parallaxoriginx") ?? 0.0f;
|
||||||
|
var parallaxOriginY = _reader.GetOptionalAttributeParseable<float>("parallaxoriginy") ?? 0.0f;
|
||||||
|
var backgroundColor = _reader.GetOptionalAttributeClass<Color>("backgroundcolor") ?? Color.Parse("#00000000", CultureInfo.InvariantCulture);
|
||||||
|
var nextLayerID = _reader.GetRequiredAttributeParseable<uint>("nextlayerid");
|
||||||
|
var nextObjectID = _reader.GetRequiredAttributeParseable<uint>("nextobjectid");
|
||||||
|
var infinite = (_reader.GetOptionalAttributeParseable<uint>("infinite") ?? 0) == 1;
|
||||||
|
|
||||||
|
// At most one of
|
||||||
|
List<IProperty>? properties = null;
|
||||||
|
|
||||||
|
// Any number of
|
||||||
|
List<BaseLayer> layers = [];
|
||||||
|
List<Tileset> tilesets = [];
|
||||||
|
|
||||||
|
_reader.ProcessChildren("map", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(), "Properties"),
|
||||||
|
"tileset" => () => tilesets.Add(ReadTileset()),
|
||||||
|
"layer" => () => layers.Add(ReadTileLayer(infinite)),
|
||||||
|
"objectgroup" => () => layers.Add(ReadObjectLayer()),
|
||||||
|
"imagelayer" => () => layers.Add(ReadImageLayer()),
|
||||||
|
"group" => () => layers.Add(ReadGroup()),
|
||||||
|
_ => r.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Map
|
||||||
|
{
|
||||||
|
Version = version,
|
||||||
|
TiledVersion = tiledVersion,
|
||||||
|
Class = @class,
|
||||||
|
Orientation = orientation,
|
||||||
|
RenderOrder = renderOrder,
|
||||||
|
CompressionLevel = compressionLevel,
|
||||||
|
Width = width,
|
||||||
|
Height = height,
|
||||||
|
TileWidth = tileWidth,
|
||||||
|
TileHeight = tileHeight,
|
||||||
|
HexSideLength = hexSideLength,
|
||||||
|
StaggerAxis = staggerAxis,
|
||||||
|
StaggerIndex = staggerIndex,
|
||||||
|
ParallaxOriginX = parallaxOriginX,
|
||||||
|
ParallaxOriginY = parallaxOriginY,
|
||||||
|
BackgroundColor = backgroundColor,
|
||||||
|
NextLayerID = nextLayerID,
|
||||||
|
NextObjectID = nextObjectID,
|
||||||
|
Infinite = infinite,
|
||||||
|
Properties = properties ?? [],
|
||||||
|
Tilesets = tilesets,
|
||||||
|
Layers = layers
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,35 +3,31 @@ using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Xml;
|
|
||||||
using DotTiled.Model;
|
using DotTiled.Model;
|
||||||
|
|
||||||
namespace DotTiled.Serialization.Tmx;
|
namespace DotTiled.Serialization.Tmx;
|
||||||
|
|
||||||
internal partial class Tmx
|
public abstract partial class TmxReaderBase
|
||||||
{
|
{
|
||||||
internal static ObjectLayer ReadObjectLayer(
|
internal ObjectLayer ReadObjectLayer()
|
||||||
XmlReader reader,
|
|
||||||
Func<string, Template> externalTemplateResolver,
|
|
||||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
{
|
||||||
// Attributes
|
// Attributes
|
||||||
var id = reader.GetRequiredAttributeParseable<uint>("id");
|
var id = _reader.GetRequiredAttributeParseable<uint>("id");
|
||||||
var name = reader.GetOptionalAttribute("name") ?? "";
|
var name = _reader.GetOptionalAttribute("name") ?? "";
|
||||||
var @class = reader.GetOptionalAttribute("class") ?? "";
|
var @class = _reader.GetOptionalAttribute("class") ?? "";
|
||||||
var x = reader.GetOptionalAttributeParseable<uint>("x") ?? 0;
|
var x = _reader.GetOptionalAttributeParseable<uint>("x") ?? 0;
|
||||||
var y = reader.GetOptionalAttributeParseable<uint>("y") ?? 0;
|
var y = _reader.GetOptionalAttributeParseable<uint>("y") ?? 0;
|
||||||
var width = reader.GetOptionalAttributeParseable<uint>("width");
|
var width = _reader.GetOptionalAttributeParseable<uint>("width");
|
||||||
var height = reader.GetOptionalAttributeParseable<uint>("height");
|
var height = _reader.GetOptionalAttributeParseable<uint>("height");
|
||||||
var opacity = reader.GetOptionalAttributeParseable<float>("opacity") ?? 1.0f;
|
var opacity = _reader.GetOptionalAttributeParseable<float>("opacity") ?? 1.0f;
|
||||||
var visible = reader.GetOptionalAttributeParseable<bool>("visible") ?? true;
|
var visible = _reader.GetOptionalAttributeParseable<bool>("visible") ?? true;
|
||||||
var tintColor = reader.GetOptionalAttributeClass<Color>("tintcolor");
|
var tintColor = _reader.GetOptionalAttributeClass<Color>("tintcolor");
|
||||||
var offsetX = reader.GetOptionalAttributeParseable<float>("offsetx") ?? 0.0f;
|
var offsetX = _reader.GetOptionalAttributeParseable<float>("offsetx") ?? 0.0f;
|
||||||
var offsetY = reader.GetOptionalAttributeParseable<float>("offsety") ?? 0.0f;
|
var offsetY = _reader.GetOptionalAttributeParseable<float>("offsety") ?? 0.0f;
|
||||||
var parallaxX = reader.GetOptionalAttributeParseable<float>("parallaxx") ?? 1.0f;
|
var parallaxX = _reader.GetOptionalAttributeParseable<float>("parallaxx") ?? 1.0f;
|
||||||
var parallaxY = reader.GetOptionalAttributeParseable<float>("parallaxy") ?? 1.0f;
|
var parallaxY = _reader.GetOptionalAttributeParseable<float>("parallaxy") ?? 1.0f;
|
||||||
var color = reader.GetOptionalAttributeClass<Color>("color");
|
var color = _reader.GetOptionalAttributeClass<Color>("color");
|
||||||
var drawOrder = reader.GetOptionalAttributeEnum<DrawOrder>("draworder", s => s switch
|
var drawOrder = _reader.GetOptionalAttributeEnum<DrawOrder>("draworder", s => s switch
|
||||||
{
|
{
|
||||||
"topdown" => DrawOrder.TopDown,
|
"topdown" => DrawOrder.TopDown,
|
||||||
"index" => DrawOrder.Index,
|
"index" => DrawOrder.Index,
|
||||||
|
@ -42,10 +38,10 @@ internal partial class Tmx
|
||||||
List<IProperty>? properties = null;
|
List<IProperty>? properties = null;
|
||||||
List<Model.Object> objects = [];
|
List<Model.Object> objects = [];
|
||||||
|
|
||||||
reader.ProcessChildren("objectgroup", (r, elementName) => elementName switch
|
_reader.ProcessChildren("objectgroup", (r, elementName) => elementName switch
|
||||||
{
|
{
|
||||||
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"),
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(), "Properties"),
|
||||||
"object" => () => objects.Add(ReadObject(r, externalTemplateResolver, customTypeDefinitions)),
|
"object" => () => objects.Add(ReadObject()),
|
||||||
_ => r.Skip
|
_ => r.Skip
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -72,16 +68,13 @@ internal partial class Tmx
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Model.Object ReadObject(
|
internal Model.Object ReadObject()
|
||||||
XmlReader reader,
|
|
||||||
Func<string, Template> externalTemplateResolver,
|
|
||||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
{
|
||||||
// Attributes
|
// Attributes
|
||||||
var template = reader.GetOptionalAttribute("template");
|
var template = _reader.GetOptionalAttribute("template");
|
||||||
Model.Object? obj = null;
|
Model.Object? obj = null;
|
||||||
if (template is not null)
|
if (template is not null)
|
||||||
obj = externalTemplateResolver(template).Object;
|
obj = _externalTemplateResolver(template).Object;
|
||||||
|
|
||||||
uint? idDefault = obj?.ID ?? null;
|
uint? idDefault = obj?.ID ?? null;
|
||||||
string nameDefault = obj?.Name ?? "";
|
string nameDefault = obj?.Name ?? "";
|
||||||
|
@ -95,30 +88,30 @@ internal partial class Tmx
|
||||||
bool visibleDefault = obj?.Visible ?? true;
|
bool visibleDefault = obj?.Visible ?? true;
|
||||||
List<IProperty>? propertiesDefault = obj?.Properties ?? null;
|
List<IProperty>? propertiesDefault = obj?.Properties ?? null;
|
||||||
|
|
||||||
var id = reader.GetOptionalAttributeParseable<uint>("id") ?? idDefault;
|
var id = _reader.GetOptionalAttributeParseable<uint>("id") ?? idDefault;
|
||||||
var name = reader.GetOptionalAttribute("name") ?? nameDefault;
|
var name = _reader.GetOptionalAttribute("name") ?? nameDefault;
|
||||||
var type = reader.GetOptionalAttribute("type") ?? typeDefault;
|
var type = _reader.GetOptionalAttribute("type") ?? typeDefault;
|
||||||
var x = reader.GetOptionalAttributeParseable<float>("x") ?? xDefault;
|
var x = _reader.GetOptionalAttributeParseable<float>("x") ?? xDefault;
|
||||||
var y = reader.GetOptionalAttributeParseable<float>("y") ?? yDefault;
|
var y = _reader.GetOptionalAttributeParseable<float>("y") ?? yDefault;
|
||||||
var width = reader.GetOptionalAttributeParseable<float>("width") ?? widthDefault;
|
var width = _reader.GetOptionalAttributeParseable<float>("width") ?? widthDefault;
|
||||||
var height = reader.GetOptionalAttributeParseable<float>("height") ?? heightDefault;
|
var height = _reader.GetOptionalAttributeParseable<float>("height") ?? heightDefault;
|
||||||
var rotation = reader.GetOptionalAttributeParseable<float>("rotation") ?? rotationDefault;
|
var rotation = _reader.GetOptionalAttributeParseable<float>("rotation") ?? rotationDefault;
|
||||||
var gid = reader.GetOptionalAttributeParseable<uint>("gid") ?? gidDefault;
|
var gid = _reader.GetOptionalAttributeParseable<uint>("gid") ?? gidDefault;
|
||||||
var visible = reader.GetOptionalAttributeParseable<bool>("visible") ?? visibleDefault;
|
var visible = _reader.GetOptionalAttributeParseable<bool>("visible") ?? visibleDefault;
|
||||||
|
|
||||||
// Elements
|
// Elements
|
||||||
Model.Object? foundObject = null;
|
Model.Object? foundObject = null;
|
||||||
int propertiesCounter = 0;
|
int propertiesCounter = 0;
|
||||||
List<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)).ToList(), "Properties", ref propertiesCounter),
|
"properties" => () => Helpers.SetAtMostOnceUsingCounter(ref properties, Helpers.MergeProperties(properties, ReadProperties()).ToList(), "Properties", ref propertiesCounter),
|
||||||
"ellipse" => () => Helpers.SetAtMostOnce(ref foundObject, ReadEllipseObject(r), "Object marker"),
|
"ellipse" => () => Helpers.SetAtMostOnce(ref foundObject, ReadEllipseObject(), "Object marker"),
|
||||||
"point" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPointObject(r), "Object marker"),
|
"point" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPointObject(), "Object marker"),
|
||||||
"polygon" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPolygonObject(r), "Object marker"),
|
"polygon" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPolygonObject(), "Object marker"),
|
||||||
"polyline" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPolylineObject(r), "Object marker"),
|
"polyline" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPolylineObject(), "Object marker"),
|
||||||
"text" => () => Helpers.SetAtMostOnce(ref foundObject, ReadTextObject(r), "Object marker"),
|
"text" => () => Helpers.SetAtMostOnce(ref foundObject, ReadTextObject(), "Object marker"),
|
||||||
_ => throw new InvalidOperationException($"Unknown object marker '{elementName}'")
|
_ => throw new InvalidOperationException($"Unknown object marker '{elementName}'")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -169,26 +162,26 @@ internal partial class Tmx
|
||||||
return OverrideObject((dynamic)obj, (dynamic)foundObject);
|
return OverrideObject((dynamic)obj, (dynamic)foundObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static EllipseObject ReadEllipseObject(XmlReader reader)
|
internal EllipseObject ReadEllipseObject()
|
||||||
{
|
{
|
||||||
reader.Skip();
|
_reader.Skip();
|
||||||
return new EllipseObject { };
|
return new EllipseObject { };
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static EllipseObject OverrideObject(EllipseObject obj, EllipseObject _) => obj;
|
internal static EllipseObject OverrideObject(EllipseObject obj, EllipseObject _) => obj;
|
||||||
|
|
||||||
internal static PointObject ReadPointObject(XmlReader reader)
|
internal PointObject ReadPointObject()
|
||||||
{
|
{
|
||||||
reader.Skip();
|
_reader.Skip();
|
||||||
return new PointObject { };
|
return new PointObject { };
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static PointObject OverrideObject(PointObject obj, PointObject _) => obj;
|
internal static PointObject OverrideObject(PointObject obj, PointObject _) => obj;
|
||||||
|
|
||||||
internal static PolygonObject ReadPolygonObject(XmlReader reader)
|
internal PolygonObject ReadPolygonObject()
|
||||||
{
|
{
|
||||||
// Attributes
|
// Attributes
|
||||||
var points = reader.GetRequiredAttributeParseable<List<Vector2>>("points", s =>
|
var points = _reader.GetRequiredAttributeParseable<List<Vector2>>("points", s =>
|
||||||
{
|
{
|
||||||
// Takes on format "x1,y1 x2,y2 x3,y3 ..."
|
// Takes on format "x1,y1 x2,y2 x3,y3 ..."
|
||||||
var coords = s.Split(' ');
|
var coords = s.Split(' ');
|
||||||
|
@ -199,7 +192,7 @@ internal partial class Tmx
|
||||||
}).ToList();
|
}).ToList();
|
||||||
});
|
});
|
||||||
|
|
||||||
reader.ReadStartElement("polygon");
|
_reader.ReadStartElement("polygon");
|
||||||
return new PolygonObject { Points = points };
|
return new PolygonObject { Points = points };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,10 +202,10 @@ internal partial class Tmx
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static PolylineObject ReadPolylineObject(XmlReader reader)
|
internal PolylineObject ReadPolylineObject()
|
||||||
{
|
{
|
||||||
// Attributes
|
// Attributes
|
||||||
var points = reader.GetRequiredAttributeParseable<List<Vector2>>("points", s =>
|
var points = _reader.GetRequiredAttributeParseable<List<Vector2>>("points", s =>
|
||||||
{
|
{
|
||||||
// Takes on format "x1,y1 x2,y2 x3,y3 ..."
|
// Takes on format "x1,y1 x2,y2 x3,y3 ..."
|
||||||
var coords = s.Split(' ');
|
var coords = s.Split(' ');
|
||||||
|
@ -223,7 +216,7 @@ internal partial class Tmx
|
||||||
}).ToList();
|
}).ToList();
|
||||||
});
|
});
|
||||||
|
|
||||||
reader.ReadStartElement("polyline");
|
_reader.ReadStartElement("polyline");
|
||||||
return new PolylineObject { Points = points };
|
return new PolylineObject { Points = points };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,19 +226,19 @@ internal partial class Tmx
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static TextObject ReadTextObject(XmlReader reader)
|
internal TextObject ReadTextObject()
|
||||||
{
|
{
|
||||||
// Attributes
|
// Attributes
|
||||||
var fontFamily = reader.GetOptionalAttribute("fontfamily") ?? "sans-serif";
|
var fontFamily = _reader.GetOptionalAttribute("fontfamily") ?? "sans-serif";
|
||||||
var pixelSize = reader.GetOptionalAttributeParseable<int>("pixelsize") ?? 16;
|
var pixelSize = _reader.GetOptionalAttributeParseable<int>("pixelsize") ?? 16;
|
||||||
var wrap = reader.GetOptionalAttributeParseable<bool>("wrap") ?? false;
|
var wrap = _reader.GetOptionalAttributeParseable<bool>("wrap") ?? false;
|
||||||
var color = reader.GetOptionalAttributeClass<Color>("color") ?? Color.Parse("#000000", CultureInfo.InvariantCulture);
|
var color = _reader.GetOptionalAttributeClass<Color>("color") ?? Color.Parse("#000000", CultureInfo.InvariantCulture);
|
||||||
var bold = reader.GetOptionalAttributeParseable<bool>("bold") ?? false;
|
var bold = _reader.GetOptionalAttributeParseable<bool>("bold") ?? false;
|
||||||
var italic = reader.GetOptionalAttributeParseable<bool>("italic") ?? false;
|
var italic = _reader.GetOptionalAttributeParseable<bool>("italic") ?? false;
|
||||||
var underline = reader.GetOptionalAttributeParseable<bool>("underline") ?? false;
|
var underline = _reader.GetOptionalAttributeParseable<bool>("underline") ?? false;
|
||||||
var strikeout = reader.GetOptionalAttributeParseable<bool>("strikeout") ?? false;
|
var strikeout = _reader.GetOptionalAttributeParseable<bool>("strikeout") ?? false;
|
||||||
var kerning = reader.GetOptionalAttributeParseable<bool>("kerning") ?? true;
|
var kerning = _reader.GetOptionalAttributeParseable<bool>("kerning") ?? true;
|
||||||
var hAlign = reader.GetOptionalAttributeEnum<TextHorizontalAlignment>("halign", s => s switch
|
var hAlign = _reader.GetOptionalAttributeEnum<TextHorizontalAlignment>("halign", s => s switch
|
||||||
{
|
{
|
||||||
"left" => TextHorizontalAlignment.Left,
|
"left" => TextHorizontalAlignment.Left,
|
||||||
"center" => TextHorizontalAlignment.Center,
|
"center" => TextHorizontalAlignment.Center,
|
||||||
|
@ -253,7 +246,7 @@ internal partial class Tmx
|
||||||
"justify" => TextHorizontalAlignment.Justify,
|
"justify" => TextHorizontalAlignment.Justify,
|
||||||
_ => throw new InvalidOperationException($"Unknown horizontal alignment '{s}'")
|
_ => throw new InvalidOperationException($"Unknown horizontal alignment '{s}'")
|
||||||
}) ?? TextHorizontalAlignment.Left;
|
}) ?? TextHorizontalAlignment.Left;
|
||||||
var vAlign = reader.GetOptionalAttributeEnum<TextVerticalAlignment>("valign", s => s switch
|
var vAlign = _reader.GetOptionalAttributeEnum<TextVerticalAlignment>("valign", s => s switch
|
||||||
{
|
{
|
||||||
"top" => TextVerticalAlignment.Top,
|
"top" => TextVerticalAlignment.Top,
|
||||||
"center" => TextVerticalAlignment.Center,
|
"center" => TextVerticalAlignment.Center,
|
||||||
|
@ -262,7 +255,7 @@ internal partial class Tmx
|
||||||
}) ?? TextVerticalAlignment.Top;
|
}) ?? TextVerticalAlignment.Top;
|
||||||
|
|
||||||
// Elements
|
// Elements
|
||||||
var text = reader.ReadElementContentAsString("text", "");
|
var text = _reader.ReadElementContentAsString("text", "");
|
||||||
|
|
||||||
return new TextObject
|
return new TextObject
|
||||||
{
|
{
|
||||||
|
@ -304,11 +297,7 @@ internal partial class Tmx
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Template ReadTemplate(
|
internal Template ReadTemplate()
|
||||||
XmlReader reader,
|
|
||||||
Func<string, Tileset> externalTilesetResolver,
|
|
||||||
Func<string, Template> externalTemplateResolver,
|
|
||||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
|
||||||
{
|
{
|
||||||
// No attributes
|
// No attributes
|
||||||
|
|
||||||
|
@ -318,10 +307,10 @@ internal partial class Tmx
|
||||||
// Should contain exactly one of
|
// Should contain exactly one of
|
||||||
Model.Object? obj = null;
|
Model.Object? obj = null;
|
||||||
|
|
||||||
reader.ProcessChildren("template", (r, elementName) => elementName switch
|
_reader.ProcessChildren("template", (r, elementName) => elementName switch
|
||||||
{
|
{
|
||||||
"tileset" => () => Helpers.SetAtMostOnce(ref tileset, ReadTileset(r, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions), "Tileset"),
|
"tileset" => () => Helpers.SetAtMostOnce(ref tileset, ReadTileset(), "Tileset"),
|
||||||
"object" => () => Helpers.SetAtMostOnce(ref obj, ReadObject(r, externalTemplateResolver, customTypeDefinitions), "Object"),
|
"object" => () => Helpers.SetAtMostOnce(ref obj, ReadObject(), "Object"),
|
||||||
_ => r.Skip
|
_ => r.Skip
|
||||||
});
|
});
|
||||||
|
|
140
src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs
Normal file
140
src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
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 => ReadClassProperty(),
|
||||||
|
_ => 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)
|
||||||
|
{
|
||||||
|
_reader.ReadStartElement("property");
|
||||||
|
var propsInType = Helpers.CreateInstanceOfCustomClass(ccd);
|
||||||
|
var props = ReadProperties();
|
||||||
|
var mergedProps = Helpers.MergeProperties(propsInType, props);
|
||||||
|
|
||||||
|
_reader.ReadEndElement();
|
||||||
|
return new ClassProperty { Name = name, PropertyType = propertyType, Value = mergedProps };
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new XmlException($"Unkonwn custom class definition: {propertyType}");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal EnumProperty ReadEnumProperty()
|
||||||
|
{
|
||||||
|
var name = _reader.GetRequiredAttribute("name");
|
||||||
|
var propertyType = _reader.GetRequiredAttribute("propertytype");
|
||||||
|
var typeInXml = _reader.GetOptionalAttributeEnum<PropertyType>("type", (s) => s switch
|
||||||
|
{
|
||||||
|
"string" => PropertyType.String,
|
||||||
|
"int" => PropertyType.Int,
|
||||||
|
_ => throw new XmlException("Invalid property type")
|
||||||
|
}) ?? PropertyType.String;
|
||||||
|
var customTypeDef = _customTypeResolver(propertyType);
|
||||||
|
|
||||||
|
if (customTypeDef is not CustomEnumDefinition ced)
|
||||||
|
throw new XmlException($"Unknown custom enum definition: {propertyType}. Enums must be defined");
|
||||||
|
|
||||||
|
if (ced.StorageType == CustomEnumStorageType.String)
|
||||||
|
{
|
||||||
|
var value = _reader.GetRequiredAttribute("value");
|
||||||
|
if (value.Contains(',') && !ced.ValueAsFlags)
|
||||||
|
throw new XmlException("Enum value must not contain ',' if not ValueAsFlags is set to true.");
|
||||||
|
|
||||||
|
if (ced.ValueAsFlags)
|
||||||
|
{
|
||||||
|
var values = value.Split(',').Select(v => v.Trim()).ToHashSet();
|
||||||
|
return new EnumProperty { Name = name, PropertyType = propertyType, Value = values };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new EnumProperty { Name = name, PropertyType = propertyType, Value = new HashSet<string> { value } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ced.StorageType == CustomEnumStorageType.Int)
|
||||||
|
{
|
||||||
|
var value = _reader.GetRequiredAttributeParseable<int>("value");
|
||||||
|
if (ced.ValueAsFlags)
|
||||||
|
{
|
||||||
|
var allValues = ced.Values;
|
||||||
|
var enumValues = new HashSet<string>();
|
||||||
|
for (var i = 0; i < allValues.Count; i++)
|
||||||
|
{
|
||||||
|
var mask = 1 << i;
|
||||||
|
if ((value & mask) == mask)
|
||||||
|
{
|
||||||
|
var enumValue = allValues[i];
|
||||||
|
_ = enumValues.Add(enumValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new EnumProperty { Name = name, PropertyType = propertyType, Value = enumValues };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var allValues = ced.Values;
|
||||||
|
var enumValue = allValues[value];
|
||||||
|
return new EnumProperty { Name = name, PropertyType = propertyType, Value = new HashSet<string> { enumValue } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new XmlException($"Unknown custom enum storage type: {ced.StorageType}");
|
||||||
|
}
|
||||||
|
}
|
147
src/DotTiled/Serialization/Tmx/TmxReaderBase.TileLayer.cs
Normal file
147
src/DotTiled/Serialization/Tmx/TmxReaderBase.TileLayer.cs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using DotTiled.Model;
|
||||||
|
|
||||||
|
namespace DotTiled.Serialization.Tmx;
|
||||||
|
|
||||||
|
public abstract partial class TmxReaderBase
|
||||||
|
{
|
||||||
|
internal TileLayer ReadTileLayer(bool dataUsesChunks)
|
||||||
|
{
|
||||||
|
var id = _reader.GetRequiredAttributeParseable<uint>("id");
|
||||||
|
var name = _reader.GetOptionalAttribute("name") ?? "";
|
||||||
|
var @class = _reader.GetOptionalAttribute("class") ?? "";
|
||||||
|
var x = _reader.GetOptionalAttributeParseable<uint>("x") ?? 0;
|
||||||
|
var y = _reader.GetOptionalAttributeParseable<uint>("y") ?? 0;
|
||||||
|
var width = _reader.GetRequiredAttributeParseable<uint>("width");
|
||||||
|
var height = _reader.GetRequiredAttributeParseable<uint>("height");
|
||||||
|
var opacity = _reader.GetOptionalAttributeParseable<float>("opacity") ?? 1.0f;
|
||||||
|
var visible = _reader.GetOptionalAttributeParseable<bool>("visible") ?? true;
|
||||||
|
var tintColor = _reader.GetOptionalAttributeClass<Color>("tintcolor");
|
||||||
|
var offsetX = _reader.GetOptionalAttributeParseable<float>("offsetx") ?? 0.0f;
|
||||||
|
var offsetY = _reader.GetOptionalAttributeParseable<float>("offsety") ?? 0.0f;
|
||||||
|
var parallaxX = _reader.GetOptionalAttributeParseable<float>("parallaxx") ?? 1.0f;
|
||||||
|
var parallaxY = _reader.GetOptionalAttributeParseable<float>("parallaxy") ?? 1.0f;
|
||||||
|
|
||||||
|
List<IProperty>? properties = null;
|
||||||
|
Data? data = null;
|
||||||
|
|
||||||
|
_reader.ProcessChildren("layer", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"data" => () => Helpers.SetAtMostOnce(ref data, ReadData(dataUsesChunks), "Data"),
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(), "Properties"),
|
||||||
|
_ => r.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
return new TileLayer
|
||||||
|
{
|
||||||
|
ID = id,
|
||||||
|
Name = name,
|
||||||
|
Class = @class,
|
||||||
|
X = x,
|
||||||
|
Y = y,
|
||||||
|
Width = width,
|
||||||
|
Height = height,
|
||||||
|
Opacity = opacity,
|
||||||
|
Visible = visible,
|
||||||
|
TintColor = tintColor,
|
||||||
|
OffsetX = offsetX,
|
||||||
|
OffsetY = offsetY,
|
||||||
|
ParallaxX = parallaxX,
|
||||||
|
ParallaxY = parallaxY,
|
||||||
|
Data = data,
|
||||||
|
Properties = properties ?? []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ImageLayer ReadImageLayer()
|
||||||
|
{
|
||||||
|
var id = _reader.GetRequiredAttributeParseable<uint>("id");
|
||||||
|
var name = _reader.GetOptionalAttribute("name") ?? "";
|
||||||
|
var @class = _reader.GetOptionalAttribute("class") ?? "";
|
||||||
|
var x = _reader.GetOptionalAttributeParseable<uint>("x") ?? 0;
|
||||||
|
var y = _reader.GetOptionalAttributeParseable<uint>("y") ?? 0;
|
||||||
|
var opacity = _reader.GetOptionalAttributeParseable<float>("opacity") ?? 1.0f;
|
||||||
|
var visible = _reader.GetOptionalAttributeParseable<bool>("visible") ?? true;
|
||||||
|
var tintColor = _reader.GetOptionalAttributeClass<Color>("tintcolor");
|
||||||
|
var offsetX = _reader.GetOptionalAttributeParseable<float>("offsetx") ?? 0.0f;
|
||||||
|
var offsetY = _reader.GetOptionalAttributeParseable<float>("offsety") ?? 0.0f;
|
||||||
|
var parallaxX = _reader.GetOptionalAttributeParseable<float>("parallaxx") ?? 1.0f;
|
||||||
|
var parallaxY = _reader.GetOptionalAttributeParseable<float>("parallaxy") ?? 1.0f;
|
||||||
|
var repeatX = (_reader.GetOptionalAttributeParseable<uint>("repeatx") ?? 0) == 1;
|
||||||
|
var repeatY = (_reader.GetOptionalAttributeParseable<uint>("repeaty") ?? 0) == 1;
|
||||||
|
|
||||||
|
List<IProperty>? properties = null;
|
||||||
|
Image? image = null;
|
||||||
|
|
||||||
|
_reader.ProcessChildren("imagelayer", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"image" => () => Helpers.SetAtMostOnce(ref image, ReadImage(), "Image"),
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(), "Properties"),
|
||||||
|
_ => r.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
return new ImageLayer
|
||||||
|
{
|
||||||
|
ID = id,
|
||||||
|
Name = name,
|
||||||
|
Class = @class,
|
||||||
|
X = x,
|
||||||
|
Y = y,
|
||||||
|
Opacity = opacity,
|
||||||
|
Visible = visible,
|
||||||
|
TintColor = tintColor,
|
||||||
|
OffsetX = offsetX,
|
||||||
|
OffsetY = offsetY,
|
||||||
|
ParallaxX = parallaxX,
|
||||||
|
ParallaxY = parallaxY,
|
||||||
|
Properties = properties ?? [],
|
||||||
|
Image = image,
|
||||||
|
RepeatX = repeatX,
|
||||||
|
RepeatY = repeatY
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Group ReadGroup()
|
||||||
|
{
|
||||||
|
var id = _reader.GetRequiredAttributeParseable<uint>("id");
|
||||||
|
var name = _reader.GetOptionalAttribute("name") ?? "";
|
||||||
|
var @class = _reader.GetOptionalAttribute("class") ?? "";
|
||||||
|
var opacity = _reader.GetOptionalAttributeParseable<float>("opacity") ?? 1.0f;
|
||||||
|
var visible = _reader.GetOptionalAttributeParseable<bool>("visible") ?? true;
|
||||||
|
var tintColor = _reader.GetOptionalAttributeClass<Color>("tintcolor");
|
||||||
|
var offsetX = _reader.GetOptionalAttributeParseable<float>("offsetx") ?? 0.0f;
|
||||||
|
var offsetY = _reader.GetOptionalAttributeParseable<float>("offsety") ?? 0.0f;
|
||||||
|
var parallaxX = _reader.GetOptionalAttributeParseable<float>("parallaxx") ?? 1.0f;
|
||||||
|
var parallaxY = _reader.GetOptionalAttributeParseable<float>("parallaxy") ?? 1.0f;
|
||||||
|
|
||||||
|
List<IProperty>? properties = null;
|
||||||
|
List<BaseLayer> layers = [];
|
||||||
|
|
||||||
|
_reader.ProcessChildren("group", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(), "Properties"),
|
||||||
|
"layer" => () => layers.Add(ReadTileLayer(false)),
|
||||||
|
"objectgroup" => () => layers.Add(ReadObjectLayer()),
|
||||||
|
"imagelayer" => () => layers.Add(ReadImageLayer()),
|
||||||
|
"group" => () => layers.Add(ReadGroup()),
|
||||||
|
_ => r.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Group
|
||||||
|
{
|
||||||
|
ID = id,
|
||||||
|
Name = name,
|
||||||
|
Class = @class,
|
||||||
|
Opacity = opacity,
|
||||||
|
Visible = visible,
|
||||||
|
TintColor = tintColor,
|
||||||
|
OffsetX = offsetX,
|
||||||
|
OffsetY = offsetY,
|
||||||
|
ParallaxX = parallaxX,
|
||||||
|
ParallaxY = parallaxY,
|
||||||
|
Properties = properties ?? [],
|
||||||
|
Layers = layers
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
317
src/DotTiled/Serialization/Tmx/TmxReaderBase.Tileset.cs
Normal file
317
src/DotTiled/Serialization/Tmx/TmxReaderBase.Tileset.cs
Normal file
|
@ -0,0 +1,317 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using DotTiled.Model;
|
||||||
|
|
||||||
|
namespace DotTiled.Serialization.Tmx;
|
||||||
|
|
||||||
|
public abstract partial class TmxReaderBase
|
||||||
|
{
|
||||||
|
internal Tileset ReadTileset()
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var version = _reader.GetOptionalAttribute("version");
|
||||||
|
var tiledVersion = _reader.GetOptionalAttribute("tiledversion");
|
||||||
|
var firstGID = _reader.GetOptionalAttributeParseable<uint>("firstgid");
|
||||||
|
var source = _reader.GetOptionalAttribute("source");
|
||||||
|
var name = _reader.GetOptionalAttribute("name");
|
||||||
|
var @class = _reader.GetOptionalAttribute("class") ?? "";
|
||||||
|
var tileWidth = _reader.GetOptionalAttributeParseable<uint>("tilewidth");
|
||||||
|
var tileHeight = _reader.GetOptionalAttributeParseable<uint>("tileheight");
|
||||||
|
var spacing = _reader.GetOptionalAttributeParseable<uint>("spacing") ?? 0;
|
||||||
|
var margin = _reader.GetOptionalAttributeParseable<uint>("margin") ?? 0;
|
||||||
|
var tileCount = _reader.GetOptionalAttributeParseable<uint>("tilecount");
|
||||||
|
var columns = _reader.GetOptionalAttributeParseable<uint>("columns");
|
||||||
|
var objectAlignment = _reader.GetOptionalAttributeEnum<ObjectAlignment>("objectalignment", s => s switch
|
||||||
|
{
|
||||||
|
"unspecified" => ObjectAlignment.Unspecified,
|
||||||
|
"topleft" => ObjectAlignment.TopLeft,
|
||||||
|
"top" => ObjectAlignment.Top,
|
||||||
|
"topright" => ObjectAlignment.TopRight,
|
||||||
|
"left" => ObjectAlignment.Left,
|
||||||
|
"center" => ObjectAlignment.Center,
|
||||||
|
"right" => ObjectAlignment.Right,
|
||||||
|
"bottomleft" => ObjectAlignment.BottomLeft,
|
||||||
|
"bottom" => ObjectAlignment.Bottom,
|
||||||
|
"bottomright" => ObjectAlignment.BottomRight,
|
||||||
|
_ => throw new InvalidOperationException($"Unknown object alignment '{s}'")
|
||||||
|
}) ?? ObjectAlignment.Unspecified;
|
||||||
|
var renderSize = _reader.GetOptionalAttributeEnum<TileRenderSize>("rendersize", s => s switch
|
||||||
|
{
|
||||||
|
"tile" => TileRenderSize.Tile,
|
||||||
|
"grid" => TileRenderSize.Grid,
|
||||||
|
_ => throw new InvalidOperationException($"Unknown render size '{s}'")
|
||||||
|
}) ?? TileRenderSize.Tile;
|
||||||
|
var fillMode = _reader.GetOptionalAttributeEnum<FillMode>("fillmode", s => s switch
|
||||||
|
{
|
||||||
|
"stretch" => FillMode.Stretch,
|
||||||
|
"preserve-aspect-fit" => FillMode.PreserveAspectFit,
|
||||||
|
_ => throw new InvalidOperationException($"Unknown fill mode '{s}'")
|
||||||
|
}) ?? FillMode.Stretch;
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
Image? image = null;
|
||||||
|
TileOffset? tileOffset = null;
|
||||||
|
Grid? grid = null;
|
||||||
|
List<IProperty>? properties = null;
|
||||||
|
List<Wangset>? wangsets = null;
|
||||||
|
Transformations? transformations = null;
|
||||||
|
List<Tile> tiles = [];
|
||||||
|
|
||||||
|
_reader.ProcessChildren("tileset", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"image" => () => Helpers.SetAtMostOnce(ref image, ReadImage(), "Image"),
|
||||||
|
"tileoffset" => () => Helpers.SetAtMostOnce(ref tileOffset, ReadTileOffset(), "TileOffset"),
|
||||||
|
"grid" => () => Helpers.SetAtMostOnce(ref grid, ReadGrid(), "Grid"),
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(), "Properties"),
|
||||||
|
"wangsets" => () => Helpers.SetAtMostOnce(ref wangsets, ReadWangsets(), "Wangsets"),
|
||||||
|
"transformations" => () => Helpers.SetAtMostOnce(ref transformations, ReadTransformations(), "Transformations"),
|
||||||
|
"tile" => () => tiles.Add(ReadTile()),
|
||||||
|
_ => r.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if tileset is referring to external file
|
||||||
|
if (source is not null)
|
||||||
|
{
|
||||||
|
var resolvedTileset = _externalTilesetResolver(source);
|
||||||
|
resolvedTileset.FirstGID = firstGID;
|
||||||
|
resolvedTileset.Source = source;
|
||||||
|
return resolvedTileset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Tileset
|
||||||
|
{
|
||||||
|
Version = version,
|
||||||
|
TiledVersion = tiledVersion,
|
||||||
|
FirstGID = firstGID,
|
||||||
|
Source = source,
|
||||||
|
Name = name,
|
||||||
|
Class = @class,
|
||||||
|
TileWidth = tileWidth,
|
||||||
|
TileHeight = tileHeight,
|
||||||
|
Spacing = spacing,
|
||||||
|
Margin = margin,
|
||||||
|
TileCount = tileCount,
|
||||||
|
Columns = columns,
|
||||||
|
ObjectAlignment = objectAlignment,
|
||||||
|
RenderSize = renderSize,
|
||||||
|
FillMode = fillMode,
|
||||||
|
Image = image,
|
||||||
|
TileOffset = tileOffset,
|
||||||
|
Grid = grid,
|
||||||
|
Properties = properties ?? [],
|
||||||
|
Wangsets = wangsets,
|
||||||
|
Transformations = transformations,
|
||||||
|
Tiles = tiles
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Image ReadImage()
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var format = _reader.GetOptionalAttributeEnum<ImageFormat>("format", s => s switch
|
||||||
|
{
|
||||||
|
"png" => ImageFormat.Png,
|
||||||
|
"jpg" => ImageFormat.Jpg,
|
||||||
|
"bmp" => ImageFormat.Bmp,
|
||||||
|
"gif" => ImageFormat.Gif,
|
||||||
|
_ => throw new InvalidOperationException($"Unknown image format '{s}'")
|
||||||
|
});
|
||||||
|
var source = _reader.GetOptionalAttribute("source");
|
||||||
|
var transparentColor = _reader.GetOptionalAttributeClass<Color>("trans");
|
||||||
|
var width = _reader.GetOptionalAttributeParseable<uint>("width");
|
||||||
|
var height = _reader.GetOptionalAttributeParseable<uint>("height");
|
||||||
|
|
||||||
|
_reader.ProcessChildren("image", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"data" => throw new NotSupportedException("Embedded image data is not supported."),
|
||||||
|
_ => r.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
if (format is null && source is not null)
|
||||||
|
format = Helpers.ParseImageFormatFromSource(source);
|
||||||
|
|
||||||
|
return new Image
|
||||||
|
{
|
||||||
|
Format = format,
|
||||||
|
Source = source,
|
||||||
|
TransparentColor = transparentColor,
|
||||||
|
Width = width,
|
||||||
|
Height = height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal TileOffset ReadTileOffset()
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var x = _reader.GetOptionalAttributeParseable<float>("x") ?? 0f;
|
||||||
|
var y = _reader.GetOptionalAttributeParseable<float>("y") ?? 0f;
|
||||||
|
|
||||||
|
_reader.ReadStartElement("tileoffset");
|
||||||
|
return new TileOffset { X = x, Y = y };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Grid ReadGrid()
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var orientation = _reader.GetOptionalAttributeEnum<GridOrientation>("orientation", s => s switch
|
||||||
|
{
|
||||||
|
"orthogonal" => GridOrientation.Orthogonal,
|
||||||
|
"isometric" => GridOrientation.Isometric,
|
||||||
|
_ => throw new InvalidOperationException($"Unknown orientation '{s}'")
|
||||||
|
}) ?? GridOrientation.Orthogonal;
|
||||||
|
var width = _reader.GetRequiredAttributeParseable<uint>("width");
|
||||||
|
var height = _reader.GetRequiredAttributeParseable<uint>("height");
|
||||||
|
|
||||||
|
_reader.ReadStartElement("grid");
|
||||||
|
return new Grid { Orientation = orientation, Width = width, Height = height };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Transformations ReadTransformations()
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var hFlip = (_reader.GetOptionalAttributeParseable<uint>("hflip") ?? 0) == 1;
|
||||||
|
var vFlip = (_reader.GetOptionalAttributeParseable<uint>("vflip") ?? 0) == 1;
|
||||||
|
var rotate = (_reader.GetOptionalAttributeParseable<uint>("rotate") ?? 0) == 1;
|
||||||
|
var preferUntransformed = (_reader.GetOptionalAttributeParseable<uint>("preferuntransformed") ?? 0) == 1;
|
||||||
|
|
||||||
|
_reader.ReadStartElement("transformations");
|
||||||
|
return new Transformations { HFlip = hFlip, VFlip = vFlip, Rotate = rotate, PreferUntransformed = preferUntransformed };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Tile ReadTile()
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var id = _reader.GetRequiredAttributeParseable<uint>("id");
|
||||||
|
var type = _reader.GetOptionalAttribute("type") ?? "";
|
||||||
|
var probability = _reader.GetOptionalAttributeParseable<float>("probability") ?? 0f;
|
||||||
|
var x = _reader.GetOptionalAttributeParseable<uint>("x") ?? 0;
|
||||||
|
var y = _reader.GetOptionalAttributeParseable<uint>("y") ?? 0;
|
||||||
|
var width = _reader.GetOptionalAttributeParseable<uint>("width");
|
||||||
|
var height = _reader.GetOptionalAttributeParseable<uint>("height");
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
List<IProperty>? properties = null;
|
||||||
|
Image? image = null;
|
||||||
|
ObjectLayer? objectLayer = null;
|
||||||
|
List<Frame>? animation = null;
|
||||||
|
|
||||||
|
_reader.ProcessChildren("tile", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(), "Properties"),
|
||||||
|
"image" => () => Helpers.SetAtMostOnce(ref image, ReadImage(), "Image"),
|
||||||
|
"objectgroup" => () => Helpers.SetAtMostOnce(ref objectLayer, ReadObjectLayer(), "ObjectLayer"),
|
||||||
|
"animation" => () => Helpers.SetAtMostOnce(ref animation, r.ReadList<Frame>("animation", "frame", (ar) =>
|
||||||
|
{
|
||||||
|
var tileID = ar.GetRequiredAttributeParseable<uint>("tileid");
|
||||||
|
var duration = ar.GetRequiredAttributeParseable<uint>("duration");
|
||||||
|
return new Frame { TileID = tileID, Duration = duration };
|
||||||
|
}), "Animation"),
|
||||||
|
_ => r.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Tile
|
||||||
|
{
|
||||||
|
ID = id,
|
||||||
|
Type = type,
|
||||||
|
Probability = probability,
|
||||||
|
X = x,
|
||||||
|
Y = y,
|
||||||
|
Width = width ?? image?.Width ?? 0,
|
||||||
|
Height = height ?? image?.Height ?? 0,
|
||||||
|
Properties = properties ?? [],
|
||||||
|
Image = image,
|
||||||
|
ObjectLayer = objectLayer,
|
||||||
|
Animation = animation
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal List<Wangset> ReadWangsets() =>
|
||||||
|
_reader.ReadList<Wangset>("wangsets", "wangset", r => ReadWangset());
|
||||||
|
|
||||||
|
internal Wangset ReadWangset()
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var name = _reader.GetRequiredAttribute("name");
|
||||||
|
var @class = _reader.GetOptionalAttribute("class") ?? "";
|
||||||
|
var tile = _reader.GetRequiredAttributeParseable<int>("tile");
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
List<IProperty>? properties = null;
|
||||||
|
List<WangColor> wangColors = [];
|
||||||
|
List<WangTile> wangTiles = [];
|
||||||
|
|
||||||
|
_reader.ProcessChildren("wangset", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(), "Properties"),
|
||||||
|
"wangcolor" => () => wangColors.Add(ReadWangColor()),
|
||||||
|
"wangtile" => () => wangTiles.Add(ReadWangTile()),
|
||||||
|
_ => r.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
if (wangColors.Count > 254)
|
||||||
|
throw new ArgumentException("Wangset can have at most 254 Wang colors.");
|
||||||
|
|
||||||
|
return new Wangset
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Class = @class,
|
||||||
|
Tile = tile,
|
||||||
|
Properties = properties ?? [],
|
||||||
|
WangColors = wangColors,
|
||||||
|
WangTiles = wangTiles
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal WangColor ReadWangColor()
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var name = _reader.GetRequiredAttribute("name");
|
||||||
|
var @class = _reader.GetOptionalAttribute("class") ?? "";
|
||||||
|
var color = _reader.GetRequiredAttributeParseable<Color>("color");
|
||||||
|
var tile = _reader.GetRequiredAttributeParseable<int>("tile");
|
||||||
|
var probability = _reader.GetOptionalAttributeParseable<float>("probability") ?? 0f;
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
List<IProperty>? properties = null;
|
||||||
|
|
||||||
|
_reader.ProcessChildren("wangcolor", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(), "Properties"),
|
||||||
|
_ => r.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
return new WangColor
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Class = @class,
|
||||||
|
Color = color,
|
||||||
|
Tile = tile,
|
||||||
|
Probability = probability,
|
||||||
|
Properties = properties ?? []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal WangTile ReadWangTile()
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var tileID = _reader.GetRequiredAttributeParseable<uint>("tileid");
|
||||||
|
var wangID = _reader.GetRequiredAttributeParseable<byte[]>("wangid", s =>
|
||||||
|
{
|
||||||
|
// Comma-separated list of indices (0-254)
|
||||||
|
var indices = s.Split(',').Select(i => byte.Parse(i, CultureInfo.InvariantCulture)).ToArray();
|
||||||
|
if (indices.Length > 8)
|
||||||
|
throw new ArgumentException("Wang ID can have at most 8 indices.");
|
||||||
|
return indices;
|
||||||
|
});
|
||||||
|
|
||||||
|
_reader.ReadStartElement("wangtile");
|
||||||
|
|
||||||
|
return new WangTile
|
||||||
|
{
|
||||||
|
TileID = tileID,
|
||||||
|
WangID = wangID
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
74
src/DotTiled/Serialization/Tmx/TmxReaderBase.cs
Normal file
74
src/DotTiled/Serialization/Tmx/TmxReaderBase.cs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
using System;
|
||||||
|
using System.Xml;
|
||||||
|
using DotTiled.Model;
|
||||||
|
|
||||||
|
namespace DotTiled.Serialization.Tmx;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for Tiled XML format readers.
|
||||||
|
/// </summary>
|
||||||
|
public abstract partial class TmxReaderBase : IDisposable
|
||||||
|
{
|
||||||
|
// External resolvers
|
||||||
|
private readonly Func<string, Tileset> _externalTilesetResolver;
|
||||||
|
private readonly Func<string, Template> _externalTemplateResolver;
|
||||||
|
private readonly Func<string, ICustomTypeDefinition> _customTypeResolver;
|
||||||
|
|
||||||
|
private readonly XmlReader _reader;
|
||||||
|
private bool disposedValue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a new <see cref="TmxReaderBase"/>, which is the base class for all Tiled XML format readers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reader">An XML reader for reading a Tiled map in the Tiled XML format.</param>
|
||||||
|
/// <param name="externalTilesetResolver">A function that resolves external tilesets given their source.</param>
|
||||||
|
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
|
||||||
|
/// <param name="customTypeResolver">A function that resolves custom types given their source.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
|
||||||
|
protected TmxReaderBase(
|
||||||
|
XmlReader reader,
|
||||||
|
Func<string, Tileset> externalTilesetResolver,
|
||||||
|
Func<string, Template> externalTemplateResolver,
|
||||||
|
Func<string, ICustomTypeDefinition> customTypeResolver)
|
||||||
|
{
|
||||||
|
_reader = reader ?? throw new ArgumentNullException(nameof(reader));
|
||||||
|
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
||||||
|
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
||||||
|
_customTypeResolver = customTypeResolver ?? throw new ArgumentNullException(nameof(customTypeResolver));
|
||||||
|
|
||||||
|
// Prepare reader
|
||||||
|
_ = _reader.MoveToContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposedValue)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
// TODO: dispose managed state (managed objects)
|
||||||
|
_reader.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
||||||
|
// TODO: set large fields to null
|
||||||
|
disposedValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||||
|
// ~TmxReaderBase()
|
||||||
|
// {
|
||||||
|
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
// Dispose(disposing: false);
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using DotTiled.Model;
|
using DotTiled.Model;
|
||||||
|
|
||||||
|
@ -8,68 +7,20 @@ namespace DotTiled.Serialization.Tmx;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A tileset reader for the Tiled XML format.
|
/// A tileset reader for the Tiled XML format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TsxTilesetReader : ITilesetReader
|
public class TsxTilesetReader : TmxReaderBase, ITilesetReader
|
||||||
{
|
{
|
||||||
// External resolvers
|
|
||||||
private readonly Func<string, Template> _externalTemplateResolver;
|
|
||||||
|
|
||||||
private readonly XmlReader _reader;
|
|
||||||
private bool disposedValue;
|
|
||||||
|
|
||||||
private readonly IReadOnlyCollection<ICustomTypeDefinition> _customTypeDefinitions;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs a new <see cref="TsxTilesetReader"/>.
|
/// Constructs a new <see cref="TsxTilesetReader"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="reader">An XML reader for reading a Tiled tileset in the Tiled XML format.</param>
|
/// <inheritdoc />
|
||||||
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
|
|
||||||
/// <param name="customTypeDefinitions">A collection of custom type definitions that can be used to resolve custom types when encountering <see cref="ClassProperty"/>.</param>
|
|
||||||
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
|
|
||||||
public TsxTilesetReader(
|
public TsxTilesetReader(
|
||||||
XmlReader reader,
|
XmlReader reader,
|
||||||
|
Func<string, Tileset> externalTilesetResolver,
|
||||||
Func<string, Template> externalTemplateResolver,
|
Func<string, Template> externalTemplateResolver,
|
||||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||||
{
|
reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||||
_reader = reader ?? throw new ArgumentNullException(nameof(reader));
|
{ }
|
||||||
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
|
||||||
_customTypeDefinitions = customTypeDefinitions ?? throw new ArgumentNullException(nameof(customTypeDefinitions));
|
|
||||||
|
|
||||||
// Prepare reader
|
|
||||||
_ = _reader.MoveToContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Tileset ReadTileset() => Tmx.ReadTileset(_reader, null, _externalTemplateResolver, _customTypeDefinitions);
|
public new Tileset ReadTileset() => base.ReadTileset();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!disposedValue)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
// TODO: dispose managed state (managed objects)
|
|
||||||
_reader.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
|
||||||
// TODO: set large fields to null
|
|
||||||
disposedValue = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
|
||||||
// ~TsxTilesetReader()
|
|
||||||
// {
|
|
||||||
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
||||||
// Dispose(disposing: false);
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
||||||
Dispose(disposing: true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using DotTiled.Model;
|
using DotTiled.Model;
|
||||||
|
|
||||||
|
@ -8,72 +7,20 @@ namespace DotTiled.Serialization.Tmx;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A template reader for the Tiled XML format.
|
/// A template reader for the Tiled XML format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TxTemplateReader : ITemplateReader
|
public class TxTemplateReader : TmxReaderBase, ITemplateReader
|
||||||
{
|
{
|
||||||
// Resolvers
|
|
||||||
private readonly Func<string, Tileset> _externalTilesetResolver;
|
|
||||||
private readonly Func<string, Template> _externalTemplateResolver;
|
|
||||||
|
|
||||||
private readonly XmlReader _reader;
|
|
||||||
private bool disposedValue;
|
|
||||||
|
|
||||||
private readonly IReadOnlyCollection<ICustomTypeDefinition> _customTypeDefinitions;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs a new <see cref="TxTemplateReader"/>.
|
/// Constructs a new <see cref="TxTemplateReader"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="reader">An XML reader for reading a Tiled template in the Tiled XML format.</param>
|
/// <inheritdoc />
|
||||||
/// <param name="externalTilesetResolver">A function that resolves external tilesets given their source.</param>
|
|
||||||
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
|
|
||||||
/// <param name="customTypeDefinitions">A collection of custom type definitions that can be used to resolve custom types when encountering <see cref="ClassProperty"/>.</param>
|
|
||||||
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
|
|
||||||
public TxTemplateReader(
|
public TxTemplateReader(
|
||||||
XmlReader reader,
|
XmlReader reader,
|
||||||
Func<string, Tileset> externalTilesetResolver,
|
Func<string, Tileset> externalTilesetResolver,
|
||||||
Func<string, Template> externalTemplateResolver,
|
Func<string, Template> externalTemplateResolver,
|
||||||
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
|
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||||
{
|
reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||||
_reader = reader ?? throw new ArgumentNullException(nameof(reader));
|
{ }
|
||||||
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
|
||||||
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
|
||||||
_customTypeDefinitions = customTypeDefinitions ?? throw new ArgumentNullException(nameof(customTypeDefinitions));
|
|
||||||
|
|
||||||
// Prepare reader
|
|
||||||
_ = _reader.MoveToContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Template ReadTemplate() => Tmx.ReadTemplate(_reader, _externalTilesetResolver, _externalTemplateResolver, _customTypeDefinitions);
|
public new Template ReadTemplate() => base.ReadTemplate();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!disposedValue)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
// TODO: dispose managed state (managed objects)
|
|
||||||
_reader.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
|
||||||
// TODO: set large fields to null
|
|
||||||
disposedValue = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
|
||||||
// ~TxTemplateReader()
|
|
||||||
// {
|
|
||||||
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
||||||
// Dispose(disposing: false);
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
||||||
Dispose(disposing: true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue