Fix rest of model to use new structure and prepare docs

This commit is contained in:
Daniel Cronqvist 2024-08-24 23:08:24 +02:00
parent b46eed774a
commit 4580772ced
25 changed files with 94 additions and 200 deletions

View file

@ -0,0 +1 @@
# Accessing properties

View file

@ -4,3 +4,4 @@
- name: Essentials - name: Essentials
- href: loading-a-map.md - href: loading-a-map.md
- href: accessing-properties.md

View file

@ -91,7 +91,7 @@ public static partial class DotTiledAssert
AssertEqual(expected.NextObjectID, actual.NextObjectID, nameof(Map.NextObjectID)); AssertEqual(expected.NextObjectID, actual.NextObjectID, nameof(Map.NextObjectID));
AssertEqual(expected.Infinite, actual.Infinite, nameof(Map.Infinite)); AssertEqual(expected.Infinite, actual.Infinite, nameof(Map.Infinite));
AssertPropertiesList(actual.Properties, expected.Properties); AssertProperties(actual.Properties, expected.Properties);
Assert.NotNull(actual.Tilesets); Assert.NotNull(actual.Tilesets);
AssertEqual(expected.Tilesets.Count, actual.Tilesets.Count, "Tilesets.Count"); AssertEqual(expected.Tilesets.Count, actual.Tilesets.Count, "Tilesets.Count");

View file

@ -4,24 +4,7 @@ namespace DotTiled.Tests;
public static partial class DotTiledAssert public static partial class DotTiledAssert
{ {
internal static void AssertProperties(Dictionary<string, IProperty>? expected, Dictionary<string, IProperty>? actual) internal static void AssertProperties(IList<IProperty>? expected, IList<IProperty>? actual)
{
if (expected is null)
{
Assert.Null(actual);
return;
}
Assert.NotNull(actual);
AssertEqual(expected.Count, actual.Count, "Properties.Count");
foreach (var kvp in expected)
{
Assert.Contains(kvp.Key, actual.Keys);
AssertProperty((dynamic)kvp.Value, (dynamic)actual[kvp.Key]);
}
}
internal static void AssertPropertiesList(IList<IProperty>? expected, IList<IProperty>? actual)
{ {
if (expected is null) if (expected is null)
{ {
@ -36,17 +19,13 @@ public static partial class DotTiledAssert
Assert.Contains(actual, p => p.Name == prop.Name); Assert.Contains(actual, p => p.Name == prop.Name);
var actualProp = actual.First(p => p.Name == prop.Name); var actualProp = actual.First(p => p.Name == prop.Name);
AssertEqual(prop.Type, actualProp.Type, "Property.Type");
AssertEqual(prop.Name, actualProp.Name, "Property.Name");
AssertProperty((dynamic)prop, (dynamic)actualProp); AssertProperty((dynamic)prop, (dynamic)actualProp);
} }
} }
private static void AssertProperty(IProperty expected, IProperty actual)
{
AssertEqual(expected.Type, actual.Type, "Property.Type");
AssertEqual(expected.Name, actual.Name, "Property.Name");
AssertProperties((dynamic)actual, (dynamic)expected);
}
private static void AssertProperty(StringProperty expected, StringProperty actual) => AssertEqual(expected.Value, actual.Value, "StringProperty.Value"); private static void AssertProperty(StringProperty expected, StringProperty actual) => AssertEqual(expected.Value, actual.Value, "StringProperty.Value");
private static void AssertProperty(IntProperty expected, IntProperty actual) => AssertEqual(expected.Value, actual.Value, "IntProperty.Value"); private static void AssertProperty(IntProperty expected, IntProperty actual) => AssertEqual(expected.Value, actual.Value, "IntProperty.Value");
@ -64,6 +43,6 @@ public static partial class DotTiledAssert
private static void AssertProperty(ClassProperty expected, ClassProperty actual) private static void AssertProperty(ClassProperty expected, ClassProperty actual)
{ {
AssertEqual(expected.PropertyType, actual.PropertyType, "ClassProperty.PropertyType"); AssertEqual(expected.PropertyType, actual.PropertyType, "ClassProperty.PropertyType");
AssertPropertiesList(expected.Value, actual.Value); AssertProperties(expected.Value, actual.Value);
} }
} }

View file

@ -49,16 +49,15 @@ public partial class TestData
Width = 1, Width = 1,
Height = 1 Height = 1
}, },
Properties = new Dictionary<string, IProperty> Properties = [
{ new BoolProperty { Name = "tilesetbool", Value = true },
["tilesetbool"] = new BoolProperty { Name = "tilesetbool", Value = true }, new ColorProperty { Name = "tilesetcolor", Value = Color.Parse("#ffff0000", CultureInfo.InvariantCulture) },
["tilesetcolor"] = new ColorProperty { Name = "tilesetcolor", Value = Color.Parse("#ffff0000", CultureInfo.InvariantCulture) }, new FileProperty { Name = "tilesetfile", Value = "" },
["tilesetfile"] = new FileProperty { Name = "tilesetfile", Value = "" }, new FloatProperty { Name = "tilesetfloat", Value = 5.2f },
["tilesetfloat"] = new FloatProperty { Name = "tilesetfloat", Value = 5.2f }, new IntProperty { Name = "tilesetint", Value = 9 },
["tilesetint"] = new IntProperty { Name = "tilesetint", Value = 9 }, new ObjectProperty { Name = "tilesetobject", Value = 0 },
["tilesetobject"] = new ObjectProperty { Name = "tilesetobject", Value = 0 }, new StringProperty { Name = "tilesetstring", Value = "hello world!" }
["tilesetstring"] = new StringProperty { Name = "tilesetstring", Value = "hello world!" } ],
},
Tiles = [ Tiles = [
new Tile new Tile
{ {

View file

@ -95,10 +95,9 @@ public partial class TestData
new Vector2(35.6667f, 32.3333f) new Vector2(35.6667f, 32.3333f)
], ],
Template = fileExt == "tmx" ? "poly.tx" : "poly.tj", Template = fileExt == "tmx" ? "poly.tx" : "poly.tj",
Properties = new Dictionary<string, IProperty> Properties = [
{ new StringProperty { Name = "templateprop", Value = "helo there" }
["templateprop"] = new StringProperty { Name = "templateprop", Value = "helo there" } ]
}
}, },
new TileObject new TileObject
{ {

View file

@ -7,7 +7,7 @@ namespace DotTiled.Model;
/// To check the type of a layer, <see href="https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/pattern-matching">use C# pattern matching</see>, /// To check the type of a layer, <see href="https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/pattern-matching">use C# pattern matching</see>,
/// or some other mechanism to determine the type of the layer at runtime. /// or some other mechanism to determine the type of the layer at runtime.
/// </summary> /// </summary>
public abstract class BaseLayer public abstract class BaseLayer : HasPropertiesBase
{ {
/// <summary> /// <summary>
/// Unique ID of the layer. Each layer that is added to a map gets a unique ID. Even if a layer is deleted, no layer ever gets the same ID. /// Unique ID of the layer. Each layer that is added to a map gets a unique ID. Even if a layer is deleted, no layer ever gets the same ID.
@ -62,5 +62,8 @@ public abstract class BaseLayer
/// <summary> /// <summary>
/// Layer properties. /// Layer properties.
/// </summary> /// </summary>
public Dictionary<string, IProperty>? Properties { get; set; } public List<IProperty> Properties { get; set; } = [];
/// <inheritdoc/>
public override IList<IProperty> GetProperties() => Properties;
} }

View file

@ -5,7 +5,7 @@ namespace DotTiled.Model;
/// <summary> /// <summary>
/// Base class for objects in object layers. /// Base class for objects in object layers.
/// </summary> /// </summary>
public abstract class Object public abstract class Object : HasPropertiesBase
{ {
/// <summary> /// <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. /// 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.
@ -60,5 +60,8 @@ public abstract class Object
/// <summary> /// <summary>
/// Object properties. /// Object properties.
/// </summary> /// </summary>
public Dictionary<string, IProperty>? Properties { get; set; } public List<IProperty> Properties { get; set; } = [];
/// <inheritdoc/>
public override IList<IProperty> GetProperties() => Properties;
} }

View file

@ -6,7 +6,7 @@ namespace DotTiled.Model;
/// Represents a single tile in a tileset, when using a collection of images to represent the tileset. /// Represents a single tile in a tileset, when using a collection of images to represent the tileset.
/// <see href="https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#tile">Tiled documentation for Tileset tiles</see> /// <see href="https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#tile">Tiled documentation for Tileset tiles</see>
/// </summary> /// </summary>
public class Tile public class Tile : HasPropertiesBase
{ {
/// <summary> /// <summary>
/// The local tile ID within its tileset. /// The local tile ID within its tileset.
@ -46,7 +46,10 @@ public class Tile
/// <summary> /// <summary>
/// Tile properties. /// Tile properties.
/// </summary> /// </summary>
public Dictionary<string, IProperty>? Properties { get; set; } public List<IProperty> Properties { get; set; } = [];
/// <inheritdoc/>
public override IList<IProperty> GetProperties() => Properties;
/// <summary> /// <summary>
/// The image representing this tile. Only used for tilesets that composed of a collection of images. /// The image representing this tile. Only used for tilesets that composed of a collection of images.

View file

@ -93,7 +93,7 @@ public enum FillMode
/// <summary> /// <summary>
/// A tileset is a collection of tiles that can be used in a tile layer, or by tile objects. /// A tileset is a collection of tiles that can be used in a tile layer, or by tile objects.
/// </summary> /// </summary>
public class Tileset public class Tileset : HasPropertiesBase
{ {
/// <summary> /// <summary>
/// The TMX format version. Is incremented to match minor Tiled releases. /// The TMX format version. Is incremented to match minor Tiled releases.
@ -188,7 +188,10 @@ public class Tileset
/// <summary> /// <summary>
/// Tileset properties. /// Tileset properties.
/// </summary> /// </summary>
public Dictionary<string, IProperty>? Properties { get; set; } public List<IProperty> Properties { get; set; } = [];
/// <inheritdoc/>
public override IList<IProperty> GetProperties() => Properties;
// public List<Terrain>? TerrainTypes { get; set; } TODO: Implement Terrain -> Wangset conversion during deserialization // public List<Terrain>? TerrainTypes { get; set; } TODO: Implement Terrain -> Wangset conversion during deserialization

View file

@ -5,7 +5,7 @@ namespace DotTiled.Model;
/// <summary> /// <summary>
/// Represents a Wang color in a Wang set. /// Represents a Wang color in a Wang set.
/// </summary> /// </summary>
public class WangColor public class WangColor : HasPropertiesBase
{ {
/// <summary> /// <summary>
/// The name of this color. /// The name of this color.
@ -35,5 +35,8 @@ public class WangColor
/// <summary> /// <summary>
/// The Wang color properties. /// The Wang color properties.
/// </summary> /// </summary>
public Dictionary<string, IProperty>? Properties { get; set; } public List<IProperty> Properties { get; set; } = [];
/// <inheritdoc/>
public override IList<IProperty> GetProperties() => Properties;
} }

View file

@ -5,7 +5,7 @@ namespace DotTiled.Model;
/// <summary> /// <summary>
/// Defines a list of colors and any number of Wang tiles using these colors. /// Defines a list of colors and any number of Wang tiles using these colors.
/// </summary> /// </summary>
public class Wangset public class Wangset : HasPropertiesBase
{ {
/// <summary> /// <summary>
/// The name of the Wang set. /// The name of the Wang set.
@ -25,7 +25,10 @@ public class Wangset
/// <summary> /// <summary>
/// The Wang set properties. /// The Wang set properties.
/// </summary> /// </summary>
public Dictionary<string, IProperty>? Properties { get; set; } public List<IProperty> Properties { get; set; } = [];
/// <inheritdoc/>
public override IList<IProperty> GetProperties() => Properties;
// Up to 254 Wang colors // Up to 254 Wang colors
/// <summary> /// <summary>

View file

@ -76,39 +76,7 @@ internal static partial class Helpers
internal static List<IProperty> CreateInstanceOfCustomClass(CustomClassDefinition customClassDefinition) => internal static List<IProperty> CreateInstanceOfCustomClass(CustomClassDefinition customClassDefinition) =>
customClassDefinition.Members.Select(x => x.Clone()).ToList(); customClassDefinition.Members.Select(x => x.Clone()).ToList();
internal static Dictionary<string, IProperty> MergeProperties(Dictionary<string, IProperty>? baseProperties, Dictionary<string, IProperty>? overrideProperties) internal static IList<IProperty> MergeProperties(IList<IProperty>? baseProperties, IList<IProperty>? overrideProperties)
{
if (baseProperties is null)
return overrideProperties ?? new Dictionary<string, IProperty>();
if (overrideProperties is null)
return baseProperties;
var result = baseProperties.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Clone());
foreach (var (key, value) in overrideProperties)
{
if (!result.TryGetValue(key, out var baseProp))
{
result[key] = value;
continue;
}
else
{
if (value is ClassProperty classProp)
{
((ClassProperty)baseProp).Value = MergePropertiesList(((ClassProperty)baseProp).Value, classProp.Value);
}
else
{
result[key] = value;
}
}
}
return result;
}
internal static IList<IProperty> MergePropertiesList(IList<IProperty>? baseProperties, IList<IProperty>? overrideProperties)
{ {
if (baseProperties is null) if (baseProperties is null)
return overrideProperties ?? []; return overrideProperties ?? [];
@ -129,7 +97,7 @@ internal static partial class Helpers
var existingProp = result.First(x => x.Name == overrideProp.Name); var existingProp = result.First(x => x.Name == overrideProp.Name);
if (existingProp is ClassProperty classProp) if (existingProp is ClassProperty classProp)
{ {
classProp.Value = MergePropertiesList(classProp.Value, ((ClassProperty)overrideProp).Value); classProp.Value = MergeProperties(classProp.Value, ((ClassProperty)overrideProp).Value);
} }
else else
{ {

View file

@ -23,7 +23,7 @@ internal partial class Tmj
var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f); var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f);
var parallaxX = element.GetOptionalProperty<float>("parallaxx", 1.0f); var parallaxX = element.GetOptionalProperty<float>("parallaxx", 1.0f);
var parallaxY = element.GetOptionalProperty<float>("parallaxy", 1.0f); var parallaxY = element.GetOptionalProperty<float>("parallaxy", 1.0f);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null); var properties = element.GetOptionalPropertyCustom("properties", e => ReadProperties(e, customTypeDefinitions), []);
var layers = element.GetOptionalPropertyCustom<List<BaseLayer>>("layers", e => e.GetValueAsList<BaseLayer>(el => ReadLayer(el, externalTemplateResolver, customTypeDefinitions)), []); var layers = element.GetOptionalPropertyCustom<List<BaseLayer>>("layers", e => e.GetValueAsList<BaseLayer>(el => ReadLayer(el, externalTemplateResolver, customTypeDefinitions)), []);
return new Group return new Group

View file

@ -21,7 +21,7 @@ internal partial class Tmj
var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f); var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f);
var parallaxX = element.GetOptionalProperty<float>("parallaxx", 1.0f); var parallaxX = element.GetOptionalProperty<float>("parallaxx", 1.0f);
var parallaxY = element.GetOptionalProperty<float>("parallaxy", 1.0f); var parallaxY = element.GetOptionalProperty<float>("parallaxy", 1.0f);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null); var properties = element.GetOptionalPropertyCustom("properties", e => ReadProperties(e, customTypeDefinitions), []);
var image = element.GetRequiredProperty<string>("image"); var image = element.GetRequiredProperty<string>("image");
var repeatX = element.GetOptionalProperty<bool>("repeatx", false); var repeatX = element.GetOptionalProperty<bool>("repeatx", false);

View file

@ -58,7 +58,7 @@ internal partial class Tmj
var nextObjectID = element.GetRequiredProperty<uint>("nextobjectid"); var nextObjectID = element.GetRequiredProperty<uint>("nextobjectid");
var infinite = element.GetOptionalProperty<bool>("infinite", false); var infinite = element.GetOptionalProperty<bool>("infinite", false);
var properties = element.GetOptionalPropertyCustom<List<IProperty>?>("properties", el => ReadPropertiesList(el, customTypeDefinitions), null); var properties = element.GetOptionalPropertyCustom("properties", el => ReadProperties(el, customTypeDefinitions), []);
List<BaseLayer> layers = element.GetOptionalPropertyCustom<List<BaseLayer>>("layers", e => e.GetValueAsList<BaseLayer>(el => ReadLayer(el, externalTemplateResolver, customTypeDefinitions)), []); List<BaseLayer> layers = element.GetOptionalPropertyCustom<List<BaseLayer>>("layers", e => e.GetValueAsList<BaseLayer>(el => ReadLayer(el, externalTemplateResolver, customTypeDefinitions)), []);
List<Tileset> tilesets = element.GetOptionalPropertyCustom<List<Tileset>>("tilesets", e => e.GetValueAsList<Tileset>(el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions)), []); List<Tileset> tilesets = element.GetOptionalPropertyCustom<List<Tileset>>("tilesets", e => e.GetValueAsList<Tileset>(el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions)), []);
@ -84,7 +84,7 @@ internal partial class Tmj
NextLayerID = nextLayerID, NextLayerID = nextLayerID,
NextObjectID = nextObjectID, NextObjectID = nextObjectID,
Infinite = infinite, Infinite = infinite,
Properties = properties ?? [], Properties = properties,
Tilesets = tilesets, Tilesets = tilesets,
Layers = layers Layers = layers
}; };

View file

@ -24,7 +24,7 @@ internal partial class Tmj
var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f); var offsetY = element.GetOptionalProperty<float>("offsety", 0.0f);
var parallaxX = element.GetOptionalProperty<float>("parallaxx", 1.0f); var parallaxX = element.GetOptionalProperty<float>("parallaxx", 1.0f);
var parallaxY = element.GetOptionalProperty<float>("parallaxy", 1.0f); var parallaxY = element.GetOptionalProperty<float>("parallaxy", 1.0f);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null); var properties = element.GetOptionalPropertyCustom("properties", e => ReadProperties(e, customTypeDefinitions), []);
var x = element.GetOptionalProperty<uint>("x", 0); var x = element.GetOptionalProperty<uint>("x", 0);
var y = element.GetOptionalProperty<uint>("y", 0); var y = element.GetOptionalProperty<uint>("y", 0);
@ -82,7 +82,7 @@ internal partial class Tmj
bool pointDefault = false; bool pointDefault = false;
List<Vector2>? polygonDefault = null; List<Vector2>? polygonDefault = null;
List<Vector2>? polylineDefault = null; List<Vector2>? polylineDefault = null;
Dictionary<string, IProperty>? propertiesDefault = null; List<IProperty> propertiesDefault = [];
var template = element.GetOptionalProperty<string?>("template", null); var template = element.GetOptionalProperty<string?>("template", null);
if (template is not null) if (template is not null)
@ -114,7 +114,7 @@ internal partial class Tmj
var point = element.GetOptionalProperty<bool>("point", pointDefault); var point = element.GetOptionalProperty<bool>("point", pointDefault);
var polygon = element.GetOptionalPropertyCustom<List<Vector2>?>("polygon", ReadPoints, polygonDefault); var polygon = element.GetOptionalPropertyCustom<List<Vector2>?>("polygon", ReadPoints, polygonDefault);
var polyline = element.GetOptionalPropertyCustom<List<Vector2>?>("polyline", ReadPoints, polylineDefault); var polyline = element.GetOptionalPropertyCustom<List<Vector2>?>("polyline", ReadPoints, polylineDefault);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), propertiesDefault); var properties = element.GetOptionalPropertyCustom("properties", e => ReadProperties(e, customTypeDefinitions), propertiesDefault);
var rotation = element.GetOptionalProperty<float>("rotation", rotationDefault); var rotation = element.GetOptionalProperty<float>("rotation", rotationDefault);
var text = element.GetOptionalPropertyCustom<TextObject?>("text", ReadText, null); var text = element.GetOptionalPropertyCustom<TextObject?>("text", ReadText, null);
var type = element.GetOptionalProperty<string>("type", typeDefault); var type = element.GetOptionalProperty<string>("type", typeDefault);

View file

@ -8,42 +8,7 @@ namespace DotTiled.Serialization.Tmj;
internal partial class Tmj internal partial class Tmj
{ {
internal static Dictionary<string, IProperty> ReadProperties( internal static List<IProperty> ReadProperties(
JsonElement element,
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions) =>
element.GetValueAsList<IProperty>(e =>
{
var name = e.GetRequiredProperty<string>("name");
var type = e.GetOptionalPropertyParseable<PropertyType>("type", s => s switch
{
"string" => PropertyType.String,
"int" => PropertyType.Int,
"float" => PropertyType.Float,
"bool" => PropertyType.Bool,
"color" => PropertyType.Color,
"file" => PropertyType.File,
"object" => PropertyType.Object,
"class" => PropertyType.Class,
_ => throw new JsonException("Invalid property type")
}, PropertyType.String);
IProperty property = type switch
{
PropertyType.String => new StringProperty { Name = name, Value = e.GetRequiredProperty<string>("value") },
PropertyType.Int => new IntProperty { Name = name, Value = e.GetRequiredProperty<int>("value") },
PropertyType.Float => new FloatProperty { Name = name, Value = e.GetRequiredProperty<float>("value") },
PropertyType.Bool => new BoolProperty { Name = name, Value = e.GetRequiredProperty<bool>("value") },
PropertyType.Color => new ColorProperty { Name = name, Value = e.GetRequiredPropertyParseable<Color>("value") },
PropertyType.File => new FileProperty { Name = name, Value = e.GetRequiredProperty<string>("value") },
PropertyType.Object => new ObjectProperty { Name = name, Value = e.GetRequiredProperty<uint>("value") },
PropertyType.Class => ReadClassProperty(e, customTypeDefinitions),
_ => throw new JsonException("Invalid property type")
};
return property!;
}).ToDictionary(p => p.Name);
internal static List<IProperty> ReadPropertiesList(
JsonElement element, JsonElement element,
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions) => IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions) =>
element.GetValueAsList<IProperty>(e => element.GetValueAsList<IProperty>(e =>
@ -92,7 +57,7 @@ internal partial class Tmj
var propsInType = Helpers.CreateInstanceOfCustomClass(ccd); var propsInType = Helpers.CreateInstanceOfCustomClass(ccd);
var props = element.GetOptionalPropertyCustom<List<IProperty>>("value", el => ReadCustomClassProperties(el, ccd, customTypeDefinitions), []); var props = element.GetOptionalPropertyCustom<List<IProperty>>("value", el => ReadCustomClassProperties(el, ccd, customTypeDefinitions), []);
var mergedProps = Helpers.MergePropertiesList(propsInType, props); var mergedProps = Helpers.MergeProperties(propsInType, props);
return new ClassProperty return new ClassProperty
{ {

View file

@ -35,7 +35,7 @@ internal partial class Tmj
var opacity = element.GetOptionalProperty<float>("opacity", 1.0f); var opacity = element.GetOptionalProperty<float>("opacity", 1.0f);
var parallaxx = element.GetOptionalProperty<float>("parallaxx", 1.0f); var parallaxx = element.GetOptionalProperty<float>("parallaxx", 1.0f);
var parallaxy = element.GetOptionalProperty<float>("parallaxy", 1.0f); var parallaxy = element.GetOptionalProperty<float>("parallaxy", 1.0f);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null); var properties = element.GetOptionalPropertyCustom("properties", e => ReadProperties(e, customTypeDefinitions), []);
var repeatX = element.GetOptionalProperty<bool>("repeatx", false); var repeatX = element.GetOptionalProperty<bool>("repeatx", false);
var repeatY = element.GetOptionalProperty<bool>("repeaty", false); var repeatY = element.GetOptionalProperty<bool>("repeaty", false);
var startX = element.GetOptionalProperty<int>("startx", 0); var startX = element.GetOptionalProperty<int>("startx", 0);

View file

@ -44,7 +44,7 @@ internal partial class Tmj
"bottomright" => ObjectAlignment.BottomRight, "bottomright" => ObjectAlignment.BottomRight,
_ => throw new JsonException($"Unknown object alignment '{s}'") _ => throw new JsonException($"Unknown object alignment '{s}'")
}, ObjectAlignment.Unspecified); }, ObjectAlignment.Unspecified);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", el => ReadProperties(el, customTypeDefinitions), null); var properties = element.GetOptionalPropertyCustom("properties", el => ReadProperties(el, customTypeDefinitions), []);
var source = element.GetOptionalProperty<string?>("source", null); var source = element.GetOptionalProperty<string?>("source", null);
var spacing = element.GetOptionalProperty<uint?>("spacing", null); var spacing = element.GetOptionalProperty<uint?>("spacing", null);
var tileCount = element.GetOptionalProperty<uint?>("tilecount", null); var tileCount = element.GetOptionalProperty<uint?>("tilecount", null);
@ -176,7 +176,7 @@ internal partial class Tmj
var height = e.GetOptionalProperty<uint>("height", imageHeight ?? 0); var height = e.GetOptionalProperty<uint>("height", imageHeight ?? 0);
var objectGroup = e.GetOptionalPropertyCustom<ObjectLayer?>("objectgroup", e => ReadObjectLayer(e, externalTemplateResolver, customTypeDefinitions), null); var objectGroup = e.GetOptionalPropertyCustom<ObjectLayer?>("objectgroup", e => ReadObjectLayer(e, externalTemplateResolver, customTypeDefinitions), null);
var probability = e.GetOptionalProperty<float>("probability", 0.0f); var probability = e.GetOptionalProperty<float>("probability", 0.0f);
var properties = e.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", el => ReadProperties(el, customTypeDefinitions), null); var properties = e.GetOptionalPropertyCustom("properties", el => ReadProperties(el, customTypeDefinitions), []);
// var terrain, replaced by wangsets // var terrain, replaced by wangsets
var type = e.GetOptionalProperty<string>("type", ""); var type = e.GetOptionalProperty<string>("type", "");
@ -223,7 +223,7 @@ internal partial class Tmj
var @clalss = element.GetOptionalProperty<string>("class", ""); var @clalss = element.GetOptionalProperty<string>("class", "");
var colors = element.GetOptionalPropertyCustom<List<WangColor>>("colors", e => e.GetValueAsList<WangColor>(el => ReadWangColor(el, customTypeDefinitions)), []); var colors = element.GetOptionalPropertyCustom<List<WangColor>>("colors", e => e.GetValueAsList<WangColor>(el => ReadWangColor(el, customTypeDefinitions)), []);
var name = element.GetRequiredProperty<string>("name"); var name = element.GetRequiredProperty<string>("name");
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null); var properties = element.GetOptionalPropertyCustom("properties", e => ReadProperties(e, customTypeDefinitions), []);
var tile = element.GetOptionalProperty<int>("tile", 0); var tile = element.GetOptionalProperty<int>("tile", 0);
var type = element.GetOptionalProperty<string>("type", ""); var type = element.GetOptionalProperty<string>("type", "");
var wangTiles = element.GetOptionalPropertyCustom<List<WangTile>>("wangtiles", e => e.GetValueAsList<WangTile>(ReadWangTile), []); var wangTiles = element.GetOptionalPropertyCustom<List<WangTile>>("wangtiles", e => e.GetValueAsList<WangTile>(ReadWangTile), []);
@ -247,7 +247,7 @@ internal partial class Tmj
var color = element.GetRequiredPropertyParseable<Color>("color", s => Color.Parse(s, CultureInfo.InvariantCulture)); var color = element.GetRequiredPropertyParseable<Color>("color", s => Color.Parse(s, CultureInfo.InvariantCulture));
var name = element.GetRequiredProperty<string>("name"); var name = element.GetRequiredProperty<string>("name");
var probability = element.GetOptionalProperty<float>("probability", 1.0f); var probability = element.GetOptionalProperty<float>("probability", 1.0f);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", e => ReadProperties(e, customTypeDefinitions), null); var properties = element.GetOptionalPropertyCustom("properties", e => ReadProperties(e, customTypeDefinitions), []);
var tile = element.GetOptionalProperty<int>("tile", 0); var tile = element.GetOptionalProperty<int>("tile", 0);
return new WangColor return new WangColor

View file

@ -69,7 +69,7 @@ internal partial class Tmx
reader.ProcessChildren("map", (r, elementName) => elementName switch reader.ProcessChildren("map", (r, elementName) => elementName switch
{ {
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadPropertiesList(r, customTypeDefinitions), "Properties"), "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"),
"tileset" => () => tilesets.Add(ReadTileset(r, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions)), "tileset" => () => tilesets.Add(ReadTileset(r, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions)),
"layer" => () => layers.Add(ReadTileLayer(r, infinite, customTypeDefinitions)), "layer" => () => layers.Add(ReadTileLayer(r, infinite, customTypeDefinitions)),
"objectgroup" => () => layers.Add(ReadObjectLayer(r, externalTemplateResolver, customTypeDefinitions)), "objectgroup" => () => layers.Add(ReadObjectLayer(r, externalTemplateResolver, customTypeDefinitions)),

View file

@ -39,7 +39,7 @@ internal partial class Tmx
}) ?? DrawOrder.TopDown; }) ?? DrawOrder.TopDown;
// Elements // Elements
Dictionary<string, IProperty>? properties = null; List<IProperty>? properties = null;
List<Model.Object> objects = []; List<Model.Object> objects = [];
reader.ProcessChildren("objectgroup", (r, elementName) => elementName switch reader.ProcessChildren("objectgroup", (r, elementName) => elementName switch
@ -66,7 +66,7 @@ internal partial class Tmx
ParallaxX = parallaxX, ParallaxX = parallaxX,
ParallaxY = parallaxY, ParallaxY = parallaxY,
Color = color, Color = color,
Properties = properties, Properties = properties ?? [],
DrawOrder = drawOrder, DrawOrder = drawOrder,
Objects = objects Objects = objects
}; };
@ -93,7 +93,7 @@ internal partial class Tmx
float rotationDefault = obj?.Rotation ?? 0f; float rotationDefault = obj?.Rotation ?? 0f;
uint? gidDefault = obj is TileObject tileObj ? tileObj.GID : null; uint? gidDefault = obj is TileObject tileObj ? tileObj.GID : null;
bool visibleDefault = obj?.Visible ?? true; bool visibleDefault = obj?.Visible ?? true;
Dictionary<string, IProperty>? propertiesDefault = obj?.Properties ?? null; List<IProperty>? propertiesDefault = obj?.Properties ?? null;
var id = reader.GetOptionalAttributeParseable<uint>("id") ?? idDefault; var id = reader.GetOptionalAttributeParseable<uint>("id") ?? idDefault;
var name = reader.GetOptionalAttribute("name") ?? nameDefault; var name = reader.GetOptionalAttribute("name") ?? nameDefault;
@ -109,11 +109,11 @@ internal partial class Tmx
// Elements // Elements
Model.Object? foundObject = null; Model.Object? foundObject = null;
int propertiesCounter = 0; int propertiesCounter = 0;
Dictionary<string, IProperty>? properties = propertiesDefault; List<IProperty>? properties = propertiesDefault;
reader.ProcessChildren("object", (r, elementName) => elementName switch reader.ProcessChildren("object", (r, elementName) => elementName switch
{ {
"properties" => () => Helpers.SetAtMostOnceUsingCounter(ref properties, Helpers.MergeProperties(properties, ReadProperties(r, customTypeDefinitions)), "Properties", ref propertiesCounter), "properties" => () => Helpers.SetAtMostOnceUsingCounter(ref properties, Helpers.MergeProperties(properties, ReadProperties(r, customTypeDefinitions)).ToList(), "Properties", ref propertiesCounter),
"ellipse" => () => Helpers.SetAtMostOnce(ref foundObject, ReadEllipseObject(r), "Object marker"), "ellipse" => () => Helpers.SetAtMostOnce(ref foundObject, ReadEllipseObject(r), "Object marker"),
"point" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPointObject(r), "Object marker"), "point" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPointObject(r), "Object marker"),
"polygon" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPolygonObject(r), "Object marker"), "polygon" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPolygonObject(r), "Object marker"),
@ -139,7 +139,7 @@ internal partial class Tmx
foundObject.Height = height; foundObject.Height = height;
foundObject.Rotation = rotation; foundObject.Rotation = rotation;
foundObject.Visible = visible; foundObject.Visible = visible;
foundObject.Properties = properties; foundObject.Properties = properties ?? [];
foundObject.Template = template; foundObject.Template = template;
return OverrideObject(obj, foundObject); return OverrideObject(obj, foundObject);
@ -161,7 +161,7 @@ internal partial class Tmx
obj.Height = foundObject.Height; obj.Height = foundObject.Height;
obj.Rotation = foundObject.Rotation; obj.Rotation = foundObject.Rotation;
obj.Visible = foundObject.Visible; obj.Visible = foundObject.Visible;
obj.Properties = Helpers.MergeProperties(obj.Properties, foundObject.Properties); obj.Properties = Helpers.MergeProperties(obj.Properties, foundObject.Properties).ToList();
obj.Template = foundObject.Template; obj.Template = foundObject.Template;
return obj; return obj;
} }

View file

@ -7,43 +7,7 @@ namespace DotTiled.Serialization.Tmx;
internal partial class Tmx internal partial class Tmx
{ {
internal static Dictionary<string, IProperty> ReadProperties( internal static List<IProperty> ReadProperties(
XmlReader reader,
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
{
return reader.ReadList("properties", "property", (r) =>
{
var name = r.GetRequiredAttribute("name");
var type = r.GetOptionalAttributeEnum<PropertyType>("type", (s) => s switch
{
"string" => PropertyType.String,
"int" => PropertyType.Int,
"float" => PropertyType.Float,
"bool" => PropertyType.Bool,
"color" => PropertyType.Color,
"file" => PropertyType.File,
"object" => PropertyType.Object,
"class" => PropertyType.Class,
_ => throw new XmlException("Invalid property type")
}) ?? PropertyType.String;
IProperty property = type switch
{
PropertyType.String => new StringProperty { Name = name, Value = r.GetRequiredAttribute("value") },
PropertyType.Int => new IntProperty { Name = name, Value = r.GetRequiredAttributeParseable<int>("value") },
PropertyType.Float => new FloatProperty { Name = name, Value = r.GetRequiredAttributeParseable<float>("value") },
PropertyType.Bool => new BoolProperty { Name = name, Value = r.GetRequiredAttributeParseable<bool>("value") },
PropertyType.Color => new ColorProperty { Name = name, Value = r.GetRequiredAttributeParseable<Color>("value") },
PropertyType.File => new FileProperty { Name = name, Value = r.GetRequiredAttribute("value") },
PropertyType.Object => new ObjectProperty { Name = name, Value = r.GetRequiredAttributeParseable<uint>("value") },
PropertyType.Class => ReadClassProperty(r, customTypeDefinitions),
_ => throw new XmlException("Invalid property type")
};
return (name, property);
}).ToDictionary(x => x.name, x => x.property);
}
internal static List<IProperty> ReadPropertiesList(
XmlReader reader, XmlReader reader,
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions) IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
{ {
@ -91,8 +55,8 @@ internal partial class Tmx
{ {
reader.ReadStartElement("property"); reader.ReadStartElement("property");
var propsInType = Helpers.CreateInstanceOfCustomClass(ccd); var propsInType = Helpers.CreateInstanceOfCustomClass(ccd);
var props = ReadPropertiesList(reader, customTypeDefinitions); var props = ReadProperties(reader, customTypeDefinitions);
var mergedProps = Helpers.MergePropertiesList(propsInType, props); var mergedProps = Helpers.MergeProperties(propsInType, props);
reader.ReadEndElement(); reader.ReadEndElement();
return new ClassProperty { Name = name, PropertyType = propertyType, Value = mergedProps }; return new ClassProperty { Name = name, PropertyType = propertyType, Value = mergedProps };

View file

@ -28,7 +28,7 @@ internal partial class Tmx
var parallaxX = reader.GetOptionalAttributeParseable<float>("parallaxx") ?? 1.0f; var parallaxX = reader.GetOptionalAttributeParseable<float>("parallaxx") ?? 1.0f;
var parallaxY = reader.GetOptionalAttributeParseable<float>("parallaxy") ?? 1.0f; var parallaxY = reader.GetOptionalAttributeParseable<float>("parallaxy") ?? 1.0f;
Dictionary<string, IProperty>? properties = null; List<IProperty>? properties = null;
Data? data = null; Data? data = null;
reader.ProcessChildren("layer", (r, elementName) => elementName switch reader.ProcessChildren("layer", (r, elementName) => elementName switch
@ -55,7 +55,7 @@ internal partial class Tmx
ParallaxX = parallaxX, ParallaxX = parallaxX,
ParallaxY = parallaxY, ParallaxY = parallaxY,
Data = data, Data = data,
Properties = properties Properties = properties ?? []
}; };
} }
@ -78,7 +78,7 @@ internal partial class Tmx
var repeatX = (reader.GetOptionalAttributeParseable<uint>("repeatx") ?? 0) == 1; var repeatX = (reader.GetOptionalAttributeParseable<uint>("repeatx") ?? 0) == 1;
var repeatY = (reader.GetOptionalAttributeParseable<uint>("repeaty") ?? 0) == 1; var repeatY = (reader.GetOptionalAttributeParseable<uint>("repeaty") ?? 0) == 1;
Dictionary<string, IProperty>? properties = null; List<IProperty>? properties = null;
Image? image = null; Image? image = null;
reader.ProcessChildren("imagelayer", (r, elementName) => elementName switch reader.ProcessChildren("imagelayer", (r, elementName) => elementName switch
@ -102,7 +102,7 @@ internal partial class Tmx
OffsetY = offsetY, OffsetY = offsetY,
ParallaxX = parallaxX, ParallaxX = parallaxX,
ParallaxY = parallaxY, ParallaxY = parallaxY,
Properties = properties, Properties = properties ?? [],
Image = image, Image = image,
RepeatX = repeatX, RepeatX = repeatX,
RepeatY = repeatY RepeatY = repeatY
@ -125,7 +125,7 @@ internal partial class Tmx
var parallaxX = reader.GetOptionalAttributeParseable<float>("parallaxx") ?? 1.0f; var parallaxX = reader.GetOptionalAttributeParseable<float>("parallaxx") ?? 1.0f;
var parallaxY = reader.GetOptionalAttributeParseable<float>("parallaxy") ?? 1.0f; var parallaxY = reader.GetOptionalAttributeParseable<float>("parallaxy") ?? 1.0f;
Dictionary<string, IProperty>? properties = null; List<IProperty>? properties = null;
List<BaseLayer> layers = []; List<BaseLayer> layers = [];
reader.ProcessChildren("group", (r, elementName) => elementName switch reader.ProcessChildren("group", (r, elementName) => elementName switch
@ -150,7 +150,7 @@ internal partial class Tmx
OffsetY = offsetY, OffsetY = offsetY,
ParallaxX = parallaxX, ParallaxX = parallaxX,
ParallaxY = parallaxY, ParallaxY = parallaxY,
Properties = properties, Properties = properties ?? [],
Layers = layers Layers = layers
}; };
} }

View file

@ -60,7 +60,7 @@ internal partial class Tmx
Image? image = null; Image? image = null;
TileOffset? tileOffset = null; TileOffset? tileOffset = null;
Grid? grid = null; Grid? grid = null;
Dictionary<string, IProperty>? properties = null; List<IProperty>? properties = null;
List<Wangset>? wangsets = null; List<Wangset>? wangsets = null;
Transformations? transformations = null; Transformations? transformations = null;
List<Tile> tiles = []; List<Tile> tiles = [];
@ -109,7 +109,7 @@ internal partial class Tmx
Image = image, Image = image,
TileOffset = tileOffset, TileOffset = tileOffset,
Grid = grid, Grid = grid,
Properties = properties, Properties = properties ?? [],
Wangsets = wangsets, Wangsets = wangsets,
Transformations = transformations, Transformations = transformations,
Tiles = tiles Tiles = tiles
@ -219,7 +219,7 @@ internal partial class Tmx
var height = reader.GetOptionalAttributeParseable<uint>("height"); var height = reader.GetOptionalAttributeParseable<uint>("height");
// Elements // Elements
Dictionary<string, IProperty>? properties = null; List<IProperty>? properties = null;
Image? image = null; Image? image = null;
ObjectLayer? objectLayer = null; ObjectLayer? objectLayer = null;
List<Frame>? animation = null; List<Frame>? animation = null;
@ -247,7 +247,7 @@ internal partial class Tmx
Y = y, Y = y,
Width = width ?? image?.Width ?? 0, Width = width ?? image?.Width ?? 0,
Height = height ?? image?.Height ?? 0, Height = height ?? image?.Height ?? 0,
Properties = properties, Properties = properties ?? [],
Image = image, Image = image,
ObjectLayer = objectLayer, ObjectLayer = objectLayer,
Animation = animation Animation = animation
@ -269,7 +269,7 @@ internal partial class Tmx
var tile = reader.GetRequiredAttributeParseable<int>("tile"); var tile = reader.GetRequiredAttributeParseable<int>("tile");
// Elements // Elements
Dictionary<string, IProperty>? properties = null; List<IProperty>? properties = null;
List<WangColor> wangColors = []; List<WangColor> wangColors = [];
List<WangTile> wangTiles = []; List<WangTile> wangTiles = [];
@ -289,7 +289,7 @@ internal partial class Tmx
Name = name, Name = name,
Class = @class, Class = @class,
Tile = tile, Tile = tile,
Properties = properties, Properties = properties ?? [],
WangColors = wangColors, WangColors = wangColors,
WangTiles = wangTiles WangTiles = wangTiles
}; };
@ -307,7 +307,7 @@ internal partial class Tmx
var probability = reader.GetOptionalAttributeParseable<float>("probability") ?? 0f; var probability = reader.GetOptionalAttributeParseable<float>("probability") ?? 0f;
// Elements // Elements
Dictionary<string, IProperty>? properties = null; List<IProperty>? properties = null;
reader.ProcessChildren("wangcolor", (r, elementName) => elementName switch reader.ProcessChildren("wangcolor", (r, elementName) => elementName switch
{ {
@ -322,7 +322,7 @@ internal partial class Tmx
Color = color, Color = color,
Tile = tile, Tile = tile,
Probability = probability, Probability = probability,
Properties = properties Properties = properties ?? []
}; };
} }