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

@ -0,0 +1,9 @@
# Representation model
Tiled map files contain various types of data, such as tilesets, layers, and object groups. The representation model is a way to represent this data in a structured way. By using the `.tmx` file format as inspiration, the representation model is a collection of classes which mimic the structure of a Tiled map file.
Certain properties throughout the representation model are marked as *optional*, meaning that they may not be present in a file. However, these properties sometimes have default values, which are used when the property is not present.
- Properties marked as *required* must be present in the file, otherwise an error will be raised.
- Properties that have default values will use the default value if the property is not present in the file, and are not marked as required or optional since you must not provide a value for them.
- Properties that are marked as *optional* may or may not be present in the file, and have no default value.

View file

@ -4,4 +4,5 @@
- name: Essentials
- href: essentials/loading-maps.md
- href: essentials/representation-model.md
- href: essentials/custom-properties.md

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.