mirror of
https://github.com/dcronqvist/DotTiled.git
synced 2025-02-05 08:52:50 +02:00
Modelling soon done, starting tests structuring
This commit is contained in:
parent
d2be83972d
commit
70fc74f43b
46 changed files with 1883 additions and 1230 deletions
|
@ -24,4 +24,9 @@
|
||||||
<Using Include="Xunit" />
|
<Using Include="Xunit" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<!-- TmxSerializer test data -->
|
||||||
|
<EmbeddedResource Include="TmxSerializer/TestData/**/*.tmx" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -1,259 +0,0 @@
|
||||||
using System.Text;
|
|
||||||
using System.Xml.Serialization;
|
|
||||||
using DotTiled;
|
|
||||||
|
|
||||||
namespace DotTiled.Tests;
|
|
||||||
|
|
||||||
public class MapTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void ReadXml_Always_SetsRequiredAttributes()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var xml =
|
|
||||||
"""
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<map
|
|
||||||
version="1.2"
|
|
||||||
class="class"
|
|
||||||
orientation="orthogonal"
|
|
||||||
renderorder="right-down"
|
|
||||||
compressionlevel="5"
|
|
||||||
width="10"
|
|
||||||
height="10"
|
|
||||||
tilewidth="32"
|
|
||||||
tileheight="32"
|
|
||||||
parallaxoriginx="0.5"
|
|
||||||
parallaxoriginy="0.5"
|
|
||||||
nextlayerid="1"
|
|
||||||
nextobjectid="1"
|
|
||||||
infinite="1"
|
|
||||||
>
|
|
||||||
</map>
|
|
||||||
""";
|
|
||||||
var xmlStream = new MemoryStream(Encoding.UTF8.GetBytes(xml));
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var map = Map.LoadFromStream(xmlStream);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
// Assert all required properties are set
|
|
||||||
Assert.Equal("1.2", map.Version);
|
|
||||||
Assert.Equal("class", map.Class);
|
|
||||||
Assert.Equal(Orientation.Orthogonal, map.Orientation);
|
|
||||||
Assert.Equal(RenderOrder.RightDown, map.RenderOrder);
|
|
||||||
Assert.Equal(5, map.CompressionLevel);
|
|
||||||
Assert.Equal(10u, map.Width);
|
|
||||||
Assert.Equal(10u, map.Height);
|
|
||||||
Assert.Equal(32u, map.TileWidth);
|
|
||||||
Assert.Equal(32u, map.TileHeight);
|
|
||||||
Assert.Equal(0.5f, map.ParallaxOriginX);
|
|
||||||
Assert.Equal(0.5f, map.ParallaxOriginY);
|
|
||||||
Assert.Equal(1u, map.NextLayerId);
|
|
||||||
Assert.Equal(1u, map.NextObjectId);
|
|
||||||
Assert.True(map.Infinite);
|
|
||||||
|
|
||||||
// Assert all optional properties are set to their default values
|
|
||||||
Assert.Null(map.TiledVersion);
|
|
||||||
Assert.Null(map.HexSideLength);
|
|
||||||
Assert.Null(map.StaggerAxis);
|
|
||||||
Assert.Null(map.StaggerIndex);
|
|
||||||
Assert.Null(map.BackgroundColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<object[]> ColorData =>
|
|
||||||
new List<object[]>
|
|
||||||
{
|
|
||||||
new object[] { "#ff0000", new TiledColor { R = 255, G = 0, B = 0, A = 255 } },
|
|
||||||
new object[] { "#00ff00", new TiledColor { R = 0, G = 255, B = 0, A = 255 } },
|
|
||||||
new object[] { "#0000ff", new TiledColor { R = 0, G = 0, B = 255, A = 255 } },
|
|
||||||
new object[] { "#ffffff", new TiledColor { R = 255, G = 255, B = 255, A = 255 } },
|
|
||||||
new object[] { "#000000", new TiledColor { R = 0, G = 0, B = 0, A = 255 } },
|
|
||||||
new object[] { "#ff000000", new TiledColor { R = 0, G = 0, B = 0, A = 255 } },
|
|
||||||
new object[] { "#fe000000", new TiledColor { R = 0, G = 0, B = 0, A = 254 } },
|
|
||||||
new object[] { "#fe00ff00", new TiledColor { R = 0, G = 255, B = 0, A = 254 } },
|
|
||||||
};
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(ColorData))]
|
|
||||||
public void ReadXml_WhenPresent_SetsOptionalAttributes(string color, TiledColor expectedColor)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var xml =
|
|
||||||
$"""
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<map
|
|
||||||
version="1.2"
|
|
||||||
class="class"
|
|
||||||
orientation="orthogonal"
|
|
||||||
renderorder="right-down"
|
|
||||||
compressionlevel="5"
|
|
||||||
width="10"
|
|
||||||
height="10"
|
|
||||||
tilewidth="32"
|
|
||||||
tileheight="32"
|
|
||||||
hexsidelength="10"
|
|
||||||
staggeraxis="y"
|
|
||||||
staggerindex="odd"
|
|
||||||
parallaxoriginx="0.5"
|
|
||||||
parallaxoriginy="0.5"
|
|
||||||
backgroundcolor="{color}"
|
|
||||||
nextlayerid="1"
|
|
||||||
nextobjectid="1"
|
|
||||||
infinite="1"
|
|
||||||
>
|
|
||||||
</map>
|
|
||||||
""";
|
|
||||||
var xmlStream = new MemoryStream(Encoding.UTF8.GetBytes(xml));
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var map = Map.LoadFromStream(xmlStream);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
// Assert all required properties are set
|
|
||||||
Assert.Equal("1.2", map.Version);
|
|
||||||
Assert.Equal("class", map.Class);
|
|
||||||
Assert.Equal(Orientation.Orthogonal, map.Orientation);
|
|
||||||
Assert.Equal(RenderOrder.RightDown, map.RenderOrder);
|
|
||||||
Assert.Equal(5, map.CompressionLevel);
|
|
||||||
Assert.Equal(10u, map.Width);
|
|
||||||
Assert.Equal(10u, map.Height);
|
|
||||||
Assert.Equal(32u, map.TileWidth);
|
|
||||||
Assert.Equal(32u, map.TileHeight);
|
|
||||||
Assert.Equal(10u, map.HexSideLength);
|
|
||||||
Assert.Equal(StaggerAxis.Y, map.StaggerAxis);
|
|
||||||
Assert.Equal(StaggerIndex.Odd, map.StaggerIndex);
|
|
||||||
Assert.Equal(0.5f, map.ParallaxOriginX);
|
|
||||||
Assert.Equal(0.5f, map.ParallaxOriginY);
|
|
||||||
Assert.Equal(expectedColor, map.BackgroundColor);
|
|
||||||
Assert.Equal(1u, map.NextLayerId);
|
|
||||||
Assert.Equal(1u, map.NextObjectId);
|
|
||||||
Assert.True(map.Infinite);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ReadXml_Always_ReadsPropertiesCorrectly()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var xml =
|
|
||||||
"""
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<map
|
|
||||||
version="1.2"
|
|
||||||
class="class"
|
|
||||||
orientation="orthogonal"
|
|
||||||
renderorder="right-down"
|
|
||||||
compressionlevel="5"
|
|
||||||
width="10"
|
|
||||||
height="10"
|
|
||||||
tilewidth="32"
|
|
||||||
tileheight="32"
|
|
||||||
parallaxoriginx="0.5"
|
|
||||||
parallaxoriginy="0.5"
|
|
||||||
nextlayerid="1"
|
|
||||||
nextobjectid="1"
|
|
||||||
infinite="1"
|
|
||||||
>
|
|
||||||
<properties>
|
|
||||||
<property name="string" type="string" value="string"/>
|
|
||||||
<property name="int" type="int" value="42"/>
|
|
||||||
<property name="float" type="float" value="42.42"/>
|
|
||||||
<property name="bool" type="bool" value="true"/>
|
|
||||||
<property name="color" type="color" value="#ff0000"/>
|
|
||||||
<property name="file" type="file" value="file"/>
|
|
||||||
<property name="object" type="object" value="5"/>
|
|
||||||
<property name="class" type="class" propertytype="TestClass">
|
|
||||||
<properties>
|
|
||||||
<property name="TestClassString" type="string" value="string"/>
|
|
||||||
<property name="TestClassInt" type="int" value="43"/>
|
|
||||||
</properties>
|
|
||||||
</property>
|
|
||||||
</properties>
|
|
||||||
</map>
|
|
||||||
""";
|
|
||||||
var xmlStream = new MemoryStream(Encoding.UTF8.GetBytes(xml));
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var map = Map.LoadFromStream(xmlStream);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.NotNull(map.Properties);
|
|
||||||
Assert.Equal(8, map.Properties.Count);
|
|
||||||
|
|
||||||
Assert.Equal(PropertyType.String, map.Properties["string"].Type);
|
|
||||||
Assert.Equal("string", map.GetProperty<StringProperty>("string").Value);
|
|
||||||
|
|
||||||
Assert.Equal(PropertyType.Int, map.Properties["int"].Type);
|
|
||||||
Assert.Equal(42, map.GetProperty<IntProperty>("int").Value);
|
|
||||||
|
|
||||||
Assert.Equal(PropertyType.Float, map.Properties["float"].Type);
|
|
||||||
Assert.Equal(42.42f, map.GetProperty<FloatProperty>("float").Value);
|
|
||||||
|
|
||||||
Assert.Equal(PropertyType.Bool, map.Properties["bool"].Type);
|
|
||||||
Assert.True(map.GetProperty<BooleanProperty>("bool").Value);
|
|
||||||
|
|
||||||
Assert.Equal(PropertyType.Color, map.Properties["color"].Type);
|
|
||||||
Assert.Equal(new TiledColor { R = 255, G = 0, B = 0, A = 255 }, map.GetProperty<ColorProperty>("color").Value);
|
|
||||||
|
|
||||||
Assert.Equal(PropertyType.File, map.Properties["file"].Type);
|
|
||||||
Assert.Equal("file", map.GetProperty<FileProperty>("file").Value);
|
|
||||||
|
|
||||||
Assert.Equal(PropertyType.Object, map.Properties["object"].Type);
|
|
||||||
Assert.Equal(5, map.GetProperty<ObjectProperty>("object").Value);
|
|
||||||
|
|
||||||
Assert.Equal(PropertyType.Class, map.Properties["class"].Type);
|
|
||||||
var classProperty = map.GetProperty<ClassProperty>("class");
|
|
||||||
Assert.Equal("TestClass", classProperty.PropertyType);
|
|
||||||
Assert.Equal(2, classProperty.Value.Count);
|
|
||||||
Assert.Equal("string", classProperty.GetProperty<StringProperty>("TestClassString").Value);
|
|
||||||
Assert.Equal(43, classProperty.GetProperty<IntProperty>("TestClassInt").Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ReadXml_Always_1()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var xml =
|
|
||||||
"""
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<map
|
|
||||||
version="1.2"
|
|
||||||
class="class"
|
|
||||||
orientation="orthogonal"
|
|
||||||
renderorder="right-down"
|
|
||||||
compressionlevel="5"
|
|
||||||
width="10"
|
|
||||||
height="10"
|
|
||||||
tilewidth="32"
|
|
||||||
tileheight="32"
|
|
||||||
parallaxoriginx="0.5"
|
|
||||||
parallaxoriginy="0.5"
|
|
||||||
nextlayerid="1"
|
|
||||||
nextobjectid="1"
|
|
||||||
infinite="1"
|
|
||||||
>
|
|
||||||
<properties>
|
|
||||||
<property name="string" type="string" value="string"/>
|
|
||||||
<property name="int" type="int" value="42"/>
|
|
||||||
<property name="float" type="float" value="42.42"/>
|
|
||||||
<property name="bool" type="bool" value="true"/>
|
|
||||||
<property name="color" type="color" value="#ff0000"/>
|
|
||||||
<property name="file" type="file" value="file"/>
|
|
||||||
<property name="object" type="object" value="5"/>
|
|
||||||
<property name="class" type="class" propertytype="TestClass">
|
|
||||||
<properties>
|
|
||||||
<property name="TestClassString" type="string" value="string"/>
|
|
||||||
<property name="TestClassInt" type="int" value="43"/>
|
|
||||||
</properties>
|
|
||||||
</property>
|
|
||||||
</properties>
|
|
||||||
<tileset firstgid="1" source="textures/tiles.tsx"/>
|
|
||||||
</map>
|
|
||||||
""";
|
|
||||||
var xmlStream = new MemoryStream(Encoding.UTF8.GetBytes(xml));
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var map = Map.LoadFromStream(xmlStream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
46
DotTiled.Tests/TmxSerializer/TestData/Map/empty-map.cs
Normal file
46
DotTiled.Tests/TmxSerializer/TestData/Map/empty-map.cs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
namespace DotTiled.Tests;
|
||||||
|
|
||||||
|
public partial class TmxSerializerMapTests
|
||||||
|
{
|
||||||
|
private readonly static Map EmptyMap = new Map
|
||||||
|
{
|
||||||
|
Version = "1.10",
|
||||||
|
TiledVersion = "1.11.0",
|
||||||
|
Orientation = MapOrientation.Orthogonal,
|
||||||
|
RenderOrder = RenderOrder.RightDown,
|
||||||
|
Width = 5,
|
||||||
|
Height = 5,
|
||||||
|
TileWidth = 32,
|
||||||
|
TileHeight = 32,
|
||||||
|
Infinite = false,
|
||||||
|
NextLayerID = 2,
|
||||||
|
NextObjectID = 1,
|
||||||
|
Layers = [
|
||||||
|
new TileLayer
|
||||||
|
{
|
||||||
|
ID = 1,
|
||||||
|
Name = "Tile Layer 1",
|
||||||
|
Width = 5,
|
||||||
|
Height = 5,
|
||||||
|
Data = new Data
|
||||||
|
{
|
||||||
|
Encoding = DataEncoding.Csv,
|
||||||
|
GlobalTileIDs = [
|
||||||
|
0,0,0,0,0,
|
||||||
|
0,0,0,0,0,
|
||||||
|
0,0,0,0,0,
|
||||||
|
0,0,0,0,0,
|
||||||
|
0,0,0,0,0
|
||||||
|
],
|
||||||
|
FlippingFlags = [
|
||||||
|
FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None,
|
||||||
|
FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None,
|
||||||
|
FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None,
|
||||||
|
FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None,
|
||||||
|
FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
12
DotTiled.Tests/TmxSerializer/TestData/Map/empty-map.tmx
Normal file
12
DotTiled.Tests/TmxSerializer/TestData/Map/empty-map.tmx
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="5" height="5" tilewidth="32" tileheight="32" infinite="0" nextlayerid="2" nextobjectid="1">
|
||||||
|
<layer id="1" name="Tile Layer 1" width="5" height="5">
|
||||||
|
<data encoding="csv">
|
||||||
|
0,0,0,0,0,
|
||||||
|
0,0,0,0,0,
|
||||||
|
0,0,0,0,0,
|
||||||
|
0,0,0,0,0,
|
||||||
|
0,0,0,0,0
|
||||||
|
</data>
|
||||||
|
</layer>
|
||||||
|
</map>
|
18
DotTiled.Tests/TmxSerializer/TestData/TestData.cs
Normal file
18
DotTiled.Tests/TmxSerializer/TestData/TestData.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
namespace DotTiled.Tests;
|
||||||
|
|
||||||
|
public static class TmxSerializerTestData
|
||||||
|
{
|
||||||
|
public static XmlReader GetReaderFor(string testDataFile)
|
||||||
|
{
|
||||||
|
var fullyQualifiedTestDataFile = $"DotTiled.Tests.{testDataFile}";
|
||||||
|
using var stream = typeof(TmxSerializerTestData).Assembly.GetManifestResourceStream(fullyQualifiedTestDataFile)
|
||||||
|
?? throw new ArgumentException($"Test data file '{fullyQualifiedTestDataFile}' not found");
|
||||||
|
|
||||||
|
using var stringReader = new StreamReader(stream);
|
||||||
|
var xml = stringReader.ReadToEnd();
|
||||||
|
var xmlStringReader = new StringReader(xml);
|
||||||
|
return XmlReader.Create(xmlStringReader);
|
||||||
|
}
|
||||||
|
}
|
50
DotTiled.Tests/TmxSerializer/TmxSerializer.MapTests.cs
Normal file
50
DotTiled.Tests/TmxSerializer/TmxSerializer.MapTests.cs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
namespace DotTiled.Tests;
|
||||||
|
|
||||||
|
public partial class TmxSerializerMapTests
|
||||||
|
{
|
||||||
|
private static void AssertMap(Map actual, Map expected)
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
Assert.Equal(expected.Version, actual.Version);
|
||||||
|
Assert.Equal(expected.TiledVersion, actual.TiledVersion);
|
||||||
|
Assert.Equal(expected.Class, actual.Class);
|
||||||
|
Assert.Equal(expected.Orientation, actual.Orientation);
|
||||||
|
Assert.Equal(expected.RenderOrder, actual.RenderOrder);
|
||||||
|
Assert.Equal(expected.CompressionLevel, actual.CompressionLevel);
|
||||||
|
Assert.Equal(expected.Width, actual.Width);
|
||||||
|
Assert.Equal(expected.Height, actual.Height);
|
||||||
|
Assert.Equal(expected.TileWidth, actual.TileWidth);
|
||||||
|
Assert.Equal(expected.TileHeight, actual.TileHeight);
|
||||||
|
Assert.Equal(expected.HexSideLength, actual.HexSideLength);
|
||||||
|
Assert.Equal(expected.StaggerAxis, actual.StaggerAxis);
|
||||||
|
Assert.Equal(expected.StaggerIndex, actual.StaggerIndex);
|
||||||
|
Assert.Equal(expected.ParallaxOriginX, actual.ParallaxOriginX);
|
||||||
|
Assert.Equal(expected.ParallaxOriginY, actual.ParallaxOriginY);
|
||||||
|
Assert.Equal(expected.BackgroundColor, actual.BackgroundColor);
|
||||||
|
Assert.Equal(expected.NextLayerID, actual.NextLayerID);
|
||||||
|
Assert.Equal(expected.NextObjectID, actual.NextObjectID);
|
||||||
|
Assert.Equal(expected.Infinite, actual.Infinite);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> DeserializeMap_ValidXmlNoExternalTilesets_ReturnsMapWithoutThrowing_Data =>
|
||||||
|
[
|
||||||
|
["TmxSerializer.TestData.Map.empty-map.tmx", EmptyMap]
|
||||||
|
];
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(DeserializeMap_ValidXmlNoExternalTilesets_ReturnsMapWithoutThrowing_Data))]
|
||||||
|
public void DeserializeMap_ValidXmlNoExternalTilesets_ReturnsMapWithoutThrowing(string testDataFile, Map expectedMap)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
using var reader = TmxSerializerTestData.GetReaderFor(testDataFile);
|
||||||
|
Func<string, Tileset> externalTilesetResolver = (string s) => throw new NotSupportedException("External tilesets are not supported in this test");
|
||||||
|
var tmxSerializer = new TmxSerializer(externalTilesetResolver);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var map = tmxSerializer.DeserializeMap(reader);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(map);
|
||||||
|
AssertMap(map, expectedMap);
|
||||||
|
}
|
||||||
|
}
|
30
DotTiled.Tests/TmxSerializer/TmxSerializerTests.cs
Normal file
30
DotTiled.Tests/TmxSerializer/TmxSerializerTests.cs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
namespace DotTiled.Tests;
|
||||||
|
|
||||||
|
public class TmxSerializerTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void TmxSerializerConstructor_ExternalTilesetResolverIsNull_ThrowsArgumentNullException()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Func<string, Tileset> externalTilesetResolver = null!;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
Action act = () => _ = new TmxSerializer(externalTilesetResolver);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Throws<ArgumentNullException>(act);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TmxSerializerConstructor_ExternalTilesetResolverIsNotNull_DoesNotThrow()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Func<string, Tileset> externalTilesetResolver = _ => new Tileset();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var tmxSerializer = new TmxSerializer(externalTilesetResolver);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(tmxSerializer);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
809
DotTiled/Map.cs
809
DotTiled/Map.cs
|
@ -1,809 +0,0 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Text;
|
|
||||||
using System.Xml;
|
|
||||||
using System.Xml.Schema;
|
|
||||||
using System.Xml.Serialization;
|
|
||||||
|
|
||||||
namespace DotTiled;
|
|
||||||
|
|
||||||
public static class Helpers
|
|
||||||
{
|
|
||||||
public static void SetAtMostOnce<T>(ref T? field, T value, string fieldName)
|
|
||||||
{
|
|
||||||
if (field is not null)
|
|
||||||
throw new XmlException($"{fieldName} already set");
|
|
||||||
|
|
||||||
field = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Orientation
|
|
||||||
{
|
|
||||||
[XmlEnum(Name = "orthogonal")]
|
|
||||||
Orthogonal,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "isometric")]
|
|
||||||
Isometric,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "staggered")]
|
|
||||||
Staggered,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "hexagonal")]
|
|
||||||
Hexagonal
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum RenderOrder
|
|
||||||
{
|
|
||||||
[XmlEnum(Name = "right-down")]
|
|
||||||
RightDown,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "right-up")]
|
|
||||||
RightUp,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "left-down")]
|
|
||||||
LeftDown,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "left-up")]
|
|
||||||
LeftUp
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum StaggerAxis
|
|
||||||
{
|
|
||||||
[XmlEnum(Name = "x")]
|
|
||||||
X,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "y")]
|
|
||||||
Y
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum StaggerIndex
|
|
||||||
{
|
|
||||||
[XmlEnum(Name = "even")]
|
|
||||||
Even,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "odd")]
|
|
||||||
Odd
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TiledColor : IParsable<TiledColor>, IEquatable<TiledColor>
|
|
||||||
{
|
|
||||||
public required byte R { get; set; }
|
|
||||||
public required byte G { get; set; }
|
|
||||||
public required byte B { get; set; }
|
|
||||||
public byte A { get; set; } = 255;
|
|
||||||
|
|
||||||
public static TiledColor Parse(string s, IFormatProvider? provider)
|
|
||||||
{
|
|
||||||
TryParse(s, provider, out var result);
|
|
||||||
return result ?? throw new FormatException($"Invalid format for TiledColor: {s}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool TryParse(
|
|
||||||
[NotNullWhen(true)] string? s,
|
|
||||||
IFormatProvider? provider,
|
|
||||||
[MaybeNullWhen(false)] out TiledColor result)
|
|
||||||
{
|
|
||||||
// Format: #RRGGBB or #AARRGGBB
|
|
||||||
if (s is null || s.Length != 7 && s.Length != 9 || s[0] != '#')
|
|
||||||
{
|
|
||||||
result = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s.Length == 7)
|
|
||||||
{
|
|
||||||
result = new TiledColor
|
|
||||||
{
|
|
||||||
R = byte.Parse(s[1..3], NumberStyles.HexNumber, provider),
|
|
||||||
G = byte.Parse(s[3..5], NumberStyles.HexNumber, provider),
|
|
||||||
B = byte.Parse(s[5..7], NumberStyles.HexNumber, provider)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = new TiledColor
|
|
||||||
{
|
|
||||||
A = byte.Parse(s[1..3], NumberStyles.HexNumber, provider),
|
|
||||||
R = byte.Parse(s[3..5], NumberStyles.HexNumber, provider),
|
|
||||||
G = byte.Parse(s[5..7], NumberStyles.HexNumber, provider),
|
|
||||||
B = byte.Parse(s[7..9], NumberStyles.HexNumber, provider)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(TiledColor? other)
|
|
||||||
{
|
|
||||||
if (other is null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return R == other.R && G == other.G && B == other.B && A == other.A;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object? obj) => obj is TiledColor other && Equals(other);
|
|
||||||
|
|
||||||
public override int GetHashCode() => HashCode.Combine(R, G, B, A);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum PropertyType
|
|
||||||
{
|
|
||||||
[XmlEnum(Name = "string")]
|
|
||||||
String,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "int")]
|
|
||||||
Int,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "float")]
|
|
||||||
Float,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "bool")]
|
|
||||||
Bool,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "color")]
|
|
||||||
Color,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "file")]
|
|
||||||
File,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "object")]
|
|
||||||
Object,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "class")]
|
|
||||||
Class
|
|
||||||
}
|
|
||||||
|
|
||||||
[XmlRoot(ElementName = "property")]
|
|
||||||
public interface IProperty : IXmlSerializable
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
public PropertyType Type { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[XmlRoot(ElementName = "property")]
|
|
||||||
public class BooleanProperty : IProperty
|
|
||||||
{
|
|
||||||
public required string Name { get; set; }
|
|
||||||
public required PropertyType Type { get; set; }
|
|
||||||
public required bool Value { get; set; }
|
|
||||||
|
|
||||||
public XmlSchema? GetSchema() => null;
|
|
||||||
|
|
||||||
public void ReadXml(XmlReader reader)
|
|
||||||
{
|
|
||||||
Name = reader.GetRequiredAttribute("name");
|
|
||||||
Type = reader.GetRequiredAttributeEnum<PropertyType>("type");
|
|
||||||
Value = reader.GetRequiredAttribute<bool>("value");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteXml(XmlWriter writer)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[XmlRoot(ElementName = "property")]
|
|
||||||
public class ColorProperty : IProperty
|
|
||||||
{
|
|
||||||
public required string Name { get; set; }
|
|
||||||
public required PropertyType Type { get; set; }
|
|
||||||
public required TiledColor Value { get; set; }
|
|
||||||
|
|
||||||
public XmlSchema? GetSchema() => null;
|
|
||||||
|
|
||||||
public void ReadXml(XmlReader reader)
|
|
||||||
{
|
|
||||||
Name = reader.GetRequiredAttribute("name");
|
|
||||||
Type = reader.GetRequiredAttributeEnum<PropertyType>("type");
|
|
||||||
Value = reader.GetRequiredAttribute<TiledColor>("value");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteXml(XmlWriter writer)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[XmlRoot(ElementName = "property")]
|
|
||||||
public class FileProperty : IProperty
|
|
||||||
{
|
|
||||||
public required string Name { get; set; }
|
|
||||||
public required PropertyType Type { get; set; }
|
|
||||||
public required string Value { get; set; }
|
|
||||||
|
|
||||||
public XmlSchema? GetSchema() => null;
|
|
||||||
|
|
||||||
public void ReadXml(XmlReader reader)
|
|
||||||
{
|
|
||||||
Name = reader.GetRequiredAttribute("name");
|
|
||||||
Type = reader.GetRequiredAttributeEnum<PropertyType>("type");
|
|
||||||
Value = reader.GetRequiredAttribute("value");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteXml(XmlWriter writer)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[XmlRoot(ElementName = "property")]
|
|
||||||
public class FloatProperty : IProperty
|
|
||||||
{
|
|
||||||
public required string Name { get; set; }
|
|
||||||
public required PropertyType Type { get; set; }
|
|
||||||
public required float Value { get; set; }
|
|
||||||
|
|
||||||
public XmlSchema? GetSchema() => null;
|
|
||||||
|
|
||||||
public void ReadXml(XmlReader reader)
|
|
||||||
{
|
|
||||||
Name = reader.GetRequiredAttribute("name");
|
|
||||||
Type = reader.GetRequiredAttributeEnum<PropertyType>("type");
|
|
||||||
Value = reader.GetRequiredAttribute<float>("value");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteXml(XmlWriter writer)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[XmlRoot(ElementName = "property")]
|
|
||||||
public class IntProperty : IProperty
|
|
||||||
{
|
|
||||||
public required string Name { get; set; }
|
|
||||||
public required PropertyType Type { get; set; }
|
|
||||||
public required int Value { get; set; }
|
|
||||||
|
|
||||||
public XmlSchema? GetSchema() => null;
|
|
||||||
|
|
||||||
public void ReadXml(XmlReader reader)
|
|
||||||
{
|
|
||||||
Name = reader.GetRequiredAttribute("name");
|
|
||||||
Type = reader.GetRequiredAttributeEnum<PropertyType>("type");
|
|
||||||
Value = reader.GetRequiredAttribute<int>("value");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteXml(XmlWriter writer)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[XmlRoot(ElementName = "property")]
|
|
||||||
public class ObjectProperty : IProperty
|
|
||||||
{
|
|
||||||
public required string Name { get; set; }
|
|
||||||
public required PropertyType Type { get; set; }
|
|
||||||
public required int Value { get; set; }
|
|
||||||
|
|
||||||
public XmlSchema? GetSchema() => null;
|
|
||||||
|
|
||||||
public void ReadXml(XmlReader reader)
|
|
||||||
{
|
|
||||||
Name = reader.GetRequiredAttribute("name");
|
|
||||||
Type = reader.GetRequiredAttributeEnum<PropertyType>("type");
|
|
||||||
Value = reader.GetRequiredAttribute<int>("value");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteXml(XmlWriter writer)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[XmlRoot(ElementName = "property")]
|
|
||||||
public class StringProperty : IProperty
|
|
||||||
{
|
|
||||||
public required string Name { get; set; }
|
|
||||||
public required PropertyType Type { get; set; }
|
|
||||||
public required string Value { get; set; }
|
|
||||||
|
|
||||||
public XmlSchema? GetSchema() => null;
|
|
||||||
|
|
||||||
public void ReadXml(XmlReader reader)
|
|
||||||
{
|
|
||||||
Name = reader.GetRequiredAttribute("name");
|
|
||||||
Type = reader.GetRequiredAttributeEnum<PropertyType>("type");
|
|
||||||
Value = reader.GetRequiredAttribute("value");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteXml(XmlWriter writer)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[XmlRoot(ElementName = "property")]
|
|
||||||
public class ClassProperty : IProperty
|
|
||||||
{
|
|
||||||
public required string Name { get; set; }
|
|
||||||
public required PropertyType Type { get; set; }
|
|
||||||
public required string PropertyType { get; set; }
|
|
||||||
public required Dictionary<string, IProperty> Value { get; set; }
|
|
||||||
|
|
||||||
public T GetProperty<T>(string propertyName) where T : IProperty =>
|
|
||||||
(T)Value[propertyName];
|
|
||||||
|
|
||||||
public XmlSchema? GetSchema() => null;
|
|
||||||
|
|
||||||
public void ReadXml(XmlReader reader)
|
|
||||||
{
|
|
||||||
Name = reader.GetRequiredAttribute("name");
|
|
||||||
Type = reader.GetRequiredAttributeEnum<PropertyType>("type");
|
|
||||||
PropertyType = reader.GetRequiredAttribute("propertytype");
|
|
||||||
|
|
||||||
// First read the start element
|
|
||||||
reader.ReadStartElement("property");
|
|
||||||
// Then read the properties
|
|
||||||
Value = XmlHelpers.ReadProperties(reader);
|
|
||||||
// Finally read the end element
|
|
||||||
reader.ReadEndElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteXml(XmlWriter writer)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum ObjectAlignment
|
|
||||||
{
|
|
||||||
[XmlEnum(Name = "unspecified")]
|
|
||||||
Unspecified,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "topleft")]
|
|
||||||
TopLeft,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "top")]
|
|
||||||
Top,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "topright")]
|
|
||||||
TopRight,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "left")]
|
|
||||||
Left,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "center")]
|
|
||||||
Center,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "right")]
|
|
||||||
Right,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "bottomleft")]
|
|
||||||
BottomLeft,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "bottom")]
|
|
||||||
Bottom,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "bottomright")]
|
|
||||||
BottomRight
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum TileRenderSize
|
|
||||||
{
|
|
||||||
[XmlEnum(Name = "tile")]
|
|
||||||
Tile,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "grid")]
|
|
||||||
Grid
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum FillMode
|
|
||||||
{
|
|
||||||
[XmlEnum(Name = "stretch")]
|
|
||||||
Stretch,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "preserve-aspect-fit")]
|
|
||||||
PreserveAspectFit
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum ImageFormat
|
|
||||||
{
|
|
||||||
[XmlEnum(Name = "png")]
|
|
||||||
Png,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "gif")]
|
|
||||||
Gif,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "jpg")]
|
|
||||||
Jpg,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "bmp")]
|
|
||||||
Bmp
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum TiledDataEncoding
|
|
||||||
{
|
|
||||||
[XmlEnum(Name = "csv")]
|
|
||||||
Csv,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "base64")]
|
|
||||||
Base64
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum TiledDataCompression
|
|
||||||
{
|
|
||||||
[XmlEnum(Name = "gzip")]
|
|
||||||
GZip,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "zlib")]
|
|
||||||
ZLib,
|
|
||||||
|
|
||||||
[XmlEnum(Name = "zstd")]
|
|
||||||
ZStd
|
|
||||||
}
|
|
||||||
|
|
||||||
[XmlRoot(ElementName = "data")]
|
|
||||||
public class TiledData : IXmlSerializable
|
|
||||||
{
|
|
||||||
public TiledDataEncoding? Encoding { get; set; }
|
|
||||||
public TiledDataCompression? Compression { get; set; }
|
|
||||||
public required int[] Data { get; set; }
|
|
||||||
|
|
||||||
public XmlSchema? GetSchema() => null;
|
|
||||||
|
|
||||||
public void ReadXml(XmlReader reader)
|
|
||||||
{
|
|
||||||
ReadXmlAttributes(reader);
|
|
||||||
ReadXmlElements(reader);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReadXmlAttributes(XmlReader reader)
|
|
||||||
{
|
|
||||||
Encoding = reader.GetOptionalAttributeEnum<TiledDataEncoding>("encoding");
|
|
||||||
Compression = reader.GetOptionalAttributeEnum<TiledDataCompression>("compression");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReadXmlElements(XmlReader reader)
|
|
||||||
{
|
|
||||||
if (Encoding is null && Compression is null)
|
|
||||||
{
|
|
||||||
// Plain csv
|
|
||||||
reader.ReadStartElement("data");
|
|
||||||
var dataAsCsvStringFromFile = reader.ReadContentAsString();
|
|
||||||
var data = dataAsCsvStringFromFile
|
|
||||||
.Split((char[])['\n', '\r', ','], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
|
||||||
.Select(int.Parse)
|
|
||||||
.ToArray();
|
|
||||||
Data = data;
|
|
||||||
reader.ReadEndElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteXml(XmlWriter writer)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[XmlRoot(ElementName = "image")]
|
|
||||||
public class Image : IXmlSerializable
|
|
||||||
{
|
|
||||||
public ImageFormat? Format { get; set; }
|
|
||||||
public string? ID { get; set; } = null; // Deprecated and unsupported
|
|
||||||
public string? Source { get; set; }
|
|
||||||
public TiledColor? TransparentColor { get; set; }
|
|
||||||
public uint? Width { get; set; }
|
|
||||||
public uint? Height { get; set; }
|
|
||||||
|
|
||||||
private TiledData? _data = null;
|
|
||||||
public TiledData? Data
|
|
||||||
{
|
|
||||||
get => _data;
|
|
||||||
set => _data = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public XmlSchema? GetSchema() => null;
|
|
||||||
|
|
||||||
public void ReadXml(XmlReader reader)
|
|
||||||
{
|
|
||||||
ReadXmlAttributes(reader);
|
|
||||||
ReadXmlElements(reader);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReadXmlAttributes(XmlReader reader)
|
|
||||||
{
|
|
||||||
Format = reader.GetOptionalAttributeEnum<ImageFormat>("format");
|
|
||||||
ID = reader.GetOptionalAttribute("id");
|
|
||||||
Source = reader.GetOptionalAttribute("source");
|
|
||||||
TransparentColor = reader.GetOptionalAttributeClass<TiledColor>("trans");
|
|
||||||
Width = reader.GetOptionalAttribute<uint>("width");
|
|
||||||
Height = reader.GetOptionalAttribute<uint>("height");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReadXmlElements(XmlReader reader)
|
|
||||||
{
|
|
||||||
reader.ReadStartElement("image");
|
|
||||||
|
|
||||||
while (reader.IsStartElement())
|
|
||||||
{
|
|
||||||
var name = reader.Name;
|
|
||||||
Action action = name switch
|
|
||||||
{
|
|
||||||
"data" => () => Helpers.SetAtMostOnce(ref _data, reader.ReadElementAs<TiledData>(), "Data"),
|
|
||||||
_ => reader.Skip
|
|
||||||
};
|
|
||||||
|
|
||||||
action();
|
|
||||||
|
|
||||||
if (reader.NodeType == XmlNodeType.EndElement)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteXml(XmlWriter writer)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class BaseTileset : IXmlSerializable
|
|
||||||
{
|
|
||||||
public required string? FirstGID { get; set; } // Not set in tsx
|
|
||||||
public required string? Source { get; set; } // Not set in tsx
|
|
||||||
public required string Name { get; set; }
|
|
||||||
public required string Class { get; set; }
|
|
||||||
public required uint TileWidth { get; set; }
|
|
||||||
public required uint TileHeight { get; set; }
|
|
||||||
public required uint? Spacing { get; set; }
|
|
||||||
public required uint? Margin { get; set; }
|
|
||||||
public required uint TileCount { get; set; }
|
|
||||||
public required uint Columns { get; set; }
|
|
||||||
public required ObjectAlignment ObjectAlignment { get; set; }
|
|
||||||
public required TileRenderSize TileRenderSize { get; set; }
|
|
||||||
public required FillMode FillMode { get; set; }
|
|
||||||
|
|
||||||
public XmlSchema? GetSchema() => null;
|
|
||||||
|
|
||||||
public void ReadXml(XmlReader reader)
|
|
||||||
{
|
|
||||||
ReadXmlAttributes(reader);
|
|
||||||
ReadXmlElements(reader);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReadXmlAttributes(XmlReader reader)
|
|
||||||
{
|
|
||||||
FirstGID = reader.GetOptionalAttribute("firstgid");
|
|
||||||
Source = reader.GetOptionalAttribute("source");
|
|
||||||
Name = reader.GetRequiredAttribute("name");
|
|
||||||
Class = reader.GetOptionalAttribute("class") ?? ""; // default value
|
|
||||||
TileWidth = reader.GetRequiredAttribute<uint>("tilewidth");
|
|
||||||
TileHeight = reader.GetRequiredAttribute<uint>("tileheight");
|
|
||||||
Spacing = reader.GetOptionalAttribute<uint>("spacing");
|
|
||||||
Margin = reader.GetOptionalAttribute<uint>("margin");
|
|
||||||
TileCount = reader.GetRequiredAttribute<uint>("tilecount");
|
|
||||||
Columns = reader.GetRequiredAttribute<uint>("columns");
|
|
||||||
ObjectAlignment = reader.GetOptionalAttributeEnum<ObjectAlignment>("objectalignment") ?? ObjectAlignment.Unspecified;
|
|
||||||
TileRenderSize = reader.GetOptionalAttributeEnum<TileRenderSize>("tilerendersize") ?? TileRenderSize.Tile;
|
|
||||||
FillMode = reader.GetOptionalAttributeEnum<FillMode>("fillmode") ?? FillMode.Stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void ReadXmlElements(XmlReader reader);
|
|
||||||
|
|
||||||
public void WriteXml(XmlWriter writer)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[XmlRoot(ElementName = "tileset")]
|
|
||||||
public class ImageTileset : BaseTileset
|
|
||||||
{
|
|
||||||
private Image? _image = null;
|
|
||||||
public required Image Image
|
|
||||||
{
|
|
||||||
get => _image ?? throw new InvalidOperationException("Image not set"); // Should not be able to happen
|
|
||||||
set => _image = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ReadXmlElements(XmlReader reader)
|
|
||||||
{
|
|
||||||
// Different types of tilesets
|
|
||||||
reader.ReadStartElement("tileset");
|
|
||||||
|
|
||||||
while (reader.IsStartElement())
|
|
||||||
{
|
|
||||||
var name = reader.Name;
|
|
||||||
Action action = name switch
|
|
||||||
{
|
|
||||||
"image" => () => Helpers.SetAtMostOnce(ref _image, reader.ReadElementAs<Image>(), "Image"),
|
|
||||||
"tileoffset" => reader.Skip,
|
|
||||||
"tile" => reader.Skip,
|
|
||||||
"terraintypes" => reader.Skip,
|
|
||||||
"wangsets" => reader.Skip,
|
|
||||||
_ => reader.Skip
|
|
||||||
};
|
|
||||||
|
|
||||||
action();
|
|
||||||
|
|
||||||
if (reader.NodeType == XmlNodeType.EndElement)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[XmlRoot(ElementName = "layer")]
|
|
||||||
public class Layer : IXmlSerializable
|
|
||||||
{
|
|
||||||
public required string ID { get; set; }
|
|
||||||
public required string Name { get; set; }
|
|
||||||
public required string Class { get; set; }
|
|
||||||
public required uint X { get; set; }
|
|
||||||
public required uint Y { get; set; }
|
|
||||||
public required uint Width { get; set; }
|
|
||||||
public required uint Height { get; set; }
|
|
||||||
public required float Opacity { get; set; }
|
|
||||||
public required bool Visible { get; set; }
|
|
||||||
public required TiledColor? TintColor { get; set; }
|
|
||||||
public required float OffsetX { get; set; }
|
|
||||||
public required float OffsetY { get; set; }
|
|
||||||
public required float ParallaxX { get; set; }
|
|
||||||
public required float ParallaxY { get; set; }
|
|
||||||
|
|
||||||
private Dictionary<string, IProperty>? _properties = null;
|
|
||||||
public required Dictionary<string, IProperty>? Properties
|
|
||||||
{
|
|
||||||
get => _properties;
|
|
||||||
set => _properties = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public XmlSchema? GetSchema() => null;
|
|
||||||
|
|
||||||
public void ReadXml(XmlReader reader)
|
|
||||||
{
|
|
||||||
ReadXmlAttributes(reader);
|
|
||||||
ReadXmlElements(reader);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReadXmlAttributes(XmlReader reader)
|
|
||||||
{
|
|
||||||
ID = reader.GetRequiredAttribute("id");
|
|
||||||
Name = reader.GetRequiredAttribute("name");
|
|
||||||
Class = reader.GetOptionalAttribute("class") ?? ""; // default value
|
|
||||||
X = reader.GetRequiredAttribute<uint>("x");
|
|
||||||
Y = reader.GetRequiredAttribute<uint>("y");
|
|
||||||
Width = reader.GetRequiredAttribute<uint>("width");
|
|
||||||
Height = reader.GetRequiredAttribute<uint>("height");
|
|
||||||
Opacity = reader.GetRequiredAttribute<float>("opacity");
|
|
||||||
Visible = reader.GetRequiredAttribute<uint>("visible") == 1;
|
|
||||||
TintColor = reader.GetOptionalAttributeClass<TiledColor>("tintcolor");
|
|
||||||
OffsetX = reader.GetRequiredAttribute<float>("offsetx");
|
|
||||||
OffsetY = reader.GetRequiredAttribute<float>("offsety");
|
|
||||||
ParallaxX = reader.GetRequiredAttribute<float>("parallaxx");
|
|
||||||
ParallaxY = reader.GetRequiredAttribute<float>("parallaxy");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReadXmlElements(XmlReader reader)
|
|
||||||
{
|
|
||||||
reader.ReadStartElement("layer");
|
|
||||||
|
|
||||||
while (reader.IsStartElement())
|
|
||||||
{
|
|
||||||
var name = reader.Name;
|
|
||||||
Action action = name switch
|
|
||||||
{
|
|
||||||
"properties" => () => Helpers.SetAtMostOnce(ref _properties, XmlHelpers.ReadProperties(reader), "Properties"),
|
|
||||||
"data" => reader.Skip,
|
|
||||||
_ => reader.Skip
|
|
||||||
};
|
|
||||||
|
|
||||||
action();
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.ReadEndElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteXml(XmlWriter writer)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[XmlRoot(ElementName = "map")]
|
|
||||||
public class Map : IXmlSerializable
|
|
||||||
{
|
|
||||||
public required string Version { get; set; }
|
|
||||||
public string? TiledVersion { get; set; }
|
|
||||||
public required string Class { get; set; }
|
|
||||||
public required Orientation Orientation { get; set; }
|
|
||||||
public required RenderOrder RenderOrder { get; set; }
|
|
||||||
public required int CompressionLevel { get; set; }
|
|
||||||
public required uint Width { get; set; }
|
|
||||||
public required uint Height { get; set; }
|
|
||||||
public required uint TileWidth { get; set; }
|
|
||||||
public required uint TileHeight { get; set; }
|
|
||||||
public uint? HexSideLength { get; set; }
|
|
||||||
public StaggerAxis? StaggerAxis { get; set; }
|
|
||||||
public StaggerIndex? StaggerIndex { get; set; }
|
|
||||||
public required float ParallaxOriginX { get; set; }
|
|
||||||
public required float ParallaxOriginY { get; set; }
|
|
||||||
public TiledColor? BackgroundColor { get; set; }
|
|
||||||
public required uint NextLayerId { get; set; }
|
|
||||||
public required uint NextObjectId { get; set; }
|
|
||||||
public required bool Infinite { get; set; }
|
|
||||||
|
|
||||||
private Dictionary<string, IProperty>? _properties = null;
|
|
||||||
public required Dictionary<string, IProperty>? Properties
|
|
||||||
{
|
|
||||||
get => _properties;
|
|
||||||
set => _properties = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public required List<BaseTileset> Tilesets { get; set; } = [];
|
|
||||||
|
|
||||||
public T GetProperty<T>(string propertyName) where T : IProperty
|
|
||||||
{
|
|
||||||
if (Properties is null)
|
|
||||||
throw new InvalidOperationException("Properties not set");
|
|
||||||
|
|
||||||
return (T)Properties[propertyName];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map LoadFromStream(Stream stream)
|
|
||||||
{
|
|
||||||
using var reader = new StreamReader(stream, Encoding.UTF8);
|
|
||||||
var serializer = new XmlSerializer(typeof(Map));
|
|
||||||
return (Map)serializer.Deserialize(reader)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
public XmlSchema? GetSchema() => null;
|
|
||||||
|
|
||||||
public void ReadXml(XmlReader reader)
|
|
||||||
{
|
|
||||||
ReadXmlAttributes(reader);
|
|
||||||
ReadXmlElements(reader, (s) => null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReadXmlAttributes(XmlReader reader)
|
|
||||||
{
|
|
||||||
Version = reader.GetRequiredAttribute("version");
|
|
||||||
TiledVersion = reader.GetOptionalAttribute("tiledversion");
|
|
||||||
Class = reader.GetOptionalAttribute("class") ?? ""; // default value
|
|
||||||
Orientation = reader.GetRequiredAttributeEnum<Orientation>("orientation");
|
|
||||||
RenderOrder = reader.GetRequiredAttributeEnum<RenderOrder>("renderorder");
|
|
||||||
CompressionLevel = reader.GetRequiredAttribute<int>("compressionlevel");
|
|
||||||
Width = reader.GetRequiredAttribute<uint>("width");
|
|
||||||
Height = reader.GetRequiredAttribute<uint>("height");
|
|
||||||
TileWidth = reader.GetRequiredAttribute<uint>("tilewidth");
|
|
||||||
TileHeight = reader.GetRequiredAttribute<uint>("tileheight");
|
|
||||||
HexSideLength = reader.GetOptionalAttribute<uint>("hexsidelength");
|
|
||||||
StaggerAxis = reader.GetOptionalAttributeEnum<StaggerAxis>("staggeraxis");
|
|
||||||
StaggerIndex = reader.GetOptionalAttributeEnum<StaggerIndex>("staggerindex");
|
|
||||||
ParallaxOriginX = reader.GetRequiredAttribute<float>("parallaxoriginx");
|
|
||||||
ParallaxOriginY = reader.GetRequiredAttribute<float>("parallaxoriginy");
|
|
||||||
BackgroundColor = reader.GetOptionalAttributeClass<TiledColor>("backgroundcolor");
|
|
||||||
NextLayerId = reader.GetRequiredAttribute<uint>("nextlayerid");
|
|
||||||
NextObjectId = reader.GetRequiredAttribute<uint>("nextobjectid");
|
|
||||||
Infinite = reader.GetRequiredAttribute<uint>("infinite") == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReadXmlElements(XmlReader reader, Func<string, BaseTileset> tilesetResolver)
|
|
||||||
{
|
|
||||||
reader.ReadStartElement("map");
|
|
||||||
|
|
||||||
while (reader.IsStartElement())
|
|
||||||
{
|
|
||||||
var name = reader.Name;
|
|
||||||
Action action = name switch
|
|
||||||
{
|
|
||||||
"properties" => () => Helpers.SetAtMostOnce(ref _properties, XmlHelpers.ReadProperties(reader), "Properties"),
|
|
||||||
"editorsettings" => reader.Skip,
|
|
||||||
"tileset" => () => Tilesets.Add(XmlHelpers.ReadTileset(reader, tilesetResolver)),
|
|
||||||
_ => reader.Skip
|
|
||||||
};
|
|
||||||
|
|
||||||
action();
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.ReadEndElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteXml(XmlWriter writer)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
69
DotTiled/Model/Color.cs
Normal file
69
DotTiled/Model/Color.cs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public class Color : IParsable<Color>, IEquatable<Color>
|
||||||
|
{
|
||||||
|
public required byte R { get; set; }
|
||||||
|
public required byte G { get; set; }
|
||||||
|
public required byte B { get; set; }
|
||||||
|
public byte A { get; set; } = 255;
|
||||||
|
|
||||||
|
public static Color Parse(string s, IFormatProvider? provider)
|
||||||
|
{
|
||||||
|
TryParse(s, provider, out var result);
|
||||||
|
return result ?? throw new FormatException($"Invalid format for TiledColor: {s}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryParse(
|
||||||
|
[NotNullWhen(true)] string? s,
|
||||||
|
IFormatProvider? provider,
|
||||||
|
[MaybeNullWhen(false)] out Color result)
|
||||||
|
{
|
||||||
|
if (s is not null && !s.StartsWith('#'))
|
||||||
|
return TryParse($"#{s}", provider, out result);
|
||||||
|
|
||||||
|
// Format: #RRGGBB or #AARRGGBB
|
||||||
|
if (s is null || s.Length != 7 && s.Length != 9 || s[0] != '#')
|
||||||
|
{
|
||||||
|
result = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.Length == 7)
|
||||||
|
{
|
||||||
|
result = new Color
|
||||||
|
{
|
||||||
|
R = byte.Parse(s[1..3], NumberStyles.HexNumber, provider),
|
||||||
|
G = byte.Parse(s[3..5], NumberStyles.HexNumber, provider),
|
||||||
|
B = byte.Parse(s[5..7], NumberStyles.HexNumber, provider)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = new Color
|
||||||
|
{
|
||||||
|
A = byte.Parse(s[1..3], NumberStyles.HexNumber, provider),
|
||||||
|
R = byte.Parse(s[3..5], NumberStyles.HexNumber, provider),
|
||||||
|
G = byte.Parse(s[5..7], NumberStyles.HexNumber, provider),
|
||||||
|
B = byte.Parse(s[7..9], NumberStyles.HexNumber, provider)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(Color? other)
|
||||||
|
{
|
||||||
|
if (other is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return R == other.R && G == other.G && B == other.B && A == other.A;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj) => obj is Color other && Equals(other);
|
||||||
|
|
||||||
|
public override int GetHashCode() => HashCode.Combine(R, G, B, A);
|
||||||
|
}
|
78
DotTiled/Model/IProperty.cs
Normal file
78
DotTiled/Model/IProperty.cs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public enum PropertyType
|
||||||
|
{
|
||||||
|
String,
|
||||||
|
Int,
|
||||||
|
Float,
|
||||||
|
Bool,
|
||||||
|
Color,
|
||||||
|
File,
|
||||||
|
Object,
|
||||||
|
Class
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IProperty
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public PropertyType Type { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StringProperty : IProperty
|
||||||
|
{
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public PropertyType Type => PropertyType.String;
|
||||||
|
public required string Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IntProperty : IProperty
|
||||||
|
{
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public PropertyType Type => PropertyType.Int;
|
||||||
|
public required int Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FloatProperty : IProperty
|
||||||
|
{
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public PropertyType Type => PropertyType.Float;
|
||||||
|
public required float Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BoolProperty : IProperty
|
||||||
|
{
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public PropertyType Type => PropertyType.Bool;
|
||||||
|
public required bool Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ColorProperty : IProperty
|
||||||
|
{
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public PropertyType Type => PropertyType.Color;
|
||||||
|
public required Color Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FileProperty : IProperty
|
||||||
|
{
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public PropertyType Type => PropertyType.File;
|
||||||
|
public required string Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ObjectProperty : IProperty
|
||||||
|
{
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public PropertyType Type => PropertyType.Object;
|
||||||
|
public required uint Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ClassProperty : IProperty
|
||||||
|
{
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public PropertyType Type => DotTiled.PropertyType.Class;
|
||||||
|
public required string PropertyType { get; set; }
|
||||||
|
public required Dictionary<string, IProperty> Properties { get; set; }
|
||||||
|
}
|
23
DotTiled/Model/Layers/BaseLayer.cs
Normal file
23
DotTiled/Model/Layers/BaseLayer.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public abstract class BaseLayer
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
public required uint ID { get; set; }
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
public string Class { get; set; } = "";
|
||||||
|
public uint X { get; set; } = 0;
|
||||||
|
public uint Y { get; set; } = 0;
|
||||||
|
public float Opacity { get; set; } = 1.0f;
|
||||||
|
public bool Visible { get; set; } = true;
|
||||||
|
public Color? TintColor { get; set; }
|
||||||
|
public float OffsetX { get; set; } = 0.0f;
|
||||||
|
public float OffsetY { get; set; } = 0.0f;
|
||||||
|
public float ParallaxX { get; set; } = 1.0f;
|
||||||
|
public float ParallaxY { get; set; } = 1.0f;
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
public Dictionary<string, IProperty>? Properties { get; set; }
|
||||||
|
}
|
51
DotTiled/Model/Layers/Data.cs
Normal file
51
DotTiled/Model/Layers/Data.cs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public enum DataEncoding
|
||||||
|
{
|
||||||
|
Csv,
|
||||||
|
Base64
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DataCompression
|
||||||
|
{
|
||||||
|
GZip,
|
||||||
|
ZLib,
|
||||||
|
ZStd
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum FlippingFlags : uint
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
FlippedHorizontally = 0x80000000u,
|
||||||
|
FlippedVertically = 0x40000000u,
|
||||||
|
FlippedDiagonally = 0x20000000u,
|
||||||
|
RotatedHexagonal120 = 0x10000000u
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Chunk
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
public required int X { get; set; }
|
||||||
|
public required int Y { get; set; }
|
||||||
|
public required uint Width { get; set; }
|
||||||
|
public required uint Height { get; set; }
|
||||||
|
|
||||||
|
// Data
|
||||||
|
public required uint[] GlobalTileIDs { get; set; }
|
||||||
|
public required FlippingFlags[] FlippingFlags { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Data
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
public DataEncoding? Encoding { get; set; }
|
||||||
|
public DataCompression? Compression { get; set; }
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
public uint[]? GlobalTileIDs { get; set; }
|
||||||
|
public FlippingFlags[]? FlippingFlags { get; set; }
|
||||||
|
public Chunk[]? Chunks { get; set; }
|
||||||
|
}
|
11
DotTiled/Model/Layers/ImageLayer.cs
Normal file
11
DotTiled/Model/Layers/ImageLayer.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public class ImageLayer : BaseLayer
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
public required bool RepeatX { get; set; }
|
||||||
|
public required bool RepeatY { get; set; }
|
||||||
|
|
||||||
|
// At most one of
|
||||||
|
public Image? Image { get; set; }
|
||||||
|
}
|
21
DotTiled/Model/Layers/ObjectLayer.cs
Normal file
21
DotTiled/Model/Layers/ObjectLayer.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public enum DrawOrder
|
||||||
|
{
|
||||||
|
TopDown,
|
||||||
|
Index
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ObjectLayer : BaseLayer
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
public uint? Width { get; set; }
|
||||||
|
public uint? Height { get; set; }
|
||||||
|
public required Color? Color { get; set; }
|
||||||
|
public required DrawOrder DrawOrder { get; set; } = DrawOrder.TopDown;
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
public required List<Object> Objects { get; set; }
|
||||||
|
}
|
3
DotTiled/Model/Layers/Objects/EllipseObject.cs
Normal file
3
DotTiled/Model/Layers/Objects/EllipseObject.cs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public class EllipseObject : Object { }
|
22
DotTiled/Model/Layers/Objects/Object.cs
Normal file
22
DotTiled/Model/Layers/Objects/Object.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public abstract class Object
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
public required uint ID { get; set; }
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
public string Type { get; set; } = "";
|
||||||
|
public float X { get; set; } = 0f;
|
||||||
|
public float Y { get; set; } = 0f;
|
||||||
|
public float Width { get; set; } = 0f;
|
||||||
|
public float Height { get; set; } = 0f;
|
||||||
|
public float Rotation { get; set; } = 0f;
|
||||||
|
public uint? GID { get; set; }
|
||||||
|
public bool Visible { get; set; } = true;
|
||||||
|
public string? Template { get; set; }
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
public Dictionary<string, IProperty>? Properties { get; set; }
|
||||||
|
}
|
3
DotTiled/Model/Layers/Objects/PointObject.cs
Normal file
3
DotTiled/Model/Layers/Objects/PointObject.cs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public class PointObject : Object { }
|
10
DotTiled/Model/Layers/Objects/PolygonObject.cs
Normal file
10
DotTiled/Model/Layers/Objects/PolygonObject.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public class PolygonObject : Object
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
public required List<Vector2> Points { get; set; }
|
||||||
|
}
|
10
DotTiled/Model/Layers/Objects/PolylineObject.cs
Normal file
10
DotTiled/Model/Layers/Objects/PolylineObject.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public class PolylineObject : Object
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
public required List<Vector2> Points { get; set; }
|
||||||
|
}
|
3
DotTiled/Model/Layers/Objects/RectangleObject.cs
Normal file
3
DotTiled/Model/Layers/Objects/RectangleObject.cs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public class RectangleObject : Object { }
|
38
DotTiled/Model/Layers/Objects/TextObject.cs
Normal file
38
DotTiled/Model/Layers/Objects/TextObject.cs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
|
||||||
|
public enum TextHorizontalAlignment
|
||||||
|
{
|
||||||
|
Left,
|
||||||
|
Center,
|
||||||
|
Right,
|
||||||
|
Justify
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TextVerticalAlignment
|
||||||
|
{
|
||||||
|
Top,
|
||||||
|
Center,
|
||||||
|
Bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TextObject : Object
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
public string FontFamily { get; set; } = "sans-serif";
|
||||||
|
public int PixelSize { get; set; } = 16;
|
||||||
|
public bool Wrap { get; set; } = false;
|
||||||
|
public Color Color { get; set; } = Color.Parse("#000000", CultureInfo.InvariantCulture);
|
||||||
|
public bool Bold { get; set; } = false;
|
||||||
|
public bool Italic { get; set; } = false;
|
||||||
|
public bool Underline { get; set; } = false;
|
||||||
|
public bool Strikeout { get; set; } = false;
|
||||||
|
public bool Kerning { get; set; } = true;
|
||||||
|
public TextHorizontalAlignment HorizontalAlignment { get; set; } = TextHorizontalAlignment.Left;
|
||||||
|
public TextVerticalAlignment VerticalAlignment { get; set; } = TextVerticalAlignment.Top;
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
public string Text { get; set; } = "";
|
||||||
|
}
|
11
DotTiled/Model/Layers/TileLayer.cs
Normal file
11
DotTiled/Model/Layers/TileLayer.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public class TileLayer : BaseLayer
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
public required uint Width { get; set; }
|
||||||
|
public required uint Height { get; set; }
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
public Data? Data { get; set; }
|
||||||
|
}
|
64
DotTiled/Model/Map.cs
Normal file
64
DotTiled/Model/Map.cs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public enum MapOrientation
|
||||||
|
{
|
||||||
|
Orthogonal,
|
||||||
|
Isometric,
|
||||||
|
Staggered,
|
||||||
|
Hexagonal
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RenderOrder
|
||||||
|
{
|
||||||
|
RightDown,
|
||||||
|
RightUp,
|
||||||
|
LeftDown,
|
||||||
|
LeftUp
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum StaggerAxis
|
||||||
|
{
|
||||||
|
X,
|
||||||
|
Y
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum StaggerIndex
|
||||||
|
{
|
||||||
|
Odd,
|
||||||
|
Even
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Map
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
public required string Version { get; set; }
|
||||||
|
public required string TiledVersion { get; set; }
|
||||||
|
public string Class { get; set; } = "";
|
||||||
|
public required MapOrientation Orientation { get; set; }
|
||||||
|
public RenderOrder RenderOrder { get; set; } = RenderOrder.RightDown;
|
||||||
|
public int CompressionLevel { get; set; } = -1;
|
||||||
|
public required uint Width { get; set; }
|
||||||
|
public required uint Height { get; set; }
|
||||||
|
public required uint TileWidth { get; set; }
|
||||||
|
public required uint TileHeight { get; set; }
|
||||||
|
public uint? HexSideLength { get; set; }
|
||||||
|
public StaggerAxis? StaggerAxis { get; set; }
|
||||||
|
public StaggerIndex? StaggerIndex { get; set; }
|
||||||
|
public float ParallaxOriginX { get; set; } = 0.0f;
|
||||||
|
public float ParallaxOriginY { get; set; } = 0.0f;
|
||||||
|
public Color BackgroundColor { get; set; } = Color.Parse("#00000000", CultureInfo.InvariantCulture);
|
||||||
|
public required uint NextLayerID { get; set; }
|
||||||
|
public required uint NextObjectID { get; set; }
|
||||||
|
public bool Infinite { get; set; } = false;
|
||||||
|
|
||||||
|
// At most one of
|
||||||
|
public Dictionary<string, IProperty>? Properties { get; set; }
|
||||||
|
|
||||||
|
// Any number of
|
||||||
|
public List<Tileset> Tilesets { get; set; } = [];
|
||||||
|
public List<BaseLayer> Layers { get; set; } = [];
|
||||||
|
// public List<Group> Groups { get; set; } = [];
|
||||||
|
}
|
8
DotTiled/Model/Tileset/Frame.cs
Normal file
8
DotTiled/Model/Tileset/Frame.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public class Frame
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
public required uint TileID { get; set; }
|
||||||
|
public required uint Duration { get; set; }
|
||||||
|
}
|
15
DotTiled/Model/Tileset/Grid.cs
Normal file
15
DotTiled/Model/Tileset/Grid.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public enum GridOrientation
|
||||||
|
{
|
||||||
|
Orthogonal,
|
||||||
|
Isometric
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Grid
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
public GridOrientation Orientation { get; set; } = GridOrientation.Orthogonal;
|
||||||
|
public required uint Width { get; set; }
|
||||||
|
public required uint Height { get; set; }
|
||||||
|
}
|
19
DotTiled/Model/Tileset/Image.cs
Normal file
19
DotTiled/Model/Tileset/Image.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public enum ImageFormat
|
||||||
|
{
|
||||||
|
Png,
|
||||||
|
Gif,
|
||||||
|
Jpg,
|
||||||
|
Bmp
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Image
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
public ImageFormat? Format { get; set; }
|
||||||
|
public string? Source { get; set; }
|
||||||
|
public Color? TransparentColor { get; set; }
|
||||||
|
public uint? Width { get; set; }
|
||||||
|
public uint? Height { get; set; }
|
||||||
|
}
|
21
DotTiled/Model/Tileset/Tile.cs
Normal file
21
DotTiled/Model/Tileset/Tile.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public class Tile
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
public required uint ID { get; set; }
|
||||||
|
public string Type { get; set; } = "";
|
||||||
|
public float Probability { get; set; } = 0f;
|
||||||
|
public uint X { get; set; } = 0;
|
||||||
|
public uint Y { get; set; } = 0;
|
||||||
|
public required uint Width { get; set; }
|
||||||
|
public required uint Height { get; set; }
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
public Dictionary<string, IProperty>? Properties { get; set; }
|
||||||
|
public Image? Image { get; set; }
|
||||||
|
public ObjectLayer? ObjectLayer { get; set; }
|
||||||
|
public List<Frame>? Animation { get; set; }
|
||||||
|
}
|
8
DotTiled/Model/Tileset/TileOffset.cs
Normal file
8
DotTiled/Model/Tileset/TileOffset.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public class TileOffset
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
public float X { get; set; } = 0f;
|
||||||
|
public float Y { get; set; } = 0f;
|
||||||
|
}
|
61
DotTiled/Model/Tileset/Tileset.cs
Normal file
61
DotTiled/Model/Tileset/Tileset.cs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public enum ObjectAlignment
|
||||||
|
{
|
||||||
|
Unspecified,
|
||||||
|
TopLeft,
|
||||||
|
Top,
|
||||||
|
TopRight,
|
||||||
|
Left,
|
||||||
|
Center,
|
||||||
|
Right,
|
||||||
|
BottomLeft,
|
||||||
|
Bottom,
|
||||||
|
BottomRight
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TileRenderSize
|
||||||
|
{
|
||||||
|
Tile,
|
||||||
|
Grid
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum FillMode
|
||||||
|
{
|
||||||
|
Stretch,
|
||||||
|
PreserveAspectFit
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Tileset
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
public string? Version { get; set; }
|
||||||
|
public string? TiledVersion { get; set; }
|
||||||
|
public uint? FirstGID { get; set; }
|
||||||
|
public string? Source { get; set; }
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public string? Class { get; set; } = "";
|
||||||
|
public uint? TileWidth { get; set; }
|
||||||
|
public uint? TileHeight { get; set; }
|
||||||
|
public float? Spacing { get; set; }
|
||||||
|
public float? Margin { get; set; }
|
||||||
|
public uint? TileCount { get; set; }
|
||||||
|
public uint? Columns { get; set; }
|
||||||
|
public ObjectAlignment ObjectAlignment { get; set; } = ObjectAlignment.Unspecified;
|
||||||
|
public TileRenderSize RenderSize { get; set; } = TileRenderSize.Tile;
|
||||||
|
public FillMode FillMode { get; set; } = FillMode.Stretch;
|
||||||
|
|
||||||
|
// At most one of
|
||||||
|
public Image? Image { get; set; }
|
||||||
|
public TileOffset? TileOffset { get; set; }
|
||||||
|
public Grid? Grid { get; set; }
|
||||||
|
public Dictionary<string, IProperty>? Properties { get; set; }
|
||||||
|
// public List<Terrain>? TerrainTypes { get; set; } TODO: Implement Terrain -> Wangset conversion during deserialization
|
||||||
|
public List<Wangset>? Wangsets { get; set; }
|
||||||
|
public Transformations? Transformations { get; set; }
|
||||||
|
|
||||||
|
// Any number of
|
||||||
|
public List<Tile> Tiles { get; set; } = [];
|
||||||
|
}
|
10
DotTiled/Model/Tileset/Transformations.cs
Normal file
10
DotTiled/Model/Tileset/Transformations.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public class Transformations
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
public bool HFlip { get; set; } = false;
|
||||||
|
public bool VFlip { get; set; } = false;
|
||||||
|
public bool Rotate { get; set; } = false;
|
||||||
|
public bool PreferUntransformed { get; set; } = false;
|
||||||
|
}
|
16
DotTiled/Model/Tileset/WangColor.cs
Normal file
16
DotTiled/Model/Tileset/WangColor.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public class WangColor
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public string Class { get; set; } = "";
|
||||||
|
public required Color Color { get; set; }
|
||||||
|
public required uint Tile { get; set; }
|
||||||
|
public float Probability { get; set; } = 0f;
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
public Dictionary<string, IProperty>? Properties { get; set; }
|
||||||
|
}
|
8
DotTiled/Model/Tileset/WangTile.cs
Normal file
8
DotTiled/Model/Tileset/WangTile.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public class WangTile
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
public required uint TileID { get; set; }
|
||||||
|
public required byte[] WangID { get; set; }
|
||||||
|
}
|
21
DotTiled/Model/Tileset/Wangset.cs
Normal file
21
DotTiled/Model/Tileset/Wangset.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public class Wangset
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public string Class { get; set; } = "";
|
||||||
|
public required uint Tile { get; set; }
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
// At most one of
|
||||||
|
public Dictionary<string, IProperty>? Properties { get; set; }
|
||||||
|
|
||||||
|
// Up to 254 Wang colors
|
||||||
|
public List<WangColor>? WangColors { get; set; } = [];
|
||||||
|
|
||||||
|
// Any number of
|
||||||
|
public List<WangTile> WangTiles { get; set; } = [];
|
||||||
|
}
|
140
DotTiled/TmxSerializer/ExtensionsXmlReader.cs
Normal file
140
DotTiled/TmxSerializer/ExtensionsXmlReader.cs
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
internal static class ExtensionsXmlReader
|
||||||
|
{
|
||||||
|
internal static string GetRequiredAttribute(this XmlReader reader, string attribute)
|
||||||
|
{
|
||||||
|
return reader.GetAttribute(attribute) ?? throw new XmlException($"{attribute} attribute is required"); ;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static T GetRequiredAttributeParseable<T>(this XmlReader reader, string attribute) where T : IParsable<T>
|
||||||
|
{
|
||||||
|
var value = reader.GetAttribute(attribute) ?? throw new XmlException($"{attribute} attribute is required");
|
||||||
|
return T.Parse(value, CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static T GetRequiredAttributeParseable<T>(this XmlReader reader, string attribute, Func<string, T> parser)
|
||||||
|
{
|
||||||
|
var value = reader.GetAttribute(attribute) ?? throw new XmlException($"{attribute} attribute is required");
|
||||||
|
return parser(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static T GetRequiredAttributeEnum<T>(this XmlReader reader, string attribute, Func<string, T> enumParser) where T : Enum
|
||||||
|
{
|
||||||
|
var value = reader.GetAttribute(attribute) ?? throw new XmlException($"{attribute} attribute is required");
|
||||||
|
return enumParser(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string? GetOptionalAttribute(this XmlReader reader, string attribute, string? defaultValue = default)
|
||||||
|
{
|
||||||
|
return reader.GetAttribute(attribute) ?? defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static T? GetOptionalAttributeParseable<T>(this XmlReader reader, string attribute) where T : struct, IParsable<T>
|
||||||
|
{
|
||||||
|
var value = reader.GetAttribute(attribute);
|
||||||
|
if (value is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return T.Parse(value, CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static T? GetOptionalAttributeParseable<T>(this XmlReader reader, string attribute, Func<string, T> parser) where T : struct
|
||||||
|
{
|
||||||
|
var value = reader.GetAttribute(attribute);
|
||||||
|
if (value is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return parser(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static T? GetOptionalAttributeClass<T>(this XmlReader reader, string attribute) where T : class, IParsable<T>
|
||||||
|
{
|
||||||
|
var value = reader.GetAttribute(attribute);
|
||||||
|
if (value is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return T.Parse(value, CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static T? GetOptionalAttributeEnum<T>(this XmlReader reader, string attribute, Func<string, T> enumParser) where T : struct, Enum
|
||||||
|
{
|
||||||
|
var value = reader.GetAttribute(attribute);
|
||||||
|
return value != null ? enumParser(value) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static List<T> ReadList<T>(this XmlReader reader, string wrapper, string elementName, Func<XmlReader, T> readElement)
|
||||||
|
{
|
||||||
|
var list = new List<T>();
|
||||||
|
|
||||||
|
if (reader.IsEmptyElement)
|
||||||
|
return list;
|
||||||
|
|
||||||
|
reader.ReadStartElement(wrapper);
|
||||||
|
while (reader.IsStartElement(elementName))
|
||||||
|
{
|
||||||
|
list.Add(readElement(reader));
|
||||||
|
|
||||||
|
if (reader.NodeType == XmlNodeType.EndElement)
|
||||||
|
continue; // At end of list, no need to read again
|
||||||
|
|
||||||
|
reader.Read();
|
||||||
|
}
|
||||||
|
reader.ReadEndElement();
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void ProcessChildren(this XmlReader reader, string wrapper, Func<XmlReader, string, Action> getProcessAction)
|
||||||
|
{
|
||||||
|
if (reader.IsEmptyElement)
|
||||||
|
{
|
||||||
|
reader.ReadStartElement(wrapper);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.ReadStartElement(wrapper);
|
||||||
|
while (reader.IsStartElement())
|
||||||
|
{
|
||||||
|
var elementName = reader.Name;
|
||||||
|
var action = getProcessAction(reader, elementName);
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
reader.ReadEndElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
[return: NotNull]
|
||||||
|
internal static List<T> ProcessChildren<T>(this XmlReader reader, string wrapper, Func<XmlReader, string, T> getProcessAction)
|
||||||
|
{
|
||||||
|
var list = new List<T>();
|
||||||
|
|
||||||
|
if (reader.IsEmptyElement)
|
||||||
|
{
|
||||||
|
reader.ReadStartElement(wrapper);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.ReadStartElement(wrapper);
|
||||||
|
while (reader.IsStartElement())
|
||||||
|
{
|
||||||
|
var elementName = reader.Name;
|
||||||
|
var item = getProcessAction(reader, elementName);
|
||||||
|
list.Add(item);
|
||||||
|
}
|
||||||
|
reader.ReadEndElement();
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void SkipXmlDeclaration(this XmlReader reader)
|
||||||
|
{
|
||||||
|
if (reader.NodeType == XmlNodeType.XmlDeclaration)
|
||||||
|
reader.Read();
|
||||||
|
}
|
||||||
|
}
|
28
DotTiled/TmxSerializer/TmxSerializer.Chunk.cs
Normal file
28
DotTiled/TmxSerializer/TmxSerializer.Chunk.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public partial class TmxSerializer
|
||||||
|
{
|
||||||
|
private Chunk ReadChunk(XmlReader reader, DataEncoding? encoding, DataCompression? compression)
|
||||||
|
{
|
||||||
|
var x = reader.GetRequiredAttributeParseable<int>("x");
|
||||||
|
var y = reader.GetRequiredAttributeParseable<int>("y");
|
||||||
|
var width = reader.GetRequiredAttributeParseable<uint>("width");
|
||||||
|
var height = reader.GetRequiredAttributeParseable<uint>("height");
|
||||||
|
|
||||||
|
var usesTileChildrenInsteadOfRawData = encoding is null;
|
||||||
|
if (usesTileChildrenInsteadOfRawData)
|
||||||
|
{
|
||||||
|
var globalTileIDsWithFlippingFlags = ReadTileChildrenInWrapper("chunk", reader);
|
||||||
|
var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(globalTileIDsWithFlippingFlags);
|
||||||
|
return new Chunk { X = x, Y = y, Width = width, Height = height, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var globalTileIDsWithFlippingFlags = ReadRawData(reader, encoding!.Value, compression);
|
||||||
|
var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(globalTileIDsWithFlippingFlags);
|
||||||
|
return new Chunk { X = x, Y = y, Width = width, Height = height, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
122
DotTiled/TmxSerializer/TmxSerializer.Data.cs
Normal file
122
DotTiled/TmxSerializer/TmxSerializer.Data.cs
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public partial class TmxSerializer
|
||||||
|
{
|
||||||
|
private Data ReadData(XmlReader reader, bool usesChunks)
|
||||||
|
{
|
||||||
|
var encoding = reader.GetOptionalAttributeEnum<DataEncoding>("encoding", e => e switch
|
||||||
|
{
|
||||||
|
"csv" => DataEncoding.Csv,
|
||||||
|
"base64" => DataEncoding.Base64,
|
||||||
|
_ => throw new XmlException("Invalid encoding")
|
||||||
|
});
|
||||||
|
var compression = reader.GetOptionalAttributeEnum<DataCompression>("compression", c => c switch
|
||||||
|
{
|
||||||
|
"gzip" => DataCompression.GZip,
|
||||||
|
"zlib" => DataCompression.ZLib,
|
||||||
|
"zstd" => DataCompression.ZStd,
|
||||||
|
_ => throw new XmlException("Invalid compression")
|
||||||
|
});
|
||||||
|
|
||||||
|
if (usesChunks)
|
||||||
|
{
|
||||||
|
var chunks = reader
|
||||||
|
.ReadList("data", "chunk", (r) => ReadChunk(r, encoding, compression))
|
||||||
|
.ToArray();
|
||||||
|
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = null, Chunks = chunks };
|
||||||
|
}
|
||||||
|
|
||||||
|
var usesTileChildrenInsteadOfRawData = encoding is null && compression is null;
|
||||||
|
if (usesTileChildrenInsteadOfRawData)
|
||||||
|
{
|
||||||
|
var tileChildrenGlobalTileIDsWithFlippingFlags = ReadTileChildrenInWrapper("data", reader);
|
||||||
|
var (tileChildrenGlobalTileIDs, tileChildrenFlippingFlags) = ReadAndClearFlippingFlagsFromGIDs(tileChildrenGlobalTileIDsWithFlippingFlags);
|
||||||
|
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = tileChildrenGlobalTileIDs, FlippingFlags = tileChildrenFlippingFlags, Chunks = null };
|
||||||
|
}
|
||||||
|
|
||||||
|
var rawDataGlobalTileIDsWithFlippingFlags = ReadRawData(reader, encoding!.Value, compression);
|
||||||
|
var (rawDataGlobalTileIDs, rawDataFlippingFlags) = ReadAndClearFlippingFlagsFromGIDs(rawDataGlobalTileIDsWithFlippingFlags);
|
||||||
|
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = rawDataGlobalTileIDs, FlippingFlags = rawDataFlippingFlags, Chunks = null };
|
||||||
|
}
|
||||||
|
|
||||||
|
private (uint[] GlobalTileIDs, FlippingFlags[] FlippingFlags) ReadAndClearFlippingFlagsFromGIDs(uint[] globalTileIDs)
|
||||||
|
{
|
||||||
|
var clearedGlobalTileIDs = new uint[globalTileIDs.Length];
|
||||||
|
var flippingFlags = new FlippingFlags[globalTileIDs.Length];
|
||||||
|
for (var i = 0; i < globalTileIDs.Length; i++)
|
||||||
|
{
|
||||||
|
var gid = globalTileIDs[i];
|
||||||
|
var flags = gid & 0xF0000000u;
|
||||||
|
flippingFlags[i] = (FlippingFlags)flags;
|
||||||
|
clearedGlobalTileIDs[i] = gid & 0x0FFFFFFFu;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (clearedGlobalTileIDs, flippingFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint[] ReadTileChildrenInWrapper(string wrapper, XmlReader reader)
|
||||||
|
{
|
||||||
|
return reader.ReadList(wrapper, "tile", (r) => r.GetOptionalAttributeParseable<uint>("gid") ?? 0).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint[] ReadRawData(XmlReader reader, DataEncoding encoding, DataCompression? compression)
|
||||||
|
{
|
||||||
|
var data = reader.ReadElementContentAsString();
|
||||||
|
if (encoding == DataEncoding.Csv)
|
||||||
|
return ParseCsvData(data);
|
||||||
|
|
||||||
|
using var bytes = new MemoryStream(Convert.FromBase64String(data));
|
||||||
|
if (compression is null)
|
||||||
|
return ReadMemoryStreamAsInt32Array(bytes);
|
||||||
|
|
||||||
|
var decompressed = compression switch
|
||||||
|
{
|
||||||
|
DataCompression.GZip => DecompressGZip(bytes),
|
||||||
|
DataCompression.ZLib => DecompressZLib(bytes),
|
||||||
|
DataCompression.ZStd => throw new NotSupportedException("ZStd compression is not supported."),
|
||||||
|
_ => throw new XmlException("Invalid compression")
|
||||||
|
};
|
||||||
|
|
||||||
|
return decompressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint[] ParseCsvData(string data)
|
||||||
|
{
|
||||||
|
var values = data
|
||||||
|
.Split((char[])['\n', '\r', ','], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||||
|
.Select(uint.Parse)
|
||||||
|
.ToArray();
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint[] ReadMemoryStreamAsInt32Array(Stream stream)
|
||||||
|
{
|
||||||
|
var finalValues = new List<uint>();
|
||||||
|
var int32Bytes = new byte[4];
|
||||||
|
while (stream.Read(int32Bytes, 0, 4) == 4)
|
||||||
|
{
|
||||||
|
var value = BitConverter.ToUInt32(int32Bytes, 0);
|
||||||
|
finalValues.Add(value);
|
||||||
|
}
|
||||||
|
return finalValues.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint[] DecompressGZip(MemoryStream stream)
|
||||||
|
{
|
||||||
|
using var decompressedStream = new GZipStream(stream, CompressionMode.Decompress);
|
||||||
|
return ReadMemoryStreamAsInt32Array(decompressedStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint[] DecompressZLib(MemoryStream stream)
|
||||||
|
{
|
||||||
|
using var decompressedStream = new ZLibStream(stream, CompressionMode.Decompress);
|
||||||
|
return ReadMemoryStreamAsInt32Array(decompressedStream);
|
||||||
|
}
|
||||||
|
}
|
17
DotTiled/TmxSerializer/TmxSerializer.Helpers.cs
Normal file
17
DotTiled/TmxSerializer/TmxSerializer.Helpers.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public partial class TmxSerializer
|
||||||
|
{
|
||||||
|
private static class Helpers
|
||||||
|
{
|
||||||
|
public static void SetAtMostOnce<T>(ref T? field, T value, string fieldName)
|
||||||
|
{
|
||||||
|
if (field is not null)
|
||||||
|
throw new InvalidOperationException($"{fieldName} already set");
|
||||||
|
|
||||||
|
field = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
101
DotTiled/TmxSerializer/TmxSerializer.Map.cs
Normal file
101
DotTiled/TmxSerializer/TmxSerializer.Map.cs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public partial class TmxSerializer
|
||||||
|
{
|
||||||
|
private Map ReadMap(XmlReader reader)
|
||||||
|
{
|
||||||
|
// 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 Exception($"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 Exception($"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 Exception($"Unknown stagger axis '{s}'")
|
||||||
|
});
|
||||||
|
var staggerIndex = reader.GetOptionalAttributeEnum<StaggerIndex>("staggerindex", s => s switch
|
||||||
|
{
|
||||||
|
"odd" => StaggerIndex.Odd,
|
||||||
|
"even" => StaggerIndex.Even,
|
||||||
|
_ => throw new Exception($"Unknown stagger index '{s}'")
|
||||||
|
});
|
||||||
|
var parallaxOriginX = reader.GetOptionalAttributeParseable<float>("parallaxoriginx") ?? 0.0f;
|
||||||
|
var parallaxOriginY = reader.GetOptionalAttributeParseable<float>("parallaxoriginy") ?? 0.0f;
|
||||||
|
var backgroundColor = reader.GetOptionalAttributeClass<Color>("backgroundcolor") ?? Color.Parse("#00000000", CultureInfo.InvariantCulture);
|
||||||
|
var nextLayerID = reader.GetRequiredAttributeParseable<uint>("nextlayerid");
|
||||||
|
var nextObjectID = reader.GetRequiredAttributeParseable<uint>("nextobjectid");
|
||||||
|
var infinite = (reader.GetOptionalAttributeParseable<uint>("infinite") ?? 0) == 1;
|
||||||
|
|
||||||
|
// At most one of
|
||||||
|
Dictionary<string, IProperty>? properties = null;
|
||||||
|
|
||||||
|
// Any number of
|
||||||
|
List<BaseLayer> layers = [];
|
||||||
|
List<Tileset> tilesets = [];
|
||||||
|
|
||||||
|
reader.ProcessChildren("map", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"),
|
||||||
|
"tileset" => () => tilesets.Add(ReadTileset(r)),
|
||||||
|
"layer" => () => layers.Add(ReadTileLayer(r, dataUsesChunks: infinite)),
|
||||||
|
"objectgroup" => () => layers.Add(ReadObjectLayer(r)),
|
||||||
|
"imagelayer" => () => layers.Add(ReadImageLayer(r)),
|
||||||
|
_ => 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
218
DotTiled/TmxSerializer/TmxSerializer.ObjectLayer.cs
Normal file
218
DotTiled/TmxSerializer/TmxSerializer.ObjectLayer.cs
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public partial class TmxSerializer
|
||||||
|
{
|
||||||
|
private ObjectLayer ReadObjectLayer(XmlReader reader)
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var id = reader.GetRequiredAttributeParseable<uint>("id");
|
||||||
|
var name = reader.GetOptionalAttribute("name") ?? "";
|
||||||
|
var @class = reader.GetOptionalAttribute("class") ?? "";
|
||||||
|
var x = reader.GetOptionalAttributeParseable<uint>("x") ?? 0;
|
||||||
|
var y = reader.GetOptionalAttributeParseable<uint>("y") ?? 0;
|
||||||
|
var width = reader.GetOptionalAttributeParseable<uint>("width");
|
||||||
|
var height = reader.GetOptionalAttributeParseable<uint>("height");
|
||||||
|
var opacity = reader.GetOptionalAttributeParseable<float>("opacity") ?? 1.0f;
|
||||||
|
var visible = reader.GetOptionalAttributeParseable<bool>("visible") ?? true;
|
||||||
|
var tintColor = reader.GetOptionalAttributeClass<Color>("tintcolor");
|
||||||
|
var offsetX = reader.GetOptionalAttributeParseable<float>("offsetx") ?? 0.0f;
|
||||||
|
var offsetY = reader.GetOptionalAttributeParseable<float>("offsety") ?? 0.0f;
|
||||||
|
var parallaxX = reader.GetOptionalAttributeParseable<float>("parallaxx") ?? 1.0f;
|
||||||
|
var parallaxY = reader.GetOptionalAttributeParseable<float>("parallaxy") ?? 1.0f;
|
||||||
|
var color = reader.GetOptionalAttributeClass<Color>("color");
|
||||||
|
var drawOrder = reader.GetOptionalAttributeEnum<DrawOrder>("draworder", s => s switch
|
||||||
|
{
|
||||||
|
"topdown" => DrawOrder.TopDown,
|
||||||
|
"index" => DrawOrder.Index,
|
||||||
|
_ => throw new Exception($"Unknown draw order '{s}'")
|
||||||
|
}) ?? DrawOrder.TopDown;
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
Dictionary<string, IProperty>? properties = null;
|
||||||
|
List<Object> objects = [];
|
||||||
|
|
||||||
|
reader.ProcessChildren("objectgroup", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"),
|
||||||
|
"object" => () => objects.Add(ReadObject(r)),
|
||||||
|
_ => r.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
return new ObjectLayer
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
Color = color,
|
||||||
|
Properties = properties,
|
||||||
|
DrawOrder = drawOrder,
|
||||||
|
Objects = objects
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object ReadObject(XmlReader reader)
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var id = reader.GetRequiredAttributeParseable<uint>("id");
|
||||||
|
var name = reader.GetOptionalAttribute("name") ?? "";
|
||||||
|
var type = reader.GetOptionalAttribute("type") ?? "";
|
||||||
|
var x = reader.GetOptionalAttributeParseable<float>("x") ?? 0f;
|
||||||
|
var y = reader.GetOptionalAttributeParseable<float>("y") ?? 0f;
|
||||||
|
var width = reader.GetOptionalAttributeParseable<float>("width") ?? 0f;
|
||||||
|
var height = reader.GetOptionalAttributeParseable<float>("height") ?? 0f;
|
||||||
|
var rotation = reader.GetOptionalAttributeParseable<float>("rotation") ?? 0f;
|
||||||
|
var gid = reader.GetOptionalAttributeParseable<uint>("gid");
|
||||||
|
var visible = reader.GetOptionalAttributeParseable<bool>("visible") ?? true;
|
||||||
|
var template = reader.GetOptionalAttribute("template");
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
Object? obj = null;
|
||||||
|
Dictionary<string, IProperty>? properties = null;
|
||||||
|
|
||||||
|
reader.ProcessChildren("object", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"),
|
||||||
|
"ellipse" => () => Helpers.SetAtMostOnce(ref obj, ReadEllipseObject(r, id), "Object marker"),
|
||||||
|
"point" => () => Helpers.SetAtMostOnce(ref obj, ReadPointObject(r, id), "Object marker"),
|
||||||
|
"polygon" => () => Helpers.SetAtMostOnce(ref obj, ReadPolygonObject(r, id), "Object marker"),
|
||||||
|
"polyline" => () => Helpers.SetAtMostOnce(ref obj, ReadPolylineObject(r, id), "Object marker"),
|
||||||
|
"text" => () => Helpers.SetAtMostOnce(ref obj, ReadTextObject(r, id), "Object marker"),
|
||||||
|
_ => throw new Exception($"Unknown object marker '{elementName}'")
|
||||||
|
});
|
||||||
|
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
obj = new RectangleObject { ID = id };
|
||||||
|
reader.Skip();
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.Name = name;
|
||||||
|
obj.Type = type;
|
||||||
|
obj.X = x;
|
||||||
|
obj.Y = y;
|
||||||
|
obj.Width = width;
|
||||||
|
obj.Height = height;
|
||||||
|
obj.Rotation = rotation;
|
||||||
|
obj.GID = gid;
|
||||||
|
obj.Visible = visible;
|
||||||
|
obj.Template = template;
|
||||||
|
obj.Properties = properties;
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EllipseObject ReadEllipseObject(XmlReader reader, uint id)
|
||||||
|
{
|
||||||
|
reader.Skip();
|
||||||
|
return new EllipseObject { ID = id };
|
||||||
|
}
|
||||||
|
|
||||||
|
private PointObject ReadPointObject(XmlReader reader, uint id)
|
||||||
|
{
|
||||||
|
reader.Skip();
|
||||||
|
return new PointObject { ID = id };
|
||||||
|
}
|
||||||
|
|
||||||
|
private PolygonObject ReadPolygonObject(XmlReader reader, uint id)
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var points = reader.GetRequiredAttributeParseable<List<Vector2>>("points", s =>
|
||||||
|
{
|
||||||
|
// Takes on format "x1,y1 x2,y2 x3,y3 ..."
|
||||||
|
var coords = s.Split(' ');
|
||||||
|
return coords.Select(c =>
|
||||||
|
{
|
||||||
|
var xy = c.Split(',');
|
||||||
|
return new Vector2(float.Parse(xy[0], CultureInfo.InvariantCulture), float.Parse(xy[1], CultureInfo.InvariantCulture));
|
||||||
|
}).ToList();
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.ReadStartElement("polygon");
|
||||||
|
return new PolygonObject { ID = id, Points = points };
|
||||||
|
}
|
||||||
|
|
||||||
|
private PolylineObject ReadPolylineObject(XmlReader reader, uint id)
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var points = reader.GetRequiredAttributeParseable<List<Vector2>>("points", s =>
|
||||||
|
{
|
||||||
|
// Takes on format "x1,y1 x2,y2 x3,y3 ..."
|
||||||
|
var coords = s.Split(' ');
|
||||||
|
return coords.Select(c =>
|
||||||
|
{
|
||||||
|
var xy = c.Split(',');
|
||||||
|
return new Vector2(float.Parse(xy[0], CultureInfo.InvariantCulture), float.Parse(xy[1], CultureInfo.InvariantCulture));
|
||||||
|
}).ToList();
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.ReadStartElement("polyline");
|
||||||
|
return new PolylineObject { ID = id, Points = points };
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextObject ReadTextObject(XmlReader reader, uint id)
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var fontFamily = reader.GetOptionalAttribute("fontfamily") ?? "sans-serif";
|
||||||
|
var pixelSize = reader.GetOptionalAttributeParseable<int>("pixelsize") ?? 16;
|
||||||
|
var wrap = reader.GetOptionalAttributeParseable<bool>("wrap") ?? false;
|
||||||
|
var color = reader.GetOptionalAttributeClass<Color>("color") ?? Color.Parse("#000000", CultureInfo.InvariantCulture);
|
||||||
|
var bold = reader.GetOptionalAttributeParseable<bool>("bold") ?? false;
|
||||||
|
var italic = reader.GetOptionalAttributeParseable<bool>("italic") ?? false;
|
||||||
|
var underline = reader.GetOptionalAttributeParseable<bool>("underline") ?? false;
|
||||||
|
var strikeout = reader.GetOptionalAttributeParseable<bool>("strikeout") ?? false;
|
||||||
|
var kerning = reader.GetOptionalAttributeParseable<bool>("kerning") ?? true;
|
||||||
|
var hAlign = reader.GetOptionalAttributeEnum<TextHorizontalAlignment>("halign", s => s switch
|
||||||
|
{
|
||||||
|
"left" => TextHorizontalAlignment.Left,
|
||||||
|
"center" => TextHorizontalAlignment.Center,
|
||||||
|
"right" => TextHorizontalAlignment.Right,
|
||||||
|
"justify" => TextHorizontalAlignment.Justify,
|
||||||
|
_ => throw new Exception($"Unknown horizontal alignment '{s}'")
|
||||||
|
}) ?? TextHorizontalAlignment.Left;
|
||||||
|
var vAlign = reader.GetOptionalAttributeEnum<TextVerticalAlignment>("valign", s => s switch
|
||||||
|
{
|
||||||
|
"top" => TextVerticalAlignment.Top,
|
||||||
|
"center" => TextVerticalAlignment.Center,
|
||||||
|
"bottom" => TextVerticalAlignment.Bottom,
|
||||||
|
_ => throw new Exception($"Unknown vertical alignment '{s}'")
|
||||||
|
}) ?? TextVerticalAlignment.Top;
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
var text = reader.ReadElementContentAsString("text", "");
|
||||||
|
|
||||||
|
return new TextObject
|
||||||
|
{
|
||||||
|
ID = id,
|
||||||
|
FontFamily = fontFamily,
|
||||||
|
PixelSize = pixelSize,
|
||||||
|
Wrap = wrap,
|
||||||
|
Color = color,
|
||||||
|
Bold = bold,
|
||||||
|
Italic = italic,
|
||||||
|
Underline = underline,
|
||||||
|
Strikeout = strikeout,
|
||||||
|
Kerning = kerning,
|
||||||
|
HorizontalAlignment = hAlign,
|
||||||
|
VerticalAlignment = vAlign,
|
||||||
|
Text = text
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
54
DotTiled/TmxSerializer/TmxSerializer.Properties.cs
Normal file
54
DotTiled/TmxSerializer/TmxSerializer.Properties.cs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public partial class TmxSerializer
|
||||||
|
{
|
||||||
|
private Dictionary<string, IProperty> ReadProperties(XmlReader reader)
|
||||||
|
{
|
||||||
|
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),
|
||||||
|
_ => throw new XmlException("Invalid property type")
|
||||||
|
};
|
||||||
|
return (name, property);
|
||||||
|
}).ToDictionary(x => x.name, x => x.property);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClassProperty ReadClassProperty(XmlReader reader)
|
||||||
|
{
|
||||||
|
var name = reader.GetRequiredAttribute("name");
|
||||||
|
var propertyType = reader.GetRequiredAttribute("propertytype");
|
||||||
|
|
||||||
|
reader.ReadStartElement("property");
|
||||||
|
var properties = ReadProperties(reader);
|
||||||
|
reader.ReadEndElement();
|
||||||
|
|
||||||
|
return new ClassProperty { Name = name, PropertyType = propertyType, Properties = properties };
|
||||||
|
}
|
||||||
|
}
|
105
DotTiled/TmxSerializer/TmxSerializer.TileLayer.cs
Normal file
105
DotTiled/TmxSerializer/TmxSerializer.TileLayer.cs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public partial class TmxSerializer
|
||||||
|
{
|
||||||
|
private TileLayer ReadTileLayer(XmlReader reader, 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;
|
||||||
|
|
||||||
|
Dictionary<string, IProperty>? properties = null;
|
||||||
|
Data? data = null;
|
||||||
|
|
||||||
|
reader.ProcessChildren("layer", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"data" => () => Helpers.SetAtMostOnce(ref data, ReadData(r, dataUsesChunks), "Data"),
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImageLayer ReadImageLayer(XmlReader reader)
|
||||||
|
{
|
||||||
|
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.GetRequiredAttributeParseable<bool>("repeatx");
|
||||||
|
var repeatY = reader.GetRequiredAttributeParseable<bool>("repeaty");
|
||||||
|
|
||||||
|
Dictionary<string, IProperty>? properties = null;
|
||||||
|
Image? image = null;
|
||||||
|
|
||||||
|
reader.ProcessChildren("imagelayer", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"image" => () => Helpers.SetAtMostOnce(ref image, ReadImage(r), "Image"),
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
313
DotTiled/TmxSerializer/TmxSerializer.Tileset.cs
Normal file
313
DotTiled/TmxSerializer/TmxSerializer.Tileset.cs
Normal file
|
@ -0,0 +1,313 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public partial class TmxSerializer
|
||||||
|
{
|
||||||
|
private Tileset ReadTileset(XmlReader reader)
|
||||||
|
{
|
||||||
|
// 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");
|
||||||
|
var margin = reader.GetOptionalAttributeParseable<uint>("margin");
|
||||||
|
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 Exception($"Unknown object alignment '{s}'")
|
||||||
|
}) ?? ObjectAlignment.Unspecified;
|
||||||
|
var renderSize = reader.GetOptionalAttributeEnum<TileRenderSize>("rendersize", s => s switch
|
||||||
|
{
|
||||||
|
"tile" => TileRenderSize.Tile,
|
||||||
|
"grid" => TileRenderSize.Grid,
|
||||||
|
_ => throw new Exception($"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 Exception($"Unknown fill mode '{s}'")
|
||||||
|
}) ?? FillMode.Stretch;
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
Image? image = null;
|
||||||
|
TileOffset? tileOffset = null;
|
||||||
|
Grid? grid = null;
|
||||||
|
Dictionary<string, IProperty>? properties = null;
|
||||||
|
List<Wangset>? wangsets = null;
|
||||||
|
Transformations? transformations = null;
|
||||||
|
List<Tile> tiles = [];
|
||||||
|
|
||||||
|
reader.ProcessChildren("tileset", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"image" => () => Helpers.SetAtMostOnce(ref image, ReadImage(r), "Image"),
|
||||||
|
"tileoffset" => () => Helpers.SetAtMostOnce(ref tileOffset, ReadTileOffset(r), "TileOffset"),
|
||||||
|
"grid" => () => Helpers.SetAtMostOnce(ref grid, ReadGrid(r), "Grid"),
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"),
|
||||||
|
"wangsets" => () => Helpers.SetAtMostOnce(ref wangsets, ReadWangsets(r), "Wangsets"),
|
||||||
|
"transformations" => () => Helpers.SetAtMostOnce(ref transformations, ReadTransformations(r), "Transformations"),
|
||||||
|
"tile" => () => tiles.Add(ReadTile(r)),
|
||||||
|
_ => r.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if tileset is referring to external file
|
||||||
|
if (source is not null)
|
||||||
|
{
|
||||||
|
var resolvedTileset = _externalTilesetResolver(source);
|
||||||
|
resolvedTileset.FirstGID = firstGID;
|
||||||
|
resolvedTileset.Source = null;
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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 Exception($"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
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Image
|
||||||
|
{
|
||||||
|
Format = format,
|
||||||
|
Source = source,
|
||||||
|
TransparentColor = transparentColor,
|
||||||
|
Width = width,
|
||||||
|
Height = height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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 };
|
||||||
|
}
|
||||||
|
|
||||||
|
private Grid ReadGrid(XmlReader reader)
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var orientation = reader.GetOptionalAttributeEnum<GridOrientation>("orientation", s => s switch
|
||||||
|
{
|
||||||
|
"orthogonal" => GridOrientation.Orthogonal,
|
||||||
|
"isometric" => GridOrientation.Isometric,
|
||||||
|
_ => throw new Exception($"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 };
|
||||||
|
}
|
||||||
|
|
||||||
|
private Transformations ReadTransformations(XmlReader reader)
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var hFlip = reader.GetOptionalAttributeParseable<bool>("hflip") ?? false;
|
||||||
|
var vFlip = reader.GetOptionalAttributeParseable<bool>("vflip") ?? false;
|
||||||
|
var rotate = reader.GetOptionalAttributeParseable<bool>("rotate") ?? false;
|
||||||
|
var preferUntransformed = reader.GetOptionalAttributeParseable<bool>("preferuntransformed") ?? false;
|
||||||
|
|
||||||
|
reader.ReadStartElement("transformations");
|
||||||
|
return new Transformations { HFlip = hFlip, VFlip = vFlip, Rotate = rotate, PreferUntransformed = preferUntransformed };
|
||||||
|
}
|
||||||
|
|
||||||
|
private Tile ReadTile(XmlReader reader)
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var id = reader.GetRequiredAttributeParseable<uint>("id");
|
||||||
|
var type = reader.GetOptionalAttribute("type") ?? "";
|
||||||
|
var probability = reader.GetOptionalAttributeParseable<float>("probability") ?? 0f;
|
||||||
|
var x = reader.GetOptionalAttributeParseable<uint>("x") ?? 0;
|
||||||
|
var y = reader.GetOptionalAttributeParseable<uint>("y") ?? 0;
|
||||||
|
var width = reader.GetOptionalAttributeParseable<uint>("width");
|
||||||
|
var height = reader.GetOptionalAttributeParseable<uint>("height");
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
Dictionary<string, IProperty>? properties = null;
|
||||||
|
Image? image = null;
|
||||||
|
ObjectLayer? objectLayer = null;
|
||||||
|
List<Frame>? animation = null;
|
||||||
|
|
||||||
|
reader.ProcessChildren("tile", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"),
|
||||||
|
"image" => () => Helpers.SetAtMostOnce(ref image, ReadImage(r), "Image"),
|
||||||
|
"objectgroup" => () => Helpers.SetAtMostOnce(ref objectLayer, ReadObjectLayer(r), "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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Wangset> ReadWangsets(XmlReader reader)
|
||||||
|
{
|
||||||
|
return reader.ReadList<Wangset>("wangsets", "wangset", ReadWangset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Wangset ReadWangset(XmlReader reader)
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var name = reader.GetRequiredAttribute("name");
|
||||||
|
var @class = reader.GetOptionalAttribute("class") ?? "";
|
||||||
|
var tile = reader.GetRequiredAttributeParseable<uint>("tile");
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
Dictionary<string, IProperty>? properties = null;
|
||||||
|
List<WangColor> wangColors = [];
|
||||||
|
List<WangTile> wangTiles = [];
|
||||||
|
|
||||||
|
reader.ProcessChildren("wangset", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"),
|
||||||
|
"wangcolor" => () => wangColors.Add(ReadWangColor(r)),
|
||||||
|
"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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private WangColor ReadWangColor(XmlReader reader)
|
||||||
|
{
|
||||||
|
// Attributes
|
||||||
|
var name = reader.GetRequiredAttribute("name");
|
||||||
|
var @class = reader.GetOptionalAttribute("class") ?? "";
|
||||||
|
var color = reader.GetRequiredAttributeParseable<Color>("color");
|
||||||
|
var tile = reader.GetRequiredAttributeParseable<uint>("tile");
|
||||||
|
var probability = reader.GetOptionalAttributeParseable<float>("probability") ?? 0f;
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
Dictionary<string, IProperty>? properties = null;
|
||||||
|
|
||||||
|
reader.ProcessChildren("wangcolor", (r, elementName) => elementName switch
|
||||||
|
{
|
||||||
|
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"),
|
||||||
|
_ => r.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
return new WangColor
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Class = @class,
|
||||||
|
Color = color,
|
||||||
|
Tile = tile,
|
||||||
|
Probability = probability,
|
||||||
|
Properties = properties
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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)).ToArray();
|
||||||
|
if (indices.Length > 8)
|
||||||
|
throw new ArgumentException("Wang ID can have at most 8 indices.");
|
||||||
|
return indices;
|
||||||
|
});
|
||||||
|
|
||||||
|
return new WangTile
|
||||||
|
{
|
||||||
|
TileID = tileID,
|
||||||
|
WangID = wangID
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
20
DotTiled/TmxSerializer/TmxSerializer.cs
Normal file
20
DotTiled/TmxSerializer/TmxSerializer.cs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
using System;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public partial class TmxSerializer
|
||||||
|
{
|
||||||
|
private readonly Func<string, Tileset> _externalTilesetResolver;
|
||||||
|
|
||||||
|
public TmxSerializer(Func<string, Tileset> externalTilesetResolver)
|
||||||
|
{
|
||||||
|
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map DeserializeMap(XmlReader reader)
|
||||||
|
{
|
||||||
|
reader.ReadToFollowing("map");
|
||||||
|
return ReadMap(reader);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,109 +0,0 @@
|
||||||
using System.Globalization;
|
|
||||||
using System.Xml;
|
|
||||||
using System.Xml.Serialization;
|
|
||||||
|
|
||||||
namespace DotTiled;
|
|
||||||
|
|
||||||
internal static class ExtensionsXmlReader
|
|
||||||
{
|
|
||||||
internal static string GetRequiredAttribute(this XmlReader reader, string attribute)
|
|
||||||
{
|
|
||||||
return reader.GetAttribute(attribute) ?? throw new XmlException($"{attribute} attribute is required"); ;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static T GetRequiredAttribute<T>(this XmlReader reader, string attribute) where T : IParsable<T>
|
|
||||||
{
|
|
||||||
var value = reader.GetAttribute(attribute) ?? throw new XmlException($"{attribute} attribute is required");
|
|
||||||
return T.Parse(value, CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static T GetRequiredAttributeEnum<T>(this XmlReader reader, string attribute) where T : Enum
|
|
||||||
{
|
|
||||||
var value = reader.GetAttribute(attribute) ?? throw new XmlException($"{attribute} attribute is required");
|
|
||||||
return ParseEnumUsingXmlEnumAttribute<T>(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static string? GetOptionalAttribute(this XmlReader reader, string attribute, string? defaultValue = default)
|
|
||||||
{
|
|
||||||
return reader.GetAttribute(attribute) ?? defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static T? GetOptionalAttribute<T>(this XmlReader reader, string attribute) where T : struct, IParsable<T>
|
|
||||||
{
|
|
||||||
var value = reader.GetAttribute(attribute);
|
|
||||||
if (value is null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return T.Parse(value, CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static T? GetOptionalAttributeClass<T>(this XmlReader reader, string attribute) where T : class, IParsable<T>
|
|
||||||
{
|
|
||||||
var value = reader.GetAttribute(attribute);
|
|
||||||
if (value is null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return T.Parse(value, CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static T? GetOptionalAttributeEnum<T>(this XmlReader reader, string attribute) where T : struct, Enum
|
|
||||||
{
|
|
||||||
var value = reader.GetAttribute(attribute);
|
|
||||||
return value != null ? ParseEnumUsingXmlEnumAttribute<T>(value) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static T ParseEnumUsingXmlEnumAttribute<T>(string value) where T : Enum
|
|
||||||
{
|
|
||||||
var enumType = typeof(T);
|
|
||||||
var enumValues = Enum.GetValues(enumType);
|
|
||||||
foreach (var enumValue in enumValues)
|
|
||||||
{
|
|
||||||
var enumMember = enumType.GetMember(enumValue.ToString()!)[0];
|
|
||||||
var xmlEnumAttribute = enumMember.GetCustomAttributes(typeof(XmlEnumAttribute), false).FirstOrDefault() as XmlEnumAttribute;
|
|
||||||
if (xmlEnumAttribute?.Name == value)
|
|
||||||
return (T)enumValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new XmlException($"Failed to parse enum value {value}");
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static List<T> ReadList<T>(this XmlReader reader, string wrapper, string elementName, Func<XmlReader, T> readElement)
|
|
||||||
{
|
|
||||||
var list = new List<T>();
|
|
||||||
|
|
||||||
if (reader.IsEmptyElement)
|
|
||||||
return list;
|
|
||||||
|
|
||||||
reader.ReadStartElement(wrapper);
|
|
||||||
while (reader.IsStartElement(elementName))
|
|
||||||
{
|
|
||||||
list.Add(readElement(reader));
|
|
||||||
|
|
||||||
if (reader.NodeType == XmlNodeType.EndElement)
|
|
||||||
continue; // At end of list, no need to read again
|
|
||||||
|
|
||||||
reader.Read();
|
|
||||||
}
|
|
||||||
reader.ReadEndElement();
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static T ReadElementAs<T>(this XmlReader reader) where T : IXmlSerializable
|
|
||||||
{
|
|
||||||
var serializer = new XmlSerializer(typeof(T));
|
|
||||||
return (T)serializer.Deserialize(reader)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int CountDirectChildrenWithName(this XmlReader reader, string name)
|
|
||||||
{
|
|
||||||
var subTree = reader.ReadSubtree();
|
|
||||||
int count = 0;
|
|
||||||
while (subTree.Read())
|
|
||||||
{
|
|
||||||
if (subTree.NodeType == XmlNodeType.Element && subTree.Name == name)
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
using System.Xml;
|
|
||||||
using System.Xml.Serialization;
|
|
||||||
|
|
||||||
namespace DotTiled;
|
|
||||||
|
|
||||||
public static class XmlHelpers
|
|
||||||
{
|
|
||||||
public static Dictionary<string, IProperty> ReadProperties(XmlReader reader)
|
|
||||||
{
|
|
||||||
return reader.ReadList<(string PropName, IProperty Prop)>("properties", "property",
|
|
||||||
reader =>
|
|
||||||
{
|
|
||||||
var type = reader.GetRequiredAttributeEnum<PropertyType>("type");
|
|
||||||
var propertyRuntimeType = type switch
|
|
||||||
{
|
|
||||||
PropertyType.String => typeof(StringProperty),
|
|
||||||
PropertyType.Int => typeof(IntProperty),
|
|
||||||
PropertyType.Float => typeof(FloatProperty),
|
|
||||||
PropertyType.Bool => typeof(BooleanProperty),
|
|
||||||
PropertyType.Color => typeof(ColorProperty),
|
|
||||||
PropertyType.File => typeof(FileProperty),
|
|
||||||
PropertyType.Object => typeof(ObjectProperty),
|
|
||||||
PropertyType.Class => typeof(ClassProperty),
|
|
||||||
_ => throw new XmlException("Invalid property type")
|
|
||||||
};
|
|
||||||
|
|
||||||
var serializer = new XmlSerializer(propertyRuntimeType);
|
|
||||||
var deserializedProperty = (IProperty)serializer.Deserialize(reader)!;
|
|
||||||
return (deserializedProperty.Name, deserializedProperty);
|
|
||||||
}
|
|
||||||
).ToDictionary(x => x.PropName, x => x.Prop);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BaseTileset ReadTileset(XmlReader reader, Func<string, BaseTileset> tilesetResolver)
|
|
||||||
{
|
|
||||||
var imageChildren = reader.CountDirectChildrenWithName("image");
|
|
||||||
var tileChildren = reader.CountDirectChildrenWithName("tile");
|
|
||||||
if (imageChildren == 0 && tileChildren == 0)
|
|
||||||
{
|
|
||||||
// This is a tileset that must have "source" set
|
|
||||||
var source = reader.GetRequiredAttribute("source");
|
|
||||||
return tilesetResolver(source);
|
|
||||||
}
|
|
||||||
if (imageChildren == 1)
|
|
||||||
{
|
|
||||||
// This is a single image tileset
|
|
||||||
return reader.ReadElementAs<ImageTileset>();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new XmlException("Invalid tileset");
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue