mirror of
https://github.com/dcronqvist/DotTiled.git
synced 2025-02-05 08:52:50 +02:00
First initial modelling, going to move serialization away from model
This commit is contained in:
parent
f0548060c0
commit
d2be83972d
10 changed files with 1302 additions and 0 deletions
8
.editorconfig
Normal file
8
.editorconfig
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*.cs]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -9,6 +9,7 @@
|
||||||
*.user
|
*.user
|
||||||
*.userosscache
|
*.userosscache
|
||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
|
.vscode/
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
*.userprefs
|
*.userprefs
|
||||||
|
|
27
DotTiled.Tests/DotTiled.Tests.csproj
Normal file
27
DotTiled.Tests/DotTiled.Tests.csproj
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||||
|
<PackageReference Include="xunit" Version="2.5.3" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\DotTiled\DotTiled.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Xunit" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
259
DotTiled.Tests/MapTests.cs
Normal file
259
DotTiled.Tests/MapTests.cs
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
28
DotTiled.sln
Normal file
28
DotTiled.sln
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.0.31903.59
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled", "DotTiled\DotTiled.csproj", "{80A60DE7-D6AE-4CC7-825F-75308D83F36D}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Tests", "DotTiled.Tests\DotTiled.Tests.csproj", "{C1311A5A-5206-467C-B323-B131CA11FDB8}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{80A60DE7-D6AE-4CC7-825F-75308D83F36D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{80A60DE7-D6AE-4CC7-825F-75308D83F36D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{80A60DE7-D6AE-4CC7-825F-75308D83F36D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{80A60DE7-D6AE-4CC7-825F-75308D83F36D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C1311A5A-5206-467C-B323-B131CA11FDB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C1311A5A-5206-467C-B323-B131CA11FDB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C1311A5A-5206-467C-B323-B131CA11FDB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C1311A5A-5206-467C-B323-B131CA11FDB8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
9
DotTiled/DotTiled.csproj
Normal file
9
DotTiled/DotTiled.csproj
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
809
DotTiled/Map.cs
Normal file
809
DotTiled/Map.cs
Normal file
|
@ -0,0 +1,809 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
109
DotTiled/XML/ExtensionsXmlReader.cs
Normal file
109
DotTiled/XML/ExtensionsXmlReader.cs
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
52
DotTiled/XML/XmlHelpers.cs
Normal file
52
DotTiled/XML/XmlHelpers.cs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
0
Makefile
Normal file
0
Makefile
Normal file
Loading…
Add table
Reference in a new issue