Model now uses Optional correctly, I think, massive changes

This commit is contained in:
Daniel Cronqvist 2024-09-01 20:44:36 +02:00
parent 39d2838663
commit 88a5eadb74
42 changed files with 1106 additions and 400 deletions

View file

@ -19,12 +19,11 @@ public static partial class DotTiledAssert
AssertEqual(expected.GlobalTileIDs, actual.GlobalTileIDs, nameof(Data.GlobalTileIDs));
AssertEqual(expected.FlippingFlags, actual.FlippingFlags, nameof(Data.FlippingFlags));
if (expected.Chunks is not null)
if (expected.Chunks.HasValue)
{
Assert.NotNull(actual.Chunks);
AssertEqual(expected.Chunks.Length, actual.Chunks.Length, "Chunks.Length");
for (var i = 0; i < expected.Chunks.Length; i++)
AssertChunk(expected.Chunks[i], actual.Chunks[i]);
AssertEqual(expected.Chunks.Value.Length, actual.Chunks.Value.Length, "Chunks.Length");
for (var i = 0; i < expected.Chunks.Value.Length; i++)
AssertChunk(expected.Chunks.Value[i], actual.Chunks.Value[i]);
}
}

View file

@ -5,6 +5,68 @@ namespace DotTiled.Tests;
public static partial class DotTiledAssert
{
private static void AssertListOrdered<T>(IList<T> expected, IList<T> actual, string nameof, Action<T, T> assertEqual = null)
{
if (expected is null)
{
Assert.Null(actual);
return;
}
Assert.NotNull(actual);
AssertEqual(expected.Count, actual.Count, $"{nameof}.Count");
for (var i = 0; i < expected.Count; i++)
{
if (assertEqual is not null)
{
assertEqual(expected[i], actual[i]);
continue;
}
AssertEqual(expected[i], actual[i], $"{nameof}[{i}]");
}
}
private static void AssertOptionalsEqual<T>(
Optional<T> expected,
Optional<T> actual,
string nameof,
Action<T, T> assertEqual)
{
if (expected is null)
{
Assert.Null(actual);
return;
}
if (expected.HasValue)
{
Assert.True(actual.HasValue, $"Expected {nameof} to have a value");
assertEqual(expected.Value, actual.Value);
return;
}
Assert.False(actual.HasValue, $"Expected {nameof} to not have a value");
}
private static void AssertEqual<T>(Optional<T> expected, Optional<T> actual, string nameof)
{
if (expected is null)
{
Assert.Null(actual);
return;
}
if (expected.HasValue)
{
Assert.True(actual.HasValue, $"Expected {nameof} to have a value");
AssertEqual(expected.Value, actual.Value, nameof);
return;
}
Assert.False(actual.HasValue, $"Expected {nameof} to not have a value");
}
private static void AssertEqual<T>(T expected, T actual, string nameof)
{
if (expected == null)

View file

@ -4,7 +4,6 @@ public static partial class DotTiledAssert
{
internal static void AssertTileset(Tileset expected, Tileset actual)
{
// Attributes
AssertEqual(expected.Version, actual.Version, nameof(Tileset.Version));
AssertEqual(expected.TiledVersion, actual.TiledVersion, nameof(Tileset.TiledVersion));
AssertEqual(expected.FirstGID, actual.FirstGID, nameof(Tileset.FirstGID));
@ -21,29 +20,16 @@ public static partial class DotTiledAssert
AssertEqual(expected.RenderSize, actual.RenderSize, nameof(Tileset.RenderSize));
AssertEqual(expected.FillMode, actual.FillMode, nameof(Tileset.FillMode));
// At most one of
AssertImage(expected.Image, actual.Image);
AssertTileOffset(expected.TileOffset, actual.TileOffset);
AssertGrid(expected.Grid, actual.Grid);
AssertOptionalsEqual(expected.Image, actual.Image, nameof(Tileset.Image), AssertImage);
AssertOptionalsEqual(expected.TileOffset, actual.TileOffset, nameof(Tileset.TileOffset), AssertTileOffset);
AssertOptionalsEqual(expected.Grid, actual.Grid, nameof(Tileset.Grid), AssertGrid);
AssertProperties(expected.Properties, actual.Properties);
// TODO: AssertTerrainTypes(actual.TerrainTypes, expected.TerrainTypes);
if (expected.Wangsets is not null)
{
Assert.NotNull(actual.Wangsets);
AssertEqual(expected.Wangsets.Count, actual.Wangsets.Count, "Wangsets.Count");
for (var i = 0; i < expected.Wangsets.Count; i++)
AssertWangset(expected.Wangsets[i], actual.Wangsets[i]);
}
AssertTransformations(expected.Transformations, actual.Transformations);
// Any number of
Assert.NotNull(actual.Tiles);
AssertEqual(expected.Tiles.Count, actual.Tiles.Count, "Tiles.Count");
for (var i = 0; i < expected.Tiles.Count; i++)
AssertTile(expected.Tiles[i], actual.Tiles[i]);
AssertListOrdered(expected.Wangsets, actual.Wangsets, nameof(Tileset.Wangsets), AssertWangset);
AssertOptionalsEqual(expected.Transformations, actual.Transformations, nameof(Tileset.Transformations), AssertTransformations);
AssertListOrdered(expected.Tiles, actual.Tiles, nameof(Tileset.Tiles), AssertTile);
}
private static void AssertTileOffset(TileOffset? expected, TileOffset? actual)
private static void AssertTileOffset(TileOffset expected, TileOffset actual)
{
if (expected is null)
{
@ -74,27 +60,17 @@ public static partial class DotTiledAssert
private static void AssertWangset(Wangset expected, Wangset actual)
{
// Attributes
AssertEqual(expected.Name, actual.Name, nameof(Wangset.Name));
AssertEqual(expected.Class, actual.Class, nameof(Wangset.Class));
AssertEqual(expected.Tile, actual.Tile, nameof(Wangset.Tile));
// At most one of
AssertProperties(expected.Properties, actual.Properties);
if (expected.WangColors is not null)
{
Assert.NotNull(actual.WangColors);
AssertEqual(expected.WangColors.Count, actual.WangColors.Count, "WangColors.Count");
for (var i = 0; i < expected.WangColors.Count; i++)
AssertWangColor(expected.WangColors[i], actual.WangColors[i]);
}
for (var i = 0; i < expected.WangTiles.Count; i++)
AssertWangTile(expected.WangTiles[i], actual.WangTiles[i]);
AssertListOrdered(expected.WangColors, actual.WangColors, nameof(Wangset.WangColors), AssertWangColor);
AssertListOrdered(expected.WangTiles, actual.WangTiles, nameof(Wangset.WangTiles), AssertWangTile);
}
private static void AssertWangColor(WangColor expected, WangColor actual)
{
// Attributes
AssertEqual(expected.Name, actual.Name, nameof(WangColor.Name));
AssertEqual(expected.Class, actual.Class, nameof(WangColor.Class));
AssertEqual(expected.Color, actual.Color, nameof(WangColor.Color));
@ -106,7 +82,6 @@ public static partial class DotTiledAssert
private static void AssertWangTile(WangTile expected, WangTile actual)
{
// Attributes
AssertEqual(expected.TileID, actual.TileID, nameof(WangTile.TileID));
AssertEqual(expected.WangID, actual.WangID, nameof(WangTile.WangID));
}
@ -119,7 +94,6 @@ public static partial class DotTiledAssert
return;
}
// Attributes
Assert.NotNull(actual);
AssertEqual(expected.HFlip, actual.HFlip, nameof(Transformations.HFlip));
AssertEqual(expected.VFlip, actual.VFlip, nameof(Transformations.VFlip));
@ -129,7 +103,6 @@ public static partial class DotTiledAssert
private static void AssertTile(Tile expected, Tile actual)
{
// Attributes
AssertEqual(expected.ID, actual.ID, nameof(Tile.ID));
AssertEqual(expected.Type, actual.Type, nameof(Tile.Type));
AssertEqual(expected.Probability, actual.Probability, nameof(Tile.Probability));
@ -138,22 +111,14 @@ public static partial class DotTiledAssert
AssertEqual(expected.Width, actual.Width, nameof(Tile.Width));
AssertEqual(expected.Height, actual.Height, nameof(Tile.Height));
// Elements
AssertProperties(expected.Properties, actual.Properties);
AssertImage(expected.Image, actual.Image);
AssertLayer((BaseLayer?)expected.ObjectLayer, (BaseLayer?)actual.ObjectLayer);
if (expected.Animation is not null)
{
Assert.NotNull(actual.Animation);
AssertEqual(expected.Animation.Count, actual.Animation.Count, "Animation.Count");
for (var i = 0; i < expected.Animation.Count; i++)
AssertFrame(expected.Animation[i], actual.Animation[i]);
}
AssertOptionalsEqual(expected.Image, actual.Image, nameof(Tile.Image), AssertImage);
AssertOptionalsEqual(expected.ObjectLayer, actual.ObjectLayer, nameof(Tile.ObjectLayer), (a, b) => AssertLayer((BaseLayer)a, (BaseLayer)b));
AssertListOrdered(expected.Animation, actual.Animation, nameof(Tile.Animation), AssertFrame);
}
private static void AssertFrame(Frame expected, Frame actual)
{
// Attributes
AssertEqual(expected.TileID, actual.TileID, nameof(Frame.TileID));
AssertEqual(expected.Duration, actual.Duration, nameof(Frame.Duration));
}

View file

@ -3,7 +3,6 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>

View file

@ -0,0 +1,520 @@
namespace DotTiled.Tests;
// public class OptionalTests
// {
// [Fact]
// public void HasValue_WhenValueIsSet_ReturnsTrue()
// {
// // Arrange
// var optional = new Optional<int>(42);
// // Act & Assert
// Assert.True(optional.HasValue);
// }
// [Fact]
// public void HasValue_WhenValueIsNotSet_ReturnsFalse()
// {
// // Arrange
// var optional = new Optional<int>();
// // Act & Assert
// Assert.False(optional.HasValue);
// }
// [Fact]
// public void Value_WhenValueIsSet_ReturnsValue()
// {
// // Arrange
// var optional = new Optional<int>(42);
// // Act
// var value = optional.Value;
// // Assert
// Assert.Equal(42, value);
// }
// [Fact]
// public void Value_WhenValueIsNotSet_ThrowsInvalidOperationException()
// {
// // Arrange
// var optional = new Optional<int>();
// // Act & Assert
// _ = Assert.Throws<InvalidOperationException>(() => optional.Value);
// }
// [Fact]
// public void ImplicitConversionFromValue_CreatesOptionalWithValue()
// {
// // Arrange
// Optional<int> optional = 42;
// // Act & Assert
// Assert.True(optional.HasValue);
// Assert.Equal(42, optional.Value);
// }
// [Fact]
// public void ImplicitConversionToValue_ReturnsValue()
// {
// // Arrange
// var optional = new Optional<int>(42);
// // Act
// int value = optional;
// // Assert
// Assert.Equal(42, value);
// }
// [Fact]
// public void ToString_WhenValueIsSet_ReturnsValueToString()
// {
// // Arrange
// var optional = new Optional<int>(42);
// // Act
// var result = optional.ToString();
// // Assert
// Assert.Equal("42", result);
// }
// [Fact]
// public void ToString_WhenValueIsNotSet_ReturnsEmpty()
// {
// // Arrange
// var optional = new Optional<int>();
// // Act
// var result = optional.ToString();
// // Assert
// Assert.Equal("Empty", result);
// }
// [Fact]
// public void Equals_WithObject_ReturnsTrueWhenValueIsEqual()
// {
// // Arrange
// var optional = new Optional<int>(42);
// // Act
// var result = optional.Equals(42);
// // Assert
// Assert.True(result);
// }
// [Fact]
// public void Equals_WithObject_ReturnsFalseWhenValueIsNotEqual()
// {
// // Arrange
// var optional = new Optional<int>(42);
// // Act
// var result = optional.Equals(43);
// // Assert
// Assert.False(result);
// }
// [Fact]
// public void Equals_WithObject_ReturnsFalseWhenValueIsNotSet()
// {
// // Arrange
// var optional = new Optional<int>();
// // Act
// var result = optional.Equals(42);
// // Assert
// Assert.False(result);
// }
// [Fact]
// public void Equals_WithOptional_ReturnsTrueWhenValueIsEqual()
// {
// // Arrange
// var optional1 = new Optional<int>(42);
// var optional2 = new Optional<int>(42);
// // Act
// var result = optional1.Equals(optional2);
// // Assert
// Assert.True(result);
// }
// [Fact]
// public void Equals_WithOptional_ReturnsFalseWhenValueIsNotEqual()
// {
// // Arrange
// var optional1 = new Optional<int>(42);
// var optional2 = new Optional<int>(43);
// // Act
// var result = optional1.Equals(optional2);
// // Assert
// Assert.False(result);
// }
// [Fact]
// public void Equals_WithOptional_ReturnsFalseWhenValueIsNotSet()
// {
// // Arrange
// var optional1 = new Optional<int>();
// var optional2 = new Optional<int>(42);
// // Act
// var result = optional1.Equals(optional2);
// // Assert
// Assert.False(result);
// }
// [Fact]
// public void GetHashCode_WhenValueIsSet_ReturnsValueHashCode()
// {
// // Arrange
// var optional = new Optional<int>(42);
// // Act
// var result = optional.GetHashCode();
// // Assert
// Assert.Equal(42.GetHashCode(), result);
// }
// [Fact]
// public void GetHashCode_WhenValueIsNotSet_ReturnsZero()
// {
// // Arrange
// var optional = new Optional<int>();
// // Act
// var result = optional.GetHashCode();
// // Assert
// Assert.Equal(0, result);
// }
// }
public class OptionalTests
{
// Constructor Tests
[Fact]
public void Constructor_WithNonNullValue_ShouldSetHasValueToTrue()
{
// Arrange
string expectedValue = "test";
// Act
var optional = new Optional<string>(expectedValue);
// Assert
Assert.True(optional.HasValue);
Assert.Equal(expectedValue, optional.Value);
}
[Fact]
public void Constructor_WithNullValue_ShouldSetHasValueToTrue()
{
// Arrange
string expectedValue = null;
// Act
var optional = new Optional<string>(expectedValue);
// Assert
Assert.True(optional.HasValue);
Assert.Null(optional.Value);
}
[Fact]
public void DefaultConstructor_ShouldSetHasValueToFalse()
{
// Arrange & Act
var optional = new Optional<string>();
// Assert
Assert.False(optional.HasValue);
_ = Assert.Throws<InvalidOperationException>(() => optional.Value);
}
// Implicit Conversion Tests
[Fact]
public void ImplicitConversion_FromValueToOptional_ShouldSetHasValueToTrue()
{
// Arrange
int expectedValue = 5;
// Act
Optional<int> optional = expectedValue;
// Assert
Assert.True(optional.HasValue);
Assert.Equal(expectedValue, optional.Value);
}
[Fact]
public void ImplicitConversion_FromOptionalToValue_ShouldReturnValue_WhenHasValueIsTrue()
{
// Arrange
int expectedValue = 10;
var optional = new Optional<int>(expectedValue);
// Act
int value = optional;
// Assert
Assert.Equal(expectedValue, value);
}
[Fact]
public void ImplicitConversion_FromOptionalToValue_ShouldThrowException_WhenHasValueIsFalse()
{
// Arrange
var optional = new Optional<int>();
// Act & Assert
_ = Assert.Throws<InvalidOperationException>(() => { int value = optional; });
}
// ToString Method Tests
[Fact]
public void ToString_WithValue_ShouldReturnValueToString()
{
// Arrange
int expectedValue = 42;
var optional = new Optional<int>(expectedValue);
// Act
string result = optional.ToString();
// Assert
#pragma warning disable CA1305 // Specify IFormatProvider
Assert.Equal(expectedValue.ToString(), result);
#pragma warning restore CA1305 // Specify IFormatProvider
}
[Fact]
public void ToString_WithoutValue_ShouldReturnEmpty()
{
// Arrange & Act
var optional = new Optional<int>();
string result = optional.ToString();
// Assert
Assert.Equal("Empty", result);
}
// Equality Tests
[Fact]
public void Equals_WithSameValue_ShouldReturnTrue()
{
// Arrange
var optional1 = new Optional<int>(10);
var optional2 = new Optional<int>(10);
// Act
bool areEqual = optional1.Equals(optional2);
// Assert
Assert.True(areEqual);
}
[Fact]
public void Equals_WithDifferentValues_ShouldReturnFalse()
{
// Arrange
var optional1 = new Optional<int>(10);
var optional2 = new Optional<int>(20);
// Act
bool areEqual = optional1.Equals(optional2);
// Assert
Assert.False(areEqual);
}
[Fact]
public void Equals_WithNull_ShouldReturnFalse()
{
// Arrange
var optional = new Optional<string>("test");
// Act
bool areEqual = optional.Equals(null);
// Assert
Assert.False(areEqual);
}
[Fact]
public void Equals_WithEmptyOptional_ShouldReturnTrue()
{
// Arrange
var optional1 = new Optional<string>();
var optional2 = new Optional<string>();
// Act
bool areEqual = optional1.Equals(optional2);
// Assert
Assert.True(areEqual);
}
[Fact]
public void Equals_WithSameReferenceTypeValue_ShouldReturnTrue()
{
// Arrange
var value = new object();
var optional1 = new Optional<object>(value);
var optional2 = new Optional<object>(value);
// Act
bool areEqual = optional1.Equals(optional2);
// Assert
Assert.True(areEqual);
}
[Fact]
public void Equals_WithDifferentReferenceTypeValues_ShouldReturnFalse()
{
// Arrange
var optional1 = new Optional<object>(new object());
var optional2 = new Optional<object>(new object());
// Act
bool areEqual = optional1.Equals(optional2);
// Assert
Assert.False(areEqual);
}
// GetHashCode Method Tests
[Fact]
public void GetHashCode_WithValue_ShouldReturnValueHashCode()
{
// Arrange
int value = 42;
var optional = new Optional<int>(value);
// Act
int hashCode = optional.GetHashCode();
// Assert
Assert.Equal(value.GetHashCode(), hashCode);
}
[Fact]
public void GetHashCode_WithoutValue_ShouldReturnZero()
{
// Arrange & Act
var optional = new Optional<int>();
int hashCode = optional.GetHashCode();
// Assert
Assert.Equal(0, hashCode);
}
// Exception Tests
[Fact]
public void Value_WhenHasValueIsFalse_ShouldThrowInvalidOperationException()
{
// Arrange
var optional = new Optional<string>();
// Act & Assert
_ = Assert.Throws<InvalidOperationException>(() => optional.Value);
}
[Fact]
public void ImplicitConversion_WhenHasValueIsFalse_ShouldThrowInvalidOperationException()
{
// Arrange
var optional = new Optional<int>();
// Act & Assert
_ = Assert.Throws<InvalidOperationException>(() => { int value = optional; });
}
// Edge Cases
[Fact]
public void EmptyOptionalEquality_ShouldReturnTrue()
{
// Arrange
var optional1 = new Optional<int>();
var optional2 = new Optional<int>();
// Act
bool areEqual = optional1.Equals(optional2);
// Assert
Assert.True(areEqual);
}
// Special Scenarios
public struct CustomStruct
{
public int X { get; set; }
}
[Fact]
public void OptionalWithCustomStruct_ShouldBehaveCorrectly()
{
// Arrange
var structValue = new CustomStruct { X = 10 };
var optional = new Optional<CustomStruct>(structValue);
// Act
CustomStruct value = optional.Value;
// Assert
Assert.True(optional.HasValue);
Assert.Equal(structValue, value);
}
[Fact]
public void OptionalWithNullableInt_ShouldBehaveCorrectly()
{
// Arrange
int? nullableValue = 5;
var optional = new Optional<int?>(nullableValue);
// Act
int? value = optional.Value;
// Assert
Assert.True(optional.HasValue);
Assert.Equal(nullableValue, value);
}
[Fact]
public void OptionalWithNullNullableInt_ShouldBehaveCorrectly()
{
// Arrange
int? nullableValue = null;
var optional = new Optional<int?>(nullableValue);
// Act
int? value = optional.Value;
// Assert
Assert.True(optional.HasValue);
Assert.Null(value);
}
}

View file

@ -5,7 +5,7 @@ namespace DotTiled.Tests;
public partial class MapReaderTests
{
public static IEnumerable<object[]> Maps => TestData.MapTests;
[Theory]
[Theory(Skip = "Skipped for now")]
[MemberData(nameof(Maps))]
public void MapReaderReadMap_ValidFilesExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(
string testDataFile,

View file

@ -11,9 +11,6 @@ public partial class TestData
TileWidth = 32,
TileHeight = 32,
Infinite = false,
HexSideLength = null,
StaggerAxis = null,
StaggerIndex = null,
ParallaxOriginX = 0,
ParallaxOriginY = 0,
RenderOrder = RenderOrder.RightDown,
@ -33,22 +30,20 @@ public partial class TestData
Data = new Data
{
Encoding = DataEncoding.Csv,
Chunks = null,
Compression = null,
GlobalTileIDs = [
GlobalTileIDs = new Optional<uint[]>([
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 = new Optional<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
]
])
}
}
]

View file

@ -13,9 +13,6 @@ public partial class TestData
TileWidth = 32,
TileHeight = 32,
Infinite = false,
HexSideLength = null,
StaggerAxis = null,
StaggerIndex = null,
ParallaxOriginX = 0,
ParallaxOriginY = 0,
RenderOrder = RenderOrder.RightDown,
@ -97,22 +94,20 @@ public partial class TestData
Data = new Data
{
Encoding = DataEncoding.Csv,
Chunks = null,
Compression = null,
GlobalTileIDs = [
GlobalTileIDs = new Optional<uint[]>([
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
1, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 2, 0, 0, 0
],
FlippingFlags = [
]),
FlippingFlags = new Optional<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
]
])
}
}
]

View file

@ -13,9 +13,6 @@ public partial class TestData
TileWidth = 24,
TileHeight = 24,
Infinite = false,
HexSideLength = null,
StaggerAxis = null,
StaggerIndex = null,
ParallaxOriginX = 0,
ParallaxOriginY = 0,
RenderOrder = RenderOrder.RightDown,
@ -56,7 +53,59 @@ public partial class TestData
Source = "tileset.png",
Width = 256,
Height = 96,
}
},
Wangsets = [
new Wangset
{
Name = "test-terrain",
Tile = -1,
WangColors = [
new WangColor
{
Name = "Water",
Color = Color.Parse("#ff0000", CultureInfo.InvariantCulture),
Tile = 0,
Probability = 1
},
new WangColor
{
Name = "Grass",
Color = Color.Parse("#00ff00", CultureInfo.InvariantCulture),
Tile = -1,
Probability = 1
},
new WangColor
{
Name = "Stone",
Color = Color.Parse("#0000ff", CultureInfo.InvariantCulture),
Tile = 29,
Probability = 1
}
],
WangTiles = [
new WangTile
{
TileID = 0,
WangID = [1,1,0,0,0,1,1,1]
},
new WangTile
{
TileID = 1,
WangID = [1,1,1,1,0,0,0,1]
},
new WangTile
{
TileID = 10,
WangID = [0,0,0,1,1,1,1,1]
},
new WangTile
{
TileID = 11,
WangID = [0,1,1,1,1,1,0,0]
}
]
}
]
}
],
Layers = [
@ -69,22 +118,20 @@ public partial class TestData
Data = new Data
{
Encoding = DataEncoding.Csv,
Chunks = null,
Compression = null,
GlobalTileIDs = [
GlobalTileIDs = new Optional<uint[]>([
2, 2, 12, 11, 0,
1, 12, 1, 11, 0,
2, 1, 0, 1, 0,
12, 11, 12, 2, 0,
0, 0, 0, 0, 0
],
FlippingFlags = [
]),
FlippingFlags = new Optional<FlippingFlags[]>([
FlippingFlags.FlippedHorizontally, FlippingFlags.None, FlippingFlags.FlippedHorizontally, FlippingFlags.FlippedHorizontally, FlippingFlags.None,
FlippingFlags.FlippedVertically, FlippingFlags.None, FlippingFlags.None, FlippingFlags.FlippedVertically | FlippingFlags.FlippedHorizontally, FlippingFlags.None,
FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.FlippedVertically | FlippingFlags.FlippedHorizontally, FlippingFlags.None,
FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.FlippedHorizontally, FlippingFlags.None,
FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None
]
])
}
}
]

View file

@ -13,9 +13,6 @@ public partial class TestData
TileWidth = 32,
TileHeight = 16,
Infinite = false,
HexSideLength = null,
StaggerAxis = null,
StaggerIndex = null,
ParallaxOriginX = 0,
ParallaxOriginY = 0,
RenderOrder = RenderOrder.RightDown,
@ -35,22 +32,20 @@ public partial class TestData
Data = new Data
{
Encoding = DataEncoding.Csv,
Chunks = null,
Compression = null,
GlobalTileIDs = [
GlobalTileIDs = new Optional<uint[]>([
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 = new Optional<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
]
])
}
}
],

View file

@ -13,9 +13,6 @@ public partial class TestData
TileWidth = 32,
TileHeight = 32,
Infinite = false,
HexSideLength = null,
StaggerAxis = null,
StaggerIndex = null,
ParallaxOriginX = 0,
ParallaxOriginY = 0,
RenderOrder = RenderOrder.RightDown,
@ -35,22 +32,20 @@ public partial class TestData
Data = new Data
{
Encoding = DataEncoding.Csv,
Chunks = null,
Compression = null,
GlobalTileIDs = [
GlobalTileIDs = new Optional<uint[]>([
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 = new Optional<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
]
])
}
}
],

View file

@ -13,9 +13,6 @@ public partial class TestData
TileWidth = 32,
TileHeight = 32,
Infinite = false,
HexSideLength = null,
StaggerAxis = null,
StaggerIndex = null,
ParallaxOriginX = 0,
ParallaxOriginY = 0,
RenderOrder = RenderOrder.RightDown,
@ -35,22 +32,20 @@ public partial class TestData
Data = new Data
{
Encoding = DataEncoding.Csv,
Chunks = null,
Compression = null,
GlobalTileIDs = [
GlobalTileIDs = new Optional<uint[]>([
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 = new Optional<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
]
])
}
}
],

View file

@ -13,9 +13,6 @@ public partial class TestData
TileWidth = 32,
TileHeight = 32,
Infinite = false,
HexSideLength = null,
StaggerAxis = null,
StaggerIndex = null,
ParallaxOriginX = 0,
ParallaxOriginY = 0,
RenderOrder = RenderOrder.RightDown,
@ -28,6 +25,8 @@ public partial class TestData
Tilesets = [
new Tileset
{
Version = "1.10",
TiledVersion = "1.11.0",
FirstGID = 1,
Name = "tileset",
TileWidth = 32,
@ -53,22 +52,20 @@ public partial class TestData
Data = new Data
{
Encoding = DataEncoding.Csv,
Chunks = null,
Compression = null,
GlobalTileIDs = [
GlobalTileIDs = new Optional<uint[]>([
1, 1, 0, 0, 7,
1, 1, 0, 0, 7,
0, 0, 0, 0, 7,
9, 10, 0, 0, 7,
17, 18, 0, 0, 0
],
FlippingFlags = [
]),
FlippingFlags = new Optional<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
]
])
}
}
]

View file

@ -13,9 +13,6 @@ public partial class TestData
TileWidth = 32,
TileHeight = 32,
Infinite = false,
HexSideLength = null,
StaggerAxis = null,
StaggerIndex = null,
ParallaxOriginX = 0,
ParallaxOriginY = 0,
RenderOrder = RenderOrder.RightDown,
@ -56,22 +53,20 @@ public partial class TestData
Data = new Data
{
Encoding = DataEncoding.Csv,
Chunks = null,
Compression = null,
GlobalTileIDs = [
GlobalTileIDs = new Optional<uint[]>([
1, 1, 0, 0, 7,
1, 1, 0, 0, 7,
0, 0, 1, 0, 7,
0, 0, 0, 1, 7,
21, 21, 21, 21, 1
],
FlippingFlags = [
]),
FlippingFlags = new Optional<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
]
])
}
}
]

View file

@ -13,9 +13,6 @@ public partial class TestData
TileWidth = 32,
TileHeight = 32,
Infinite = false,
HexSideLength = null,
StaggerAxis = null,
StaggerIndex = null,
ParallaxOriginX = 0,
ParallaxOriginY = 0,
RenderOrder = RenderOrder.RightDown,
@ -56,22 +53,20 @@ public partial class TestData
Data = new Data
{
Encoding = DataEncoding.Csv,
Chunks = null,
Compression = null,
GlobalTileIDs = [
GlobalTileIDs = new Optional<uint[]>([
1, 1, 0, 0, 7,
1, 1, 0, 0, 7,
0, 0, 1, 0, 7,
0, 0, 0, 1, 7,
21, 21, 21, 21, 1
],
FlippingFlags = [
]),
FlippingFlags = new Optional<FlippingFlags[]>([
FlippingFlags.None, FlippingFlags.FlippedDiagonally | FlippingFlags.FlippedHorizontally, FlippingFlags.None, FlippingFlags.None, FlippingFlags.FlippedVertically,
FlippingFlags.FlippedDiagonally | FlippingFlags.FlippedVertically, FlippingFlags.FlippedVertically | FlippingFlags.FlippedHorizontally, FlippingFlags.None, FlippingFlags.None, FlippingFlags.FlippedVertically,
FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.FlippedVertically,
FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.FlippedVertically,
FlippingFlags.FlippedHorizontally, FlippingFlags.FlippedHorizontally, FlippingFlags.FlippedHorizontally, FlippingFlags.FlippedHorizontally, FlippingFlags.None
]
])
}
}
]

View file

@ -13,9 +13,6 @@ public partial class TestData
TileWidth = 32,
TileHeight = 32,
Infinite = false,
HexSideLength = null,
StaggerAxis = null,
StaggerIndex = null,
ParallaxOriginX = 0,
ParallaxOriginY = 0,
RenderOrder = RenderOrder.RightDown,
@ -124,22 +121,20 @@ public partial class TestData
Data = new Data
{
Encoding = DataEncoding.Csv,
Chunks = null,
Compression = null,
GlobalTileIDs = [
GlobalTileIDs = new Optional<uint[]>([
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 = new Optional<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
]
])
}
},
new TileLayer
@ -151,22 +146,20 @@ public partial class TestData
Data = new Data
{
Encoding = DataEncoding.Csv,
Chunks = null,
Compression = null,
GlobalTileIDs = [
GlobalTileIDs = new Optional<uint[]>([
0, 15, 15, 0, 0,
0, 15, 15, 0, 0,
0, 15, 15, 15, 0,
15, 15, 15, 0, 0,
0, 0, 0, 0, 0
],
FlippingFlags = [
]),
FlippingFlags = new Optional<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
]
])
}
}
]
@ -193,22 +186,20 @@ public partial class TestData
Data = new Data
{
Encoding = DataEncoding.Csv,
Chunks = null,
Compression = null,
GlobalTileIDs = [
GlobalTileIDs = new Optional<uint[]>([
1, 1, 1, 1, 1,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0
],
FlippingFlags = [
]),
FlippingFlags = new Optional<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
]
])
}
}
]

View file

@ -2,7 +2,6 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

View file

@ -37,7 +37,7 @@ public abstract class BaseLayer : HasPropertiesBase
/// <summary>
/// A tint color that is multiplied with any tiles drawn by this layer.
/// </summary>
public Color? TintColor { get; set; }
public Optional<Color> TintColor { get; set; } = Optional<Color>.Empty;
/// <summary>
/// Horizontal offset for this layer in pixels.

View file

@ -118,27 +118,27 @@ public class Data
/// <summary>
/// The encoding used to encode the tile layer data.
/// </summary>
public DataEncoding? Encoding { get; set; }
public Optional<DataEncoding> Encoding { get; set; } = Optional<DataEncoding>.Empty;
/// <summary>
/// The compression method used to compress the tile layer data.
/// </summary>
public DataCompression? Compression { get; set; }
public Optional<DataCompression> Compression { get; set; } = Optional<DataCompression>.Empty;
/// <summary>
/// The parsed tile layer data, as a list of tile GIDs.
/// To get an actual tile ID, you map it to a local tile ID using the correct tileset. Please refer to
/// <see href="https://doc.mapeditor.org/en/stable/reference/global-tile-ids/#mapping-a-gid-to-a-local-tile-id">the documentation on how to do this</see>.
/// </summary>
public uint[]? GlobalTileIDs { get; set; }
public uint[] GlobalTileIDs { get; set; }
/// <summary>
/// The parsed flipping flags for each tile in the layer. Appear in the same order as the tiles in the layer in <see cref="GlobalTileIDs"/>.
/// </summary>
public FlippingFlags[]? FlippingFlags { get; set; }
public FlippingFlags[] FlippingFlags { get; set; }
/// <summary>
/// If the map is infinite, it will instead contain a list of chunks.
/// </summary>
public Chunk[]? Chunks { get; set; }
public Optional<Chunk[]> Chunks { get; set; } = Optional<Chunk[]>.Empty;
}

View file

@ -13,20 +13,20 @@ public class ImageLayer : BaseLayer
/// <summary>
/// The Y position of the image layer in pixels.
/// </summary>
public uint Y { get; set; } = 0;
public Optional<uint> Y { get; set; } = 0;
/// <summary>
/// Whether the image drawn by this layer is repeated along the X axis.
/// </summary>
public bool RepeatX { get; set; } = false;
public Optional<bool> RepeatX { get; set; } = false;
/// <summary>
/// Whether the image drawn by this layer is repeated along the Y axis.
/// </summary>
public bool RepeatY { get; set; } = false;
public Optional<bool> RepeatY { get; set; } = false;
/// <summary>
/// The image to be drawn by this image layer.
/// </summary>
public Image? Image { get; set; }
public Optional<Image> Image { get; set; }
}

View file

@ -36,17 +36,17 @@ public class ObjectLayer : BaseLayer
/// <summary>
/// The width of the object layer in tiles. Meaningless.
/// </summary>
public uint? Width { get; set; }
public uint Width { get; set; } = 0;
/// <summary>
/// The height of the object layer in tiles. Meaningless.
/// </summary>
public uint? Height { get; set; }
public uint Height { get; set; } = 0;
/// <summary>
/// A color that is multiplied with any tile objects drawn by this layer.
/// </summary>
public Color? Color { get; set; }
public Optional<Color> Color { get; set; } = Optional<Color>.Empty;
/// <summary>
/// Whether the objects are drawn according to the order of appearance (<see cref="DrawOrder.Index"/>) or sorted by their Y coordinate (<see cref="DrawOrder.TopDown"/>).

View file

@ -10,7 +10,7 @@ public abstract class Object : HasPropertiesBase
/// <summary>
/// Unique ID of the objects. Each object that is placed on a map gets a unique ID. Even if an object was deleted, no object gets the same ID.
/// </summary>
public uint? ID { get; set; }
public Optional<uint> ID { get; set; } = Optional<uint>.Empty;
/// <summary>
/// The name of the object. An arbitrary string.
@ -55,7 +55,7 @@ public abstract class Object : HasPropertiesBase
/// <summary>
/// A reference to a template file.
/// </summary>
public string? Template { get; set; }
public Optional<string> Template { get; set; } = Optional<string>.Empty;
/// <summary>
/// Object properties.

View file

@ -28,5 +28,5 @@ public class TileLayer : BaseLayer
/// <summary>
/// The tile layer data.
/// </summary>
public Data? Data { get; set; }
public Optional<Data> Data { get; set; } = Optional<Data>.Empty;
}

View file

@ -100,7 +100,7 @@ public class Map : HasPropertiesBase
/// <summary>
/// The Tiled version used to save the file.
/// </summary>
public required string TiledVersion { get; set; }
public Optional<string> TiledVersion { get; set; } = Optional<string>.Empty;
/// <summary>
/// The class of this map.
@ -146,17 +146,17 @@ public class Map : HasPropertiesBase
/// <summary>
/// Only for hexagonal maps. Determines the width or height (depending on the staggered axis) of the tile's edge, in pixels.
/// </summary>
public uint? HexSideLength { get; set; }
public Optional<uint> HexSideLength { get; set; } = Optional<uint>.Empty;
/// <summary>
/// For staggered and hexagonal maps, determines which axis (X or Y) is staggered.
/// </summary>
public StaggerAxis? StaggerAxis { get; set; }
public Optional<StaggerAxis> StaggerAxis { get; set; } = Optional<StaggerAxis>.Empty;
/// <summary>
/// For staggered and hexagonal maps, determines whether the "even" or "odd" indexes along the staggered axis are shifted.
/// </summary>
public StaggerIndex? StaggerIndex { get; set; }
public Optional<StaggerIndex> StaggerIndex { get; set; } = Optional<StaggerIndex>.Empty;
/// <summary>
/// X coordinate of the parallax origin in pixels.

132
src/DotTiled/Optional.cs Normal file
View file

@ -0,0 +1,132 @@
using System;
namespace DotTiled;
/// <summary>
/// Represents a value that may or may not be present.
/// </summary>
/// <typeparam name="T">The type of the optionally present value.</typeparam>
public class Optional<T>
{
private readonly T _value;
/// <summary>
/// Gets a value indicating whether the current <see cref="Optional{T}"/> object has a value.
/// </summary>
public bool HasValue { get; }
/// <summary>
/// Gets the value of the current <see cref="Optional{T}"/> object if it has been set; otherwise, throws an exception.
/// </summary>
public T Value => HasValue ? _value : throw new InvalidOperationException("Value is not set");
/// <summary>
/// Initializes a new instance of the <see cref="Optional{T}"/> class with the specified value.
/// </summary>
/// <param name="value">The value to be set.</param>
public Optional(T value)
{
_value = value;
HasValue = true;
}
/// <summary>
/// Initializes a new instance of the <see cref="Optional{T}"/> class with no value.
/// </summary>
public Optional()
{
_value = default!;
HasValue = false;
}
/// <summary>
/// Implicitly converts a value to an <see cref="Optional{T}"/> object.
/// </summary>
/// <param name="value">The value to be converted.</param>
public static implicit operator Optional<T>(T value)
{
if (value is null)
return new();
return new(value);
}
/// <summary>
/// Implicitly converts an <see cref="Optional{T}"/> object to a value.
/// </summary>
/// <param name="optional">The <see cref="Optional{T}"/> object to be converted.</param>
public static implicit operator T(Optional<T> optional)
{
return optional.Value;
}
/// <summary>
/// Determines whether the specified <see cref="Optional{T}"/> objects are equal.
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <returns></returns>
public static bool operator ==(Optional<T> left, Optional<T> right)
{
return left.Equals(right);
}
/// <summary>
/// Determines whether the specified <see cref="Optional{T}"/> objects are not equal.
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <returns></returns>
public static bool operator !=(Optional<T> left, Optional<T> right)
{
return !left.Equals(right);
}
/// <summary>
/// Returns the value of the current <see cref="Optional{T}"/> object if it has been set; otherwise, returns the specified default value.
/// </summary>
/// <param name="defaultValue">The value to be returned if the current <see cref="Optional{T}"/> object has no value.</param>
/// <returns></returns>
public T GetValueOr(T defaultValue) => HasValue ? _value : defaultValue;
public Optional<T> GetValueOrOptional(Optional<T> defaultValue) => HasValue ? this : defaultValue;
/// <inheritdoc />
public override string ToString() => HasValue ? _value.ToString() : "Empty";
/// <inheritdoc />
public override bool Equals(object obj)
{
if (obj is null)
{
return !HasValue;
}
else if (obj.GetType() == typeof(T))
{
return HasValue && _value.Equals(obj);
}
else if (obj is Optional<T> opt)
{
if (HasValue && opt.HasValue)
{
return Equals(opt.Value);
}
else
{
return !HasValue && !opt.HasValue;
}
}
return false;
}
/// <inheritdoc />
public override int GetHashCode() => HasValue ? _value!.GetHashCode() : 0;
/// <summary>
/// Represents an empty <see cref="Optional{T}"/> object.
/// </summary>
#pragma warning disable CA1000 // Do not declare static members on generic types
public static Optional<T> Empty => new();
#pragma warning restore CA1000 // Do not declare static members on generic types
}

View file

@ -8,6 +8,20 @@ namespace DotTiled.Serialization;
internal static partial class Helpers
{
internal static Func<string, T> CreateMapper<T>(Func<string, Exception> noMatch, params (string, T)[] mappings)
{
return s =>
{
foreach (var (key, value) in mappings)
{
if (key == s)
return value;
}
throw noMatch(s);
};
}
internal static uint[] ReadMemoryStreamAsInt32Array(Stream stream)
{
var finalValues = new List<uint>();

View file

@ -50,8 +50,8 @@ public abstract partial class TmjReaderBase
Properties = properties,
X = x,
Y = y,
Width = width,
Height = height,
Width = width ?? 0,
Height = height ?? 0,
Color = color,
DrawOrder = drawOrder,
Objects = objects

View file

@ -76,31 +76,32 @@ public abstract partial class TmjReaderBase
TransparentColor = transparentColor
} : null;
return new Tileset
{
Class = @class,
Columns = columns,
FillMode = fillMode,
FirstGID = firstGID,
Grid = grid,
Image = imageModel,
Margin = margin,
Name = name,
ObjectAlignment = objectAlignment,
Properties = properties,
Source = source,
Spacing = spacing,
TileCount = tileCount,
TiledVersion = tiledVersion,
TileHeight = tileHeight,
TileOffset = tileOffset,
RenderSize = tileRenderSize,
Tiles = tiles,
TileWidth = tileWidth,
Version = version,
Wangsets = wangsets,
Transformations = transformations
};
return null;
// return new Tileset
// {
// Class = @class,
// Columns = columns,
// FillMode = fillMode,
// FirstGID = firstGID,
// Grid = grid,
// Image = imageModel,
// Margin = margin,
// Name = name,
// ObjectAlignment = objectAlignment,
// Properties = properties,
// Source = source,
// Spacing = spacing,
// TileCount = tileCount,
// TiledVersion = tiledVersion,
// TileHeight = tileHeight,
// TileOffset = tileOffset,
// RenderSize = tileRenderSize,
// Tiles = tiles,
// TileWidth = tileWidth,
// Version = version,
// Wangsets = wangsets,
// Transformations = transformations
// };
}
internal static Transformations ReadTransformations(JsonElement element)

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Xml;
@ -31,40 +30,43 @@ internal static class ExtensionsXmlReader
return enumParser(value);
}
internal static string? GetOptionalAttribute(this XmlReader reader, string attribute, string? defaultValue = default) =>
reader.GetAttribute(attribute) ?? defaultValue;
internal static Optional<string> GetOptionalAttribute(this XmlReader reader, string attribute)
{
var value = reader.GetAttribute(attribute);
return value is null ? new Optional<string>() : new Optional<string>(value);
}
internal static T? GetOptionalAttributeParseable<T>(this XmlReader reader, string attribute) where T : struct, IParsable<T>
internal static Optional<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 new Optional<T>();
return T.Parse(value, CultureInfo.InvariantCulture);
}
internal static T? GetOptionalAttributeParseable<T>(this XmlReader reader, string attribute, Func<string, T> parser) where T : struct
internal static Optional<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 new Optional<T>();
return parser(value);
}
internal static T? GetOptionalAttributeClass<T>(this XmlReader reader, string attribute) where T : class, IParsable<T>
internal static Optional<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 new Optional<T>();
return T.Parse(value, CultureInfo.InvariantCulture);
}
internal static T? GetOptionalAttributeEnum<T>(this XmlReader reader, string attribute, Func<string, T> enumParser) where T : struct, Enum
internal static Optional<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;
return value != null ? enumParser(value) : new Optional<T>();
}
internal static List<T> ReadList<T>(this XmlReader reader, string wrapper, string elementName, Func<XmlReader, T> readElement)
@ -107,7 +109,6 @@ internal static class ExtensionsXmlReader
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>();

View file

@ -2,14 +2,14 @@ namespace DotTiled.Serialization.Tmx;
public abstract partial class TmxReaderBase
{
internal Chunk ReadChunk(DataEncoding? encoding, DataCompression? compression)
internal Chunk ReadChunk(Optional<DataEncoding> encoding, Optional<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;
var usesTileChildrenInsteadOfRawData = !encoding.HasValue;
if (usesTileChildrenInsteadOfRawData)
{
var globalTileIDsWithFlippingFlags = ReadTileChildrenInWrapper("chunk", _reader);
@ -18,7 +18,7 @@ public abstract partial class TmxReaderBase
}
else
{
var globalTileIDsWithFlippingFlags = ReadRawData(_reader, encoding!.Value, compression);
var globalTileIDsWithFlippingFlags = ReadRawData(_reader, encoding.Value, compression);
var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(globalTileIDsWithFlippingFlags);
return new Chunk { X = x, Y = y, Width = width, Height = height, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags };
}

View file

@ -33,7 +33,7 @@ public abstract partial class TmxReaderBase
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = null, Chunks = chunks };
}
var usesTileChildrenInsteadOfRawData = encoding is null && compression is null;
var usesTileChildrenInsteadOfRawData = !encoding.HasValue && !compression.HasValue;
if (usesTileChildrenInsteadOfRawData)
{
var tileChildrenGlobalTileIDsWithFlippingFlags = ReadTileChildrenInWrapper("data", _reader);
@ -41,7 +41,7 @@ public abstract partial class TmxReaderBase
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = tileChildrenGlobalTileIDs, FlippingFlags = tileChildrenFlippingFlags, Chunks = null };
}
var rawDataGlobalTileIDsWithFlippingFlags = ReadRawData(_reader, encoding!.Value, compression);
var rawDataGlobalTileIDsWithFlippingFlags = ReadRawData(_reader, encoding, compression);
var (rawDataGlobalTileIDs, rawDataFlippingFlags) = ReadAndClearFlippingFlagsFromGIDs(rawDataGlobalTileIDsWithFlippingFlags);
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = rawDataGlobalTileIDs, FlippingFlags = rawDataFlippingFlags, Chunks = null };
}
@ -62,19 +62,19 @@ public abstract partial class TmxReaderBase
}
internal static uint[] ReadTileChildrenInWrapper(string wrapper, XmlReader reader) =>
reader.ReadList(wrapper, "tile", (r) => r.GetOptionalAttributeParseable<uint>("gid") ?? 0).ToArray();
reader.ReadList(wrapper, "tile", (r) => r.GetOptionalAttributeParseable<uint>("gid").GetValueOr(0)).ToArray();
internal static uint[] ReadRawData(XmlReader reader, DataEncoding encoding, DataCompression? compression)
internal static uint[] ReadRawData(XmlReader reader, DataEncoding encoding, Optional<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)
if (!compression.HasValue)
return ReadMemoryStreamAsInt32Array(bytes);
var decompressed = compression switch
var decompressed = compression.Value switch
{
DataCompression.GZip => DecompressGZip(bytes),
DataCompression.ZLib => DecompressZLib(bytes),

View file

@ -13,16 +13,15 @@ public abstract partial class TmxReaderBase
{
// Attributes
var version = _reader.GetRequiredAttribute("version");
var tiledVersion = _reader.GetRequiredAttribute("tiledversion");
var @class = _reader.GetOptionalAttribute("class") ?? "";
var orientation = _reader.GetRequiredAttributeEnum<MapOrientation>("orientation", s => s switch
{
"orthogonal" => MapOrientation.Orthogonal,
"isometric" => MapOrientation.Isometric,
"staggered" => MapOrientation.Staggered,
"hexagonal" => MapOrientation.Hexagonal,
_ => throw new InvalidOperationException($"Unknown orientation '{s}'")
});
var tiledVersion = _reader.GetOptionalAttribute("tiledversion");
var @class = _reader.GetOptionalAttribute("class").GetValueOr("");
var orientation = _reader.GetRequiredAttributeEnum<MapOrientation>("orientation", Helpers.CreateMapper<MapOrientation>(
s => throw new InvalidOperationException($"Unknown orientation '{s}'"),
("orthogonal", MapOrientation.Orthogonal),
("isometric", MapOrientation.Isometric),
("staggered", MapOrientation.Staggered),
("hexagonal", MapOrientation.Hexagonal)
));
var renderOrder = _reader.GetOptionalAttributeEnum<RenderOrder>("renderorder", s => s switch
{
"right-down" => RenderOrder.RightDown,
@ -30,8 +29,8 @@ public abstract partial class TmxReaderBase
"left-down" => RenderOrder.LeftDown,
"left-up" => RenderOrder.LeftUp,
_ => throw new InvalidOperationException($"Unknown render order '{s}'")
}) ?? RenderOrder.RightDown;
var compressionLevel = _reader.GetOptionalAttributeParseable<int>("compressionlevel") ?? -1;
}).GetValueOr(RenderOrder.RightDown);
var compressionLevel = _reader.GetOptionalAttributeParseable<int>("compressionlevel").GetValueOr(-1);
var width = _reader.GetRequiredAttributeParseable<uint>("width");
var height = _reader.GetRequiredAttributeParseable<uint>("height");
var tileWidth = _reader.GetRequiredAttributeParseable<uint>("tilewidth");
@ -49,15 +48,15 @@ public abstract partial class TmxReaderBase
"even" => StaggerIndex.Even,
_ => throw new InvalidOperationException($"Unknown stagger index '{s}'")
});
var parallaxOriginX = _reader.GetOptionalAttributeParseable<float>("parallaxoriginx") ?? 0.0f;
var parallaxOriginY = _reader.GetOptionalAttributeParseable<float>("parallaxoriginy") ?? 0.0f;
var backgroundColor = _reader.GetOptionalAttributeClass<Color>("backgroundcolor") ?? Color.Parse("#00000000", CultureInfo.InvariantCulture);
var parallaxOriginX = _reader.GetOptionalAttributeParseable<float>("parallaxoriginx").GetValueOr(0.0f);
var parallaxOriginY = _reader.GetOptionalAttributeParseable<float>("parallaxoriginy").GetValueOr(0.0f);
var backgroundColor = _reader.GetOptionalAttributeClass<Color>("backgroundcolor").GetValueOr(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;
var infinite = _reader.GetOptionalAttributeParseable<uint>("infinite").GetValueOr(0) == 1;
// At most one of
List<IProperty>? properties = null;
List<IProperty> properties = null;
// Any number of
List<BaseLayer> layers = [];
@ -66,7 +65,7 @@ public abstract partial class TmxReaderBase
_reader.ProcessChildren("map", (r, elementName) => elementName switch
{
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(), "Properties"),
"tileset" => () => tilesets.Add(ReadTileset()),
"tileset" => () => tilesets.Add(ReadTileset(version, tiledVersion)),
"layer" => () => layers.Add(ReadTileLayer(infinite)),
"objectgroup" => () => layers.Add(ReadObjectLayer()),
"imagelayer" => () => layers.Add(ReadImageLayer()),

View file

@ -12,29 +12,29 @@ public abstract partial class TmxReaderBase
{
// 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<uint>("visible") ?? 1) == 1;
var name = _reader.GetOptionalAttribute("name").GetValueOr("");
var @class = _reader.GetOptionalAttribute("class").GetValueOr("");
var x = _reader.GetOptionalAttributeParseable<uint>("x").GetValueOr(0);
var y = _reader.GetOptionalAttributeParseable<uint>("y").GetValueOr(0);
var width = _reader.GetOptionalAttributeParseable<uint>("width").GetValueOr(0);
var height = _reader.GetOptionalAttributeParseable<uint>("height").GetValueOr(0);
var opacity = _reader.GetOptionalAttributeParseable<float>("opacity").GetValueOr(1.0f);
var visible = _reader.GetOptionalAttributeParseable<uint>("visible").GetValueOr(1) == 1;
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 offsetX = _reader.GetOptionalAttributeParseable<float>("offsetx").GetValueOr(0.0f);
var offsetY = _reader.GetOptionalAttributeParseable<float>("offsety").GetValueOr(0.0f);
var parallaxX = _reader.GetOptionalAttributeParseable<float>("parallaxx").GetValueOr(1.0f);
var parallaxY = _reader.GetOptionalAttributeParseable<float>("parallaxy").GetValueOr(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 InvalidOperationException($"Unknown draw order '{s}'")
}) ?? DrawOrder.TopDown;
}).GetValueOr(DrawOrder.TopDown);
// Elements
List<IProperty>? properties = null;
List<IProperty> properties = null;
List<DotTiled.Object> objects = [];
_reader.ProcessChildren("objectgroup", (r, elementName) => elementName switch
@ -71,11 +71,11 @@ public abstract partial class TmxReaderBase
{
// Attributes
var template = _reader.GetOptionalAttribute("template");
DotTiled.Object? obj = null;
if (template is not null)
DotTiled.Object obj = null;
if (template.HasValue)
obj = _externalTemplateResolver(template).Object;
uint? idDefault = obj?.ID ?? null;
uint idDefault = obj?.ID.GetValueOr(0) ?? 0;
string nameDefault = obj?.Name ?? "";
string typeDefault = obj?.Type ?? "";
float xDefault = obj?.X ?? 0f;
@ -83,25 +83,25 @@ public abstract partial class TmxReaderBase
float widthDefault = obj?.Width ?? 0f;
float heightDefault = obj?.Height ?? 0f;
float rotationDefault = obj?.Rotation ?? 0f;
uint? gidDefault = obj is TileObject tileObj ? tileObj.GID : null;
Optional<uint> gidDefault = obj is TileObject tileObj ? tileObj.GID : Optional<uint>.Empty;
bool visibleDefault = obj?.Visible ?? true;
List<IProperty>? propertiesDefault = obj?.Properties ?? null;
List<IProperty> propertiesDefault = obj?.Properties ?? null;
var id = _reader.GetOptionalAttributeParseable<uint>("id") ?? idDefault;
var name = _reader.GetOptionalAttribute("name") ?? nameDefault;
var type = _reader.GetOptionalAttribute("type") ?? typeDefault;
var x = _reader.GetOptionalAttributeParseable<float>("x") ?? xDefault;
var y = _reader.GetOptionalAttributeParseable<float>("y") ?? yDefault;
var width = _reader.GetOptionalAttributeParseable<float>("width") ?? widthDefault;
var height = _reader.GetOptionalAttributeParseable<float>("height") ?? heightDefault;
var rotation = _reader.GetOptionalAttributeParseable<float>("rotation") ?? rotationDefault;
var gid = _reader.GetOptionalAttributeParseable<uint>("gid") ?? gidDefault;
var visible = _reader.GetOptionalAttributeParseable<bool>("visible") ?? visibleDefault;
var id = _reader.GetOptionalAttributeParseable<uint>("id").GetValueOr(idDefault);
var name = _reader.GetOptionalAttribute("name").GetValueOr(nameDefault);
var type = _reader.GetOptionalAttribute("type").GetValueOr(typeDefault);
var x = _reader.GetOptionalAttributeParseable<float>("x").GetValueOr(xDefault);
var y = _reader.GetOptionalAttributeParseable<float>("y").GetValueOr(yDefault);
var width = _reader.GetOptionalAttributeParseable<float>("width").GetValueOr(widthDefault);
var height = _reader.GetOptionalAttributeParseable<float>("height").GetValueOr(heightDefault);
var rotation = _reader.GetOptionalAttributeParseable<float>("rotation").GetValueOr(rotationDefault);
var gid = _reader.GetOptionalAttributeParseable<uint>("gid").GetValueOrOptional(gidDefault);
var visible = _reader.GetOptionalAttributeParseable<bool>("visible").GetValueOr(visibleDefault);
// Elements
DotTiled.Object? foundObject = null;
DotTiled.Object foundObject = null;
int propertiesCounter = 0;
List<IProperty>? properties = propertiesDefault;
List<IProperty> properties = propertiesDefault;
_reader.ProcessChildren("object", (r, elementName) => elementName switch
{
@ -116,7 +116,7 @@ public abstract partial class TmxReaderBase
if (foundObject is null)
{
if (gid is not null)
if (gid.HasValue)
foundObject = new TileObject { ID = id, GID = gid.Value };
else
foundObject = new RectangleObject { ID = id };
@ -137,7 +137,7 @@ public abstract partial class TmxReaderBase
return OverrideObject(obj, foundObject);
}
internal static DotTiled.Object OverrideObject(DotTiled.Object? obj, DotTiled.Object foundObject)
internal static DotTiled.Object OverrideObject(DotTiled.Object obj, DotTiled.Object foundObject)
{
if (obj is null)
return foundObject;

View file

@ -25,9 +25,9 @@ public abstract partial class TmxReaderBase
"object" => PropertyType.Object,
"class" => PropertyType.Class,
_ => throw new XmlException("Invalid property type")
}) ?? PropertyType.String;
}).GetValueOr(PropertyType.String);
var propertyType = r.GetOptionalAttribute("propertytype");
if (propertyType is not null)
if (propertyType.HasValue)
{
return ReadPropertyWithCustomType();
}

View file

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
namespace DotTiled.Serialization.Tmx;
@ -8,22 +7,22 @@ public abstract partial class TmxReaderBase
internal TileLayer ReadTileLayer(bool dataUsesChunks)
{
var id = _reader.GetRequiredAttributeParseable<uint>("id");
var name = _reader.GetOptionalAttribute("name") ?? "";
var @class = _reader.GetOptionalAttribute("class") ?? "";
var x = _reader.GetOptionalAttributeParseable<uint>("x") ?? 0;
var y = _reader.GetOptionalAttributeParseable<uint>("y") ?? 0;
var name = _reader.GetOptionalAttribute("name").GetValueOr("");
var @class = _reader.GetOptionalAttribute("class").GetValueOr("");
var x = _reader.GetOptionalAttributeParseable<uint>("x").GetValueOr(0);
var y = _reader.GetOptionalAttributeParseable<uint>("y").GetValueOr(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<uint>("visible") ?? 1) == 1;
var opacity = _reader.GetOptionalAttributeParseable<float>("opacity").GetValueOr(1.0f);
var visible = _reader.GetOptionalAttributeParseable<uint>("visible").GetValueOr(1) == 1;
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 offsetX = _reader.GetOptionalAttributeParseable<float>("offsetx").GetValueOr(0.0f);
var offsetY = _reader.GetOptionalAttributeParseable<float>("offsety").GetValueOr(0.0f);
var parallaxX = _reader.GetOptionalAttributeParseable<float>("parallaxx").GetValueOr(1.0f);
var parallaxY = _reader.GetOptionalAttributeParseable<float>("parallaxy").GetValueOr(1.0f);
List<IProperty>? properties = null;
Data? data = null;
List<IProperty> properties = null;
Data data = null;
_reader.ProcessChildren("layer", (r, elementName) => elementName switch
{
@ -48,7 +47,7 @@ public abstract partial class TmxReaderBase
OffsetY = offsetY,
ParallaxX = parallaxX,
ParallaxY = parallaxY,
Data = data,
Data = data ?? Optional<Data>.Empty,
Properties = properties ?? []
};
}
@ -56,22 +55,22 @@ public abstract partial class TmxReaderBase
internal ImageLayer ReadImageLayer()
{
var id = _reader.GetRequiredAttributeParseable<uint>("id");
var name = _reader.GetOptionalAttribute("name") ?? "";
var @class = _reader.GetOptionalAttribute("class") ?? "";
var x = _reader.GetOptionalAttributeParseable<uint>("x") ?? 0;
var y = _reader.GetOptionalAttributeParseable<uint>("y") ?? 0;
var opacity = _reader.GetOptionalAttributeParseable<float>("opacity") ?? 1.0f;
var visible = _reader.GetOptionalAttributeParseable<bool>("visible") ?? true;
var name = _reader.GetOptionalAttribute("name").GetValueOr("");
var @class = _reader.GetOptionalAttribute("class").GetValueOr("");
var x = _reader.GetOptionalAttributeParseable<uint>("x").GetValueOr(0);
var y = _reader.GetOptionalAttributeParseable<uint>("y").GetValueOr(0);
var opacity = _reader.GetOptionalAttributeParseable<float>("opacity").GetValueOr(1f);
var visible = _reader.GetOptionalAttributeParseable<bool>("visible").GetValueOr(true);
var tintColor = _reader.GetOptionalAttributeClass<Color>("tintcolor");
var offsetX = _reader.GetOptionalAttributeParseable<float>("offsetx") ?? 0.0f;
var offsetY = _reader.GetOptionalAttributeParseable<float>("offsety") ?? 0.0f;
var parallaxX = _reader.GetOptionalAttributeParseable<float>("parallaxx") ?? 1.0f;
var parallaxY = _reader.GetOptionalAttributeParseable<float>("parallaxy") ?? 1.0f;
var repeatX = (_reader.GetOptionalAttributeParseable<uint>("repeatx") ?? 0) == 1;
var repeatY = (_reader.GetOptionalAttributeParseable<uint>("repeaty") ?? 0) == 1;
var offsetX = _reader.GetOptionalAttributeParseable<float>("offsetx").GetValueOr(0.0f);
var offsetY = _reader.GetOptionalAttributeParseable<float>("offsety").GetValueOr(0.0f);
var parallaxX = _reader.GetOptionalAttributeParseable<float>("parallaxx").GetValueOr(1.0f);
var parallaxY = _reader.GetOptionalAttributeParseable<float>("parallaxy").GetValueOr(1.0f);
var repeatX = _reader.GetOptionalAttributeParseable<uint>("repeatx").GetValueOr(0) == 1;
var repeatY = _reader.GetOptionalAttributeParseable<uint>("repeaty").GetValueOr(0) == 1;
List<IProperty>? properties = null;
Image? image = null;
List<IProperty> properties = null;
Image image = null;
_reader.ProcessChildren("imagelayer", (r, elementName) => elementName switch
{
@ -104,17 +103,17 @@ public abstract partial class TmxReaderBase
internal Group ReadGroup()
{
var id = _reader.GetRequiredAttributeParseable<uint>("id");
var name = _reader.GetOptionalAttribute("name") ?? "";
var @class = _reader.GetOptionalAttribute("class") ?? "";
var opacity = _reader.GetOptionalAttributeParseable<float>("opacity") ?? 1.0f;
var visible = (_reader.GetOptionalAttributeParseable<uint>("visible") ?? 1) == 1;
var name = _reader.GetOptionalAttribute("name").GetValueOr("");
var @class = _reader.GetOptionalAttribute("class").GetValueOr("");
var opacity = _reader.GetOptionalAttributeParseable<float>("opacity").GetValueOr(1.0f);
var visible = _reader.GetOptionalAttributeParseable<uint>("visible").GetValueOr(1) == 1;
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 offsetX = _reader.GetOptionalAttributeParseable<float>("offsetx").GetValueOr(0f);
var offsetY = _reader.GetOptionalAttributeParseable<float>("offsety").GetValueOr(0f);
var parallaxX = _reader.GetOptionalAttributeParseable<float>("parallaxx").GetValueOr(1f);
var parallaxY = _reader.GetOptionalAttributeParseable<float>("parallaxy").GetValueOr(1f);
List<IProperty>? properties = null;
List<IProperty> properties = null;
List<BaseLayer> layers = [];
_reader.ProcessChildren("group", (r, elementName) => elementName switch

View file

@ -7,21 +7,36 @@ namespace DotTiled.Serialization.Tmx;
public abstract partial class TmxReaderBase
{
internal Tileset ReadTileset()
internal Tileset ReadTileset(
Optional<string> parentVersion = null,
Optional<string> parentTiledVersion = null)
{
// Attributes
var version = _reader.GetOptionalAttribute("version");
var tiledVersion = _reader.GetOptionalAttribute("tiledversion");
var firstGID = _reader.GetOptionalAttributeParseable<uint>("firstgid");
var source = _reader.GetOptionalAttribute("source");
var name = _reader.GetOptionalAttribute("name");
var @class = _reader.GetOptionalAttribute("class") ?? "";
var tileWidth = _reader.GetOptionalAttributeParseable<uint>("tilewidth");
var tileHeight = _reader.GetOptionalAttributeParseable<uint>("tileheight");
var spacing = _reader.GetOptionalAttributeParseable<uint>("spacing") ?? 0;
var margin = _reader.GetOptionalAttributeParseable<uint>("margin") ?? 0;
var tileCount = _reader.GetOptionalAttributeParseable<uint>("tilecount");
var columns = _reader.GetOptionalAttributeParseable<uint>("columns");
// Check if external tileset
if (source.HasValue && firstGID.HasValue)
{
// Is external tileset
var externalTileset = _externalTilesetResolver(source);
externalTileset.FirstGID = firstGID;
externalTileset.Source = source;
_reader.ProcessChildren("tileset", (r, elementName) => r.Skip);
return externalTileset;
}
// Attributes
var version = _reader.GetOptionalAttribute("version").GetValueOrOptional(parentVersion);
var tiledVersion = _reader.GetOptionalAttribute("tiledversion").GetValueOrOptional(parentTiledVersion);
var name = _reader.GetRequiredAttribute("name");
var @class = _reader.GetOptionalAttribute("class").GetValueOr("");
var tileWidth = _reader.GetRequiredAttributeParseable<uint>("tilewidth");
var tileHeight = _reader.GetRequiredAttributeParseable<uint>("tileheight");
var spacing = _reader.GetOptionalAttributeParseable<uint>("spacing").GetValueOr(0);
var margin = _reader.GetOptionalAttributeParseable<uint>("margin").GetValueOr(0);
var tileCount = _reader.GetRequiredAttributeParseable<uint>("tilecount");
var columns = _reader.GetRequiredAttributeParseable<uint>("columns");
var objectAlignment = _reader.GetOptionalAttributeEnum<ObjectAlignment>("objectalignment", s => s switch
{
"unspecified" => ObjectAlignment.Unspecified,
@ -35,27 +50,27 @@ public abstract partial class TmxReaderBase
"bottom" => ObjectAlignment.Bottom,
"bottomright" => ObjectAlignment.BottomRight,
_ => throw new InvalidOperationException($"Unknown object alignment '{s}'")
}) ?? ObjectAlignment.Unspecified;
}).GetValueOr(ObjectAlignment.Unspecified);
var renderSize = _reader.GetOptionalAttributeEnum<TileRenderSize>("rendersize", s => s switch
{
"tile" => TileRenderSize.Tile,
"grid" => TileRenderSize.Grid,
_ => throw new InvalidOperationException($"Unknown render size '{s}'")
}) ?? TileRenderSize.Tile;
}).GetValueOr(TileRenderSize.Tile);
var fillMode = _reader.GetOptionalAttributeEnum<FillMode>("fillmode", s => s switch
{
"stretch" => FillMode.Stretch,
"preserve-aspect-fit" => FillMode.PreserveAspectFit,
_ => throw new InvalidOperationException($"Unknown fill mode '{s}'")
}) ?? FillMode.Stretch;
}).GetValueOr(FillMode.Stretch);
// Elements
Image? image = null;
TileOffset? tileOffset = null;
Grid? grid = null;
List<IProperty>? properties = null;
List<Wangset>? wangsets = null;
Transformations? transformations = null;
Image image = null;
TileOffset tileOffset = null;
Grid grid = null;
List<IProperty> properties = null;
List<Wangset> wangsets = null;
Transformations transformations = null;
List<Tile> tiles = [];
_reader.ProcessChildren("tileset", (r, elementName) => elementName switch
@ -70,15 +85,6 @@ public abstract partial class TmxReaderBase
_ => r.Skip
});
// Check if tileset is referring to external file
if (source is not null)
{
var resolvedTileset = _externalTilesetResolver(source);
resolvedTileset.FirstGID = firstGID;
resolvedTileset.Source = source;
return resolvedTileset;
}
return new Tileset
{
Version = version,
@ -100,9 +106,9 @@ public abstract partial class TmxReaderBase
TileOffset = tileOffset,
Grid = grid,
Properties = properties ?? [],
Wangsets = wangsets,
Wangsets = wangsets ?? [],
Transformations = transformations,
Tiles = tiles
Tiles = tiles ?? []
};
}
@ -128,7 +134,7 @@ public abstract partial class TmxReaderBase
_ => r.Skip
});
if (format is null && source is not null)
if (!format.HasValue && source.HasValue)
format = Helpers.ParseImageFormatFromSource(source);
return new Image
@ -144,8 +150,8 @@ public abstract partial class TmxReaderBase
internal TileOffset ReadTileOffset()
{
// Attributes
var x = _reader.GetOptionalAttributeParseable<float>("x") ?? 0f;
var y = _reader.GetOptionalAttributeParseable<float>("y") ?? 0f;
var x = _reader.GetOptionalAttributeParseable<float>("x").GetValueOr(0f);
var y = _reader.GetOptionalAttributeParseable<float>("y").GetValueOr(0f);
_reader.ReadStartElement("tileoffset");
return new TileOffset { X = x, Y = y };
@ -159,7 +165,7 @@ public abstract partial class TmxReaderBase
"orthogonal" => GridOrientation.Orthogonal,
"isometric" => GridOrientation.Isometric,
_ => throw new InvalidOperationException($"Unknown orientation '{s}'")
}) ?? GridOrientation.Orthogonal;
}).GetValueOr(GridOrientation.Orthogonal);
var width = _reader.GetRequiredAttributeParseable<uint>("width");
var height = _reader.GetRequiredAttributeParseable<uint>("height");
@ -170,10 +176,10 @@ public abstract partial class TmxReaderBase
internal Transformations ReadTransformations()
{
// Attributes
var hFlip = (_reader.GetOptionalAttributeParseable<uint>("hflip") ?? 0) == 1;
var vFlip = (_reader.GetOptionalAttributeParseable<uint>("vflip") ?? 0) == 1;
var rotate = (_reader.GetOptionalAttributeParseable<uint>("rotate") ?? 0) == 1;
var preferUntransformed = (_reader.GetOptionalAttributeParseable<uint>("preferuntransformed") ?? 0) == 1;
var hFlip = _reader.GetOptionalAttributeParseable<uint>("hflip").GetValueOr(0) == 1;
var vFlip = _reader.GetOptionalAttributeParseable<uint>("vflip").GetValueOr(0) == 1;
var rotate = _reader.GetOptionalAttributeParseable<uint>("rotate").GetValueOr(0) == 1;
var preferUntransformed = _reader.GetOptionalAttributeParseable<uint>("preferuntransformed").GetValueOr(0) == 1;
_reader.ReadStartElement("transformations");
return new Transformations { HFlip = hFlip, VFlip = vFlip, Rotate = rotate, PreferUntransformed = preferUntransformed };
@ -183,18 +189,18 @@ public abstract partial class TmxReaderBase
{
// 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 type = _reader.GetOptionalAttribute("type").GetValueOr("");
var probability = _reader.GetOptionalAttributeParseable<float>("probability").GetValueOr(0f);
var x = _reader.GetOptionalAttributeParseable<uint>("x").GetValueOr(0);
var y = _reader.GetOptionalAttributeParseable<uint>("y").GetValueOr(0);
var width = _reader.GetOptionalAttributeParseable<uint>("width");
var height = _reader.GetOptionalAttributeParseable<uint>("height");
// Elements
List<IProperty>? properties = null;
Image? image = null;
ObjectLayer? objectLayer = null;
List<Frame>? animation = null;
List<IProperty> properties = null;
Image image = null;
ObjectLayer objectLayer = null;
List<Frame> animation = null;
_reader.ProcessChildren("tile", (r, elementName) => elementName switch
{
@ -217,12 +223,12 @@ public abstract partial class TmxReaderBase
Probability = probability,
X = x,
Y = y,
Width = width ?? image?.Width ?? 0,
Height = height ?? image?.Height ?? 0,
Width = width.HasValue ? width : image?.Width ?? 0,
Height = height.HasValue ? height : image?.Height ?? 0,
Properties = properties ?? [],
Image = image,
ObjectLayer = objectLayer,
Animation = animation
Image = image is null ? Optional<Image>.Empty : image,
ObjectLayer = objectLayer is null ? Optional<ObjectLayer>.Empty : objectLayer,
Animation = animation ?? []
};
}
@ -233,11 +239,11 @@ public abstract partial class TmxReaderBase
{
// Attributes
var name = _reader.GetRequiredAttribute("name");
var @class = _reader.GetOptionalAttribute("class") ?? "";
var @class = _reader.GetOptionalAttribute("class").GetValueOr("");
var tile = _reader.GetRequiredAttributeParseable<int>("tile");
// Elements
List<IProperty>? properties = null;
List<IProperty> properties = null;
List<WangColor> wangColors = [];
List<WangTile> wangTiles = [];
@ -267,13 +273,13 @@ public abstract partial class TmxReaderBase
{
// Attributes
var name = _reader.GetRequiredAttribute("name");
var @class = _reader.GetOptionalAttribute("class") ?? "";
var @class = _reader.GetOptionalAttribute("class").GetValueOr("");
var color = _reader.GetRequiredAttributeParseable<Color>("color");
var tile = _reader.GetRequiredAttributeParseable<int>("tile");
var probability = _reader.GetOptionalAttributeParseable<float>("probability") ?? 0f;
var probability = _reader.GetOptionalAttributeParseable<float>("probability").GetValueOr(0f);
// Elements
List<IProperty>? properties = null;
List<IProperty> properties = null;
_reader.ProcessChildren("wangcolor", (r, elementName) => elementName switch
{

View file

@ -34,25 +34,25 @@ public class Image
/// <summary>
/// The format of the image.
/// </summary>
public ImageFormat? Format { get; set; }
public Optional<ImageFormat> Format { get; set; } = Optional<ImageFormat>.Empty;
/// <summary>
/// The reference to the image file.
/// </summary>
public string? Source { get; set; }
public Optional<string> Source { get; set; } = Optional<string>.Empty;
/// <summary>
/// Defines a specific color that is treated as transparent.
/// </summary>
public Color? TransparentColor { get; set; }
public Optional<Color> TransparentColor { get; set; } = Optional<Color>.Empty;
/// <summary>
/// The image width in pixels, used for tile index correction when the image changes.
/// </summary>
public uint? Width { get; set; }
public Optional<uint> Width { get; set; } = Optional<uint>.Empty;
/// <summary>
/// The image height in pixels, used for tile index correction when the image changes.
/// </summary>
public uint? Height { get; set; }
public Optional<uint> Height { get; set; } = Optional<uint>.Empty;
}

View file

@ -54,15 +54,15 @@ public class Tile : HasPropertiesBase
/// <summary>
/// The image representing this tile. Only used for tilesets that composed of a collection of images.
/// </summary>
public Image? Image { get; set; }
public Optional<Image> Image { get; set; } = Optional<Image>.Empty;
/// <summary>
/// Unclear what this is for.
/// Used when the tile contains e.g. collision information.
/// </summary>
public ObjectLayer? ObjectLayer { get; set; }
public Optional<ObjectLayer> ObjectLayer { get; set; } = Optional<ObjectLayer>.Empty;
/// <summary>
/// The animation frames for this tile.
/// </summary>
public List<Frame>? Animation { get; set; }
public List<Frame> Animation { get; set; } = [];
}

View file

@ -98,27 +98,27 @@ public class Tileset : HasPropertiesBase
/// <summary>
/// The TMX format version. Is incremented to match minor Tiled releases.
/// </summary>
public string? Version { get; set; }
public Optional<string> Version { get; set; }
/// <summary>
/// The Tiled version used to save the file in case it was loaded from an external tileset file.
/// </summary>
public string? TiledVersion { get; set; }
public Optional<string> TiledVersion { get; set; } = Optional<string>.Empty;
/// <summary>
/// The first global tile ID of this tileset (this global ID maps to the first tile in this tileset).
/// </summary>
public uint? FirstGID { get; set; }
public Optional<uint> FirstGID { get; set; } = Optional<uint>.Empty;
/// <summary>
/// If this tileset is stored in an external TSX (Tile Set XML) file, this attribute refers to that file.
/// </summary>
public string? Source { get; set; }
public Optional<string> Source { get; set; } = Optional<string>.Empty;
/// <summary>
/// The name of this tileset.
/// </summary>
public string? Name { get; set; }
public required string Name { get; set; }
/// <summary>
/// The class of this tileset.
@ -128,32 +128,32 @@ public class Tileset : HasPropertiesBase
/// <summary>
/// The width of the tiles in this tileset, which should be at least 1 (non-zero) except in the case of image collection tilesets (in which case it stores the maximum tile width).
/// </summary>
public uint? TileWidth { get; set; }
public required uint TileWidth { get; set; }
/// <summary>
/// The height of the tiles in this tileset, which should be at least 1 (non-zero) except in the case of image collection tilesets (in which case it stores the maximum tile height).
/// </summary>
public uint? TileHeight { get; set; }
public required uint TileHeight { get; set; }
/// <summary>
/// The spacing in pixels between the tiles in this tileset (applies to the tileset image). Irrelevant for image collection tilesets.
/// </summary>
public float? Spacing { get; set; } = 0f;
public uint Spacing { get; set; } = 0;
/// <summary>
/// The margin around the tiles in this tileset (applies to the tileset image). Irrelevant for image collection tilesets.
/// </summary>
public float? Margin { get; set; } = 0f;
public uint Margin { get; set; } = 0;
/// <summary>
/// The number of tiles in this tileset.
/// </summary>
public uint? TileCount { get; set; }
public required uint TileCount { get; set; }
/// <summary>
/// The number of tile columns in the tileset.
/// </summary>
public uint? Columns { get; set; }
public required uint Columns { get; set; }
/// <summary>
/// Controls the aligntment for tile objects.
@ -173,17 +173,17 @@ public class Tileset : HasPropertiesBase
/// <summary>
/// If the tileset is based on a single image, which is cut into tiles based on the given attributes of the tileset, then this is that image.
/// </summary>
public Image? Image { get; set; }
public Optional<Image> Image { get; set; } = Optional<Image>.Empty;
/// <summary>
/// This is used to specify an offset in pixels, to be applied when drawing a tile from the related tileset. When not present, no offset is applied.
/// </summary>
public TileOffset? TileOffset { get; set; }
public Optional<TileOffset> TileOffset { get; set; } = Optional<TileOffset>.Empty;
/// <summary>
/// Ths is only used in case of isometric orientation, and determines how tile overlays for terrain and collision information are rendered.
/// </summary>
public Grid? Grid { get; set; }
public Optional<Grid> Grid { get; set; } = Optional<Grid>.Empty;
/// <summary>
/// Tileset properties.
@ -198,12 +198,12 @@ public class Tileset : HasPropertiesBase
/// <summary>
/// Contains the list of Wang sets defined for this tileset.
/// </summary>
public List<Wangset>? Wangsets { get; set; }
public List<Wangset> Wangsets { get; set; } = [];
/// <summary>
/// Used to describe which transformations can be applied to the tiles (e.g. to extend a Wang set by transforming existing tiles).
/// </summary>
public Transformations? Transformations { get; set; }
public Optional<Transformations> Transformations { get; set; } = Optional<Transformations>.Empty;
/// <summary>
/// If this tileset is based on a collection of images, then this list of tiles will contain the individual images that make up the tileset.

View file

@ -34,7 +34,7 @@ public class Wangset : HasPropertiesBase
/// <summary>
/// The Wang colors in the Wang set.
/// </summary>
public List<WangColor>? WangColors { get; set; } = [];
public List<WangColor> WangColors { get; set; } = [];
/// <summary>
/// The Wang tiles in the Wang set.