Merge pull request #20 from dcronqvist/dev

v0.1.0 - initial release
This commit is contained in:
dcronqvist 2024-08-30 20:29:17 +02:00 committed by GitHub
commit e00f48fab8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
165 changed files with 5089 additions and 2675 deletions

View file

@ -1,11 +1,247 @@
root = true
[*.cs]
#### Core EditorConfig Options ####
charset = utf-8
end_of_line = lf
# Indentation and spacing
indent_size = 2
indent_style = space
# New line preferences
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
#### .NET Coding Conventions ####
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = true
file_header_template = unset
# this. and Me. preferences
dotnet_style_qualification_for_event = false
dotnet_style_qualification_for_field = false
dotnet_style_qualification_for_method = false
dotnet_style_qualification_for_property = false
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true
dotnet_style_predefined_type_for_member_access = true
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members
# Expression-level preferences
dotnet_style_coalesce_expression = true
dotnet_style_collection_initializer = false
dotnet_style_explicit_tuple_names = true
dotnet_style_namespace_match_folder = false
dotnet_style_null_propagation = true
dotnet_style_object_initializer = true
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true
dotnet_style_prefer_collection_expression = false
dotnet_style_prefer_compound_assignment = true
dotnet_style_prefer_conditional_expression_over_assignment = false
dotnet_style_prefer_conditional_expression_over_return = false
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
dotnet_style_prefer_inferred_anonymous_type_member_names = true
dotnet_style_prefer_inferred_tuple_names = true
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
dotnet_style_prefer_simplified_boolean_expressions = true
dotnet_style_prefer_simplified_interpolation = true
# Field preferences
dotnet_style_readonly_field = true
# Parameter preferences
dotnet_code_quality_unused_parameters = all:silent
# Suppression preferences
dotnet_remove_unnecessary_suppression_exclusions = none
# New line preferences
dotnet_style_allow_multiple_blank_lines_experimental = true
dotnet_style_allow_statement_immediately_after_block_experimental = true
#### C# Coding Conventions ####
# var preferences
csharp_style_var_elsewhere = false
csharp_style_var_for_built_in_types = false
csharp_style_var_when_type_is_apparent = false
# Expression-bodied members
csharp_style_expression_bodied_accessors = true
csharp_style_expression_bodied_constructors = false
csharp_style_expression_bodied_indexers = true
csharp_style_expression_bodied_lambdas = true
csharp_style_expression_bodied_local_functions = false
csharp_style_expression_bodied_methods = when_on_single_line
csharp_style_expression_bodied_operators = false
csharp_style_expression_bodied_properties = true
# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true
csharp_style_pattern_matching_over_is_with_cast_check = true
csharp_style_prefer_extended_property_pattern = true
csharp_style_prefer_not_pattern = true
csharp_style_prefer_pattern_matching = true
csharp_style_prefer_switch_expression = true
# Null-checking preferences
csharp_style_conditional_delegate_call = true
# Modifier preferences
csharp_prefer_static_local_function = true
csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
csharp_style_prefer_readonly_struct = true
csharp_style_prefer_readonly_struct_member = true
# Code-block preferences
csharp_prefer_braces = when_multiline
csharp_prefer_simple_using_statement = true
csharp_style_namespace_declarations = block_scoped
csharp_style_prefer_method_group_conversion = true
csharp_style_prefer_primary_constructors = false
csharp_style_prefer_top_level_statements = true
# Expression-level preferences
csharp_prefer_simple_default_expression = true
csharp_style_deconstructed_variable_declaration = true
csharp_style_implicit_object_creation_when_type_is_apparent = false
csharp_style_inlined_variable_declaration = true
csharp_style_prefer_index_operator = true
csharp_style_prefer_local_over_anonymous_function = true
csharp_style_prefer_null_check_over_type_check = true
csharp_style_prefer_range_operator = true
csharp_style_prefer_tuple_swap = true
csharp_style_prefer_utf8_string_literals = true
csharp_style_throw_expression = true
csharp_style_unused_value_assignment_preference = discard_variable
csharp_style_unused_value_expression_statement_preference = discard_variable
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace
# New line preferences
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
csharp_style_allow_embedded_statements_on_same_line_experimental = true
#### C# Formatting Rules ####
# New line preferences
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = false
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Wrapping preferences
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
# Diagnostics
dotnet_analyzer_diagnostic.severity = warning
dotnet_diagnostic.IDE0001.severity = none
dotnet_diagnostic.IDE0004.severity = silent
dotnet_diagnostic.IDE0005.severity = error
dotnet_diagnostic.IDE0008.severity = silent
dotnet_diagnostic.IDE0055.severity = silent
dotnet_diagnostic.IDE0160.severity = none
dotnet_diagnostic.CA1707.severity = silent
dotnet_diagnostic.CA1852.severity = none
dotnet_diagnostic.CA1805.severity = none
dotnet_diagnostic.CA1720.severity = silent
dotnet_diagnostic.CA1711.severity = silent
dotnet_diagnostic.CA1716.severity = silent
[.github/**/*.yml]
charset = utf-8

37
.github/workflows/master-pr.yml vendored Normal file
View file

@ -0,0 +1,37 @@
on:
pull_request:
branches:
- master
jobs:
check-pr-version:
runs-on: ubuntu-latest
steps:
- name: Checkout PR branch
uses: actions/checkout@v3
- name: Get version from PR branch
id: pr_version
run: |
PR_VERSION=$(grep '<Version>' **/*.csproj | sed -E 's/.*<Version>(.*)<\/Version>.*/\1/')
echo "PR_VERSION=$PR_VERSION" >> $GITHUB_ENV
- name: Checkout master branch
run: |
git fetch origin master
git checkout origin/master
- name: Get version from master branch
id: master_version
run: |
MASTER_VERSION=$(grep '<Version>' **/*.csproj | sed -E 's/.*<Version>(.*)<\/Version>.*/\1/')
echo "MASTER_VERSION=$MASTER_VERSION" >> $GITHUB_ENV
- name: Compare versions
run: |
if [ "$(printf '%s\n' "$PR_VERSION" "$MASTER_VERSION" | sort -V | head -n1)" = "$PR_VERSION" ] && [ "$PR_VERSION" != "$MASTER_VERSION" ]; then
echo "Version in PR is not higher than master."
exit 1
else
echo "Version in PR is higher than master."
fi

View file

@ -5,7 +5,7 @@ on:
- dev
jobs:
tests:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@ -19,4 +19,7 @@ jobs:
run: dotnet build --no-restore src/DotTiled.sln
- name: Test
run: dotnet test --no-build --verbosity normal src/DotTiled.sln
- name: Lint style
run: dotnet format style --verify-no-changes --verbosity diagnostic src/DotTiled.sln
- name: Lint analyzers
run: dotnet format analyzers --verify-no-changes --verbosity diagnostic src/DotTiled.sln

30
.github/workflows/release-nuget.yml vendored Normal file
View file

@ -0,0 +1,30 @@
on:
release:
types: [published]
jobs:
release-nuget:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore src/DotTiled.sln
- name: Build
run: dotnet build --no-restore src/DotTiled.sln
- name: Test
run: dotnet test --no-build --verbosity normal src/DotTiled.sln
- name: Lint style
run: dotnet format style --verify-no-changes --verbosity diagnostic src/DotTiled.sln
- name: Lint analyzers
run: dotnet format analyzers --verify-no-changes --verbosity diagnostic src/DotTiled.sln
- name: Pack
run: make pack
- name: Publish to NuGet.org
run: |
dotnet nuget push ./nupkg/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json --skip-duplicate
dotnet nuget push ./nupkg/*.snupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json --skip-duplicate

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Daniel Cronqvist (daniel@dcronqvist.se)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,17 +1,25 @@
test:
dotnet build src/DotTiled.sln
dotnet test src/DotTiled.sln
docs-serve: docs-build
docs-serve:
docfx docs/docfx.json --serve
docs-build:
cp README.md docs/index.md
docfx docs/docfx.json
BENCHMARK_SOURCES = DotTiled.Benchmark/Program.cs DotTiled.Benchmark/DotTiled.Benchmark.csproj
BENCHMARK_OUTPUTDIR = DotTiled.Benchmark/BenchmarkDotNet.Artifacts
lint:
dotnet format style --verify-no-changes src/DotTiled.sln
dotnet format analyzers --verify-no-changes src/DotTiled.sln
pack:
dotnet pack src/DotTiled/DotTiled.csproj -c Release -o ./nupkg -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg
BENCHMARK_SOURCES = src/DotTiled.Benchmark/Program.cs src/DotTiled.Benchmark/DotTiled.Benchmark.csproj
BENCHMARK_OUTPUTDIR = src/DotTiled.Benchmark/BenchmarkDotNet.Artifacts
.PHONY: benchmark
benchmark: $(BENCHMARK_OUTPUTDIR)/results/MyBenchmarks.MapLoading-report-github.md
$(BENCHMARK_OUTPUTDIR)/results/MyBenchmarks.MapLoading-report-github.md: $(BENCHMARK_SOURCES)
dotnet run --project DotTiled.Benchmark/DotTiled.Benchmark.csproj -c Release -- $(BENCHMARK_OUTPUTDIR)
dotnet run --project src/DotTiled.Benchmark/DotTiled.Benchmark.csproj -c Release -- $(BENCHMARK_OUTPUTDIR)

View file

@ -4,11 +4,11 @@
DotTiled is a simple and easy-to-use library for loading, saving, and managing [Tiled maps and tilesets](https://mapeditor.org) in your .NET projects. After [TiledCS](https://github.com/TheBoneJarmer/TiledCS) unfortunately became unmaintained (since 2022), I aimed to create a new library that could fill its shoes. DotTiled is the result of that effort.
DotTiled is designed to be a lightweight and efficient library that provides a simple API for loading and managing Tiled maps and tilesets. It is built with performance in mind and aims to be as fast and memory-efficient as possible. Targeting `netstandard2.0` and `net8.0` allows DotTiled to be used in popular game engines like Unity and Godot, as well as in popular game development frameworks like MonoGame.
DotTiled is designed to be a lightweight and efficient library that provides a simple API for loading and managing Tiled maps and tilesets. It is built with performance in mind and aims to be as fast and memory-efficient as possible.
- [Alternative libraries and comparison + benchmarks](#alternative-libraries-and-comparison)
- [Feature coverage comparison](#feature-coverage-comparison)
- [Installing DotTiled](#installing-dottiled)
- [Quick Start](#quick-start)
# Alternative libraries and comparison
@ -20,7 +20,7 @@ Other similar libraries exist, and you may want to consider them for your projec
| Benchmark (time)* | 1.00 | 1.83 | 2.16 | - | - | - |
| Benchmark (memory)* | 1.00 | 1.43 | 2.03 | - | - | - |
| .NET Targets | `net8.0` |`net6.0`<br>`net7.0`|`netstandard2.1`|`netstandard2.0`|`netstandard2.0`|`net45`|
| Docs |Usage,<br>XML Docs|Usage|Usage, API,<br>XML Docs|Usage, API|Usage, XML Docs|Usage, XML Docs|
| Docs |Usage, API,<br>XML Docs|Usage|Usage, API,<br>XML Docs|Usage, API|Usage, XML Docs|Usage, XML Docs|
| License | MIT | MIT | MIT | Apache-2.0 | MIT | BSD 3-Clause |
> [!NOTE]
@ -73,10 +73,12 @@ Below is a comparison of the feature coverage of DotTiled and other similar libr
> [!NOTE]
> ✅ Full support. ⚠️ Partial support, see respective library for details about supported features. ❌ No support.
# Installing DotTiled
# Quick Start
DotTiled is available as a NuGet package. You can install it by using the NuGet Package Manager UI in Visual Studio, or equivalent, or using the following command for the .NET CLI:
```pwsh
dotnet add package DotTiled
```
Then head to the detailed [documentation](https://dcronqvist.github.io/DotTiled/docs/quickstart.html) for more information on how to use DotTiled in your project.

View file

@ -9,7 +9,8 @@
]
}
],
"dest": "api"
"dest": "api",
"enumSortOrder": "declaringOrder"
}
],
"build": {

View file

@ -0,0 +1,161 @@
# Custom properties
[Tiled facilitates a very flexible way to store custom data in your maps using properties](https://doc.mapeditor.org/en/stable/manual/custom-properties/#custom-properties). Accessing these properties is a common task when working with Tiled maps in your game since it will allow you to fully utilize the strengths of Tiled, such as customizing the behavior of your game objects or setting up the initial state of your game world.
## All classes that can contain properties
All classes that can contain custom properties implement the interface <xref:DotTiled.IHasProperties> in some way. Below is an exhaustive list of all classes that can contain custom properties:
- <xref:DotTiled.BaseLayer>
- <xref:DotTiled.TileLayer>
- <xref:DotTiled.ObjectLayer>
- <xref:DotTiled.ImageLayer>
- <xref:DotTiled.Group>
- <xref:DotTiled.ClassProperty> (allows for recursive property objects)
- <xref:DotTiled.CustomClassDefinition> (used to define custom Tiled property types)
- <xref:DotTiled.Object>
- <xref:DotTiled.EllipseObject>
- <xref:DotTiled.PointObject>
- <xref:DotTiled.PolygonObject>
- <xref:DotTiled.PolylineObject>
- <xref:DotTiled.RectangleObject>
- <xref:DotTiled.TextObject>
- <xref:DotTiled.TileObject>
- <xref:DotTiled.Tileset>
- <xref:DotTiled.Tile>
- <xref:DotTiled.WangTile>
- <xref:DotTiled.WangColor>
## How to access properties
To access the properties on one of the classes listed above, you will make use of the <xref:DotTiled.IHasProperties> interface.
In situations where you know that a property must exist, and you simply want to retrieve it, you can use the <xref:DotTiled.IHasProperties.GetProperty``1(System.String)> method like so:
```csharp
var map = LoadMap();
var propertyValue = map.GetProperty<BoolProperty>("boolPropertyInMap").Value;
```
If you are unsure whether a property exists, or you want to provide some kind of default behaviour if the property is not present, you can instead use the <xref:DotTiled.IHasProperties.TryGetProperty``1(System.String,``0@)> method like so:
```csharp
var map = LoadMap();
if (map.TryGetProperty<BoolProperty>("boolPropertyInMap", out var property))
{
// Do something with existing property
var propertyValue = property.Value;
}
else
{
// Do something if property does not exist
}
```
For both methods, you can replace `BoolProperty` with any of the property types that Tiled supports. You can find a list of all property types and their corresponding classes in the [next section](#all-types-of-properties).
## All types of properties
Tiled supports a variety of property types, which are represented in the DotTiled library as classes that implement the <xref:DotTiled.IProperty`1> interface. Below is a list of all property types that Tiled supports and their corresponding classes in DotTiled:
- `bool` - <xref:DotTiled.BoolProperty>
- `color` - <xref:DotTiled.ColorProperty>
- `float` - <xref:DotTiled.FloatProperty>
- `file` - <xref:DotTiled.FileProperty>
- `int` - <xref:DotTiled.IntProperty>
- `object` - <xref:DotTiled.ObjectProperty>
- `string` - <xref:DotTiled.StringProperty>
In addition to these primitive property types, [Tiled also supports more complex property types](https://doc.mapeditor.org/en/stable/manual/custom-properties/#custom-types). These custom property types are defined in Tiled according to the linked documentation, and to work with them in DotTiled, you *must* define their equivalences as a <xref:DotTiled.ICustomTypeDefinition>. You must then provide a resolving function to a defined type given a custom type name, as it is defined in Tiled.
## Custom types
Tiled allows you to define custom property types that can be used in your maps. These custom property types can be of type `class` or `enum`. DotTiled supports custom property types by allowing you to define the equivalent in C# and then providing a custom type resolver function that will return the equivalent definition given a custom type name.
### Class properties
Whenever DotTiled encounters a property that is of type `class` in a Tiled file, it will use the supplied custom type resolver function to retrieve the custom type definition. It will then use that definition to know the default values of the properties of that class, and then override those defaults with the values found in the Tiled file when populating a <xref:DotTiled.ClassProperty> instance. `class` properties allow you to create hierarchical structures of properties.
For example, if you have a `class` property in Tiled that looks like this:
![MonsterSpawner class in Tiled UI](../../images/monster-spawner-class.png)
The equivalent definition in DotTiled would look like the following:
```csharp
var monsterSpawnerDefinition = new CustomClassDefinition
{
Name = "MonsterSpawner",
UseAs = CustomClassUseAs.All, // Not really validated by DotTiled
Members = [ // Make sure that the default values match the Tiled UI
new BoolProperty { Name = "enabled", Value = true },
new IntProperty { Name = "maxSpawnAmount", Value = 10 },
new IntProperty { Name = "minSpawnAmount", Value = 0 },
new StringProperty { Name = "monsterNames", Value = "" }
]
};
```
### Enum properties
Tiled also allows you to define custom property types that work as enums. Similarly to `class` properties, you must define the equivalent in DotTiled as a <xref:DotTiled.CustomEnumDefinition>. You can then return the corresponding definition in the resolving function.
For example, if you have a custom property type in Tiled that looks like this:
![EntityType enum in Tiled UI](../../images/entity-type-enum.png)
The equivalent definition in DotTiled would look like the following:
```csharp
var entityTypeDefinition = new CustomEnumDefinition
{
Name = "EntityType",
StorageType = CustomEnumStorageType.String,
ValueAsFlags = false,
Values = [
"Bomb",
"Chest",
"Flower",
"Chair"
]
};
```
### [Future] Automatically map custom property `class` types to C# classes
In the future, DotTiled will support automatically mapping custom property `class` types to C# classes. This will allow you to define a C# class that matches the structure of the `class` property in Tiled, and DotTiled will automatically map the properties of the `class` property to the properties of the C# class. This will make working with `class` properties much easier and more intuitive.
The idea is to expand on the <xref:DotTiled.IHasProperties> interface with a method like `GetMappedProperty<T>(string propertyName)`, where `T` is a class that matches the structure of the `class` property in Tiled.
This functionality would be accompanied by a way to automatically create a matching <xref:DotTiled.ICustomTypeDefinition> given a C# class or enum. Something like this would then be possible:
```csharp
class MonsterSpawner
{
public bool Enabled { get; set; } = true;
public int MaxSpawnAmount { get; set; } = 10;
public int MinSpawnAmount { get; set; } = 0;
public string MonsterNames { get; set; } = "";
}
enum EntityType
{
Bomb,
Chest,
Flower,
Chair
}
var monsterSpawnerDefinition = CustomClassDefinition.FromClass<MonsterSpawner>();
var entityTypeDefinition = CustomEnumDefinition.FromEnum<EntityType>();
// ...
var map = LoadMap();
var monsterSpawner = map.GetMappedProperty<MonsterSpawner>("monsterSpawnerPropertyInMap");
var entityType = map.GetMappedProperty<EntityType>("entityTypePropertyInMap");
```
Finally, it might be possible to also make some kind of exporting functionality for <xref:DotTiled.ICustomTypeDefinition>. Given a collection of custom type definitions, DotTiled could generate a corresponding `propertytypes.json` file that you then can import into Tiled. This would make it so that you only have to define your custom property types once (in C#) and then import them into Tiled to use them in your maps.
Depending on implementation this might become something that can inhibit native AOT compilation due to potential reflection usage. Source generators could be used to mitigate this, but it is not yet clear how this will be implemented.

View file

@ -0,0 +1,86 @@
# Loading maps
Loading maps with DotTiled is straightforward and easy. The <xref:DotTiled.Map> class is a representation of a Tiled map, mimicking the structure of a Tiled map file. Map files can either be in the [`.tmx`/XML](https://doc.mapeditor.org/en/stable/reference/tmx-map-format/) or [`.tmj`/json](https://doc.mapeditor.org/en/stable/reference/json-map-format/) format. DotTiled supports **both** formats fully.
> [!NOTE]
> Using the `.tmj` file format will result in <xref:DotTiled.ImageLayer.Image> not having the same amount of information as for the `.tmx` format. This is due to the fact that the `.tmj` format does not include the full information that the `.tmx` format does. This is not a problem with DotTiled, but rather a limitation of the `.tmj` format.
## External resolution
Tiled maps may consist of several external files, such as tilesets or object templates. In Tiled map files, they are typically referenced by their path relative to the map file. It would be annoying to have to first load all these external resources before loading a map (which is how some other similar libraries work), so loading a map with DotTiled is designed in a way that you only have to provide a function that resolves these external resources. This way, DotTiled will figure out which external resources are needed and will invoke the corresponding resolver function to load them.
Loading a map, tileset, or template will require you to specify **three** resolver functions. We'll go through each of them below.
### `Func<string, Tileset>` - Tileset resolver
This function is used to resolve external tilesets by their source path. The function should return a <xref:DotTiled.Tileset> instance given the source path of the tileset. If you just want to load tilesets from the file system, you can use something like this:
```csharp
Tileset ResolveTileset(string source)
{
using var tilesetFileReader = new StreamReader(source);
var tilesetString = tilesetReader.ReadToEnd();
using var tilesetReader = new TilesetReader(tilesetString, ResolveTileset, ResolveTemplate, ResolveCustomType);
return tilesetReader.ReadTileset();
}
```
But, DotTiled is designed this way so you can retrieve your external resources from anywhere, such as a database or a custom file format, by implementing your own resolver function however you like. If you have some other means of accessing resources, you can use that instead of the file system.
```csharp
Tileset ResolveTileset(string source)
{
var tilesetString = ContentManager.GetString($"tilesets/{source}");
using var tilesetReader = new TilesetReader(tilesetString, ResolveTileset, ResolveTemplate, ResolveCustomType);
return tilesetReader.ReadTileset();
}
```
### `Func<string, Template>` - Template resolver
This function is used to resolve external object templates by their source path. The function should return a <xref:DotTiled.Template> instance given the source path of the template. If you just want to load templates from the file system, you can use something very similar to the tileset resolver by replacing <xref:DotTiled.Serialization.TilesetReader> with <xref:DotTiled.Serialization.TemplateReader>.
### `Func<string, CustomType>` - Custom type resolver
This function is used to resolve custom types that are defined in Tiled maps. Please refer to the [custom properties](custom-properties.md) documentation for more information on custom types. The function should return a <xref:DotTiled.ICustomTypeDefinition> instance given the custom type's name.
## Putting it all together
The following classes are the readers that you will need to use to read the map, tileset, and template: <xref:DotTiled.Serialization.MapReader>, <xref:DotTiled.Serialization.TilesetReader>, and <xref:DotTiled.Serialization.TemplateReader>.
Here is an example of how you can load a map with DotTiled:
```csharp
string mapPath = "path/to/map.tmx";
string mapDirectory = Path.GetDirectoryName(mapPath);
Tileset ResolveTileset(string source)
{
string tilesetPath = Path.Combine(mapDirectory, source);
using var tilesetFileReader = new StreamReader(tilesetPath);
var tilesetString = tilesetReader.ReadToEnd();
using var tilesetReader = new TilesetReader(tilesetString, ResolveTileset, ResolveTemplate, ResolveCustomType);
return tilesetReader.ReadTileset();
}
Template ResolveTemplate(string source)
{
string templatePath = Path.Combine(mapDirectory, source);
using var templateFileReader = new StreamReader(templatePath);
var templateString = templateReader.ReadToEnd();
using var templateReader = new TemplateReader(templateString, ResolveTileset, ResolveTemplate, ResolveCustomType);
return templateReader.ReadTemplate();
}
ICustomTypeDefinition ResolveCustomType(string name)
{
var allDefinedTypes = [ ... ];
return allDefinedTypes.FirstOrDefault(type => type.Name == name);
}
using var mapFileReader = new StreamReader(mapPath);
var mapString = mapFileReader.ReadToEnd();
using var mapReader = new MapReader(mapString, ResolveTileset, ResolveTemplate, ResolveCustomType);
var map = mapReader.ReadMap();
```

View file

@ -1 +0,0 @@
# Loading a map

View file

@ -1 +1,46 @@
# Quick Start
# Quick Start
Install DotTiled from NuGet:
```bash
dotnet add package DotTiled
```
Load a map from file system:
```csharp
string mapPath = "path/to/map.tmx";
string mapDirectory = Path.GetDirectoryName(mapPath);
Tileset ResolveTileset(string source)
{
string tilesetPath = Path.Combine(mapDirectory, source);
using var tilesetFileReader = new StreamReader(tilesetPath);
var tilesetString = tilesetReader.ReadToEnd();
using var tilesetReader = new TilesetReader(tilesetString, ResolveTileset, ResolveTemplate, ResolveCustomType);
return tilesetReader.ReadTileset();
}
Template ResolveTemplate(string source)
{
string templatePath = Path.Combine(mapDirectory, source);
using var templateFileReader = new StreamReader(templatePath);
var templateString = templateReader.ReadToEnd();
using var templateReader = new TemplateReader(templateString, ResolveTileset, ResolveTemplate, ResolveCustomType);
return templateReader.ReadTemplate();
}
ICustomTypeDefinition ResolveCustomType(string name)
{
var allDefinedTypes = [ ... ];
return allDefinedTypes.FirstOrDefault(type => type.Name == name);
}
using var mapFileReader = new StreamReader(mapPath);
var mapString = mapFileReader.ReadToEnd();
using var mapReader = new MapReader(mapString, ResolveTileset, ResolveTemplate, ResolveCustomType);
var map = mapReader.ReadMap();
```
If the above looks intimidating, don't worry! DotTiled is designed to be flexible and allow you to load maps from any source, such as a database or a custom file format. The above example is just one way to load a map from a file system. Please look at [Loading Maps](essentials/loading-maps.md) for more information on how to load maps from different sources.

View file

@ -3,4 +3,5 @@
- href: quickstart.md
- name: Essentials
- href: loading-a-map.md
- href: essentials/loading-maps.md
- href: essentials/custom-properties.md

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -1,17 +1,13 @@
using System;
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Order;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;
namespace MyBenchmarks
namespace DotTiled.Benchmark
{
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[CategoriesColumn]
@ -19,11 +15,11 @@ namespace MyBenchmarks
[HideColumns(["StdDev", "Error", "RatioSD"])]
public class MapLoading
{
private string _tmxPath = @"DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.tmx";
private string _tmxContents = "";
private readonly string _tmxPath = @"DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.tmx";
private readonly string _tmxContents = "";
private string _tmjPath = @"DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.tmj";
private string _tmjContents = "";
private readonly string _tmjPath = @"DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.tmj";
private readonly string _tmjContents = "";
public MapLoading()
{
@ -31,27 +27,27 @@ namespace MyBenchmarks
var tmxPath = Path.Combine(basePath, $"../{_tmxPath}");
var tmjPath = Path.Combine(basePath, $"../{_tmjPath}");
_tmxContents = System.IO.File.ReadAllText(tmxPath);
_tmjContents = System.IO.File.ReadAllText(tmjPath);
_tmxContents = File.ReadAllText(tmxPath);
_tmjContents = File.ReadAllText(tmjPath);
}
static string WhereAmI([CallerFilePath] string callerFilePath = "") => callerFilePath;
private static string WhereAmI([CallerFilePath] string callerFilePath = "") => callerFilePath;
[BenchmarkCategory("MapFromInMemoryTmxString")]
[Benchmark(Baseline = true, Description = "DotTiled")]
public DotTiled.Model.Map LoadWithDotTiledFromInMemoryTmxString()
public DotTiled.Map LoadWithDotTiledFromInMemoryTmxString()
{
using var stringReader = new StringReader(_tmxContents);
using var xmlReader = XmlReader.Create(stringReader);
using var mapReader = new DotTiled.Serialization.Tmx.TmxMapReader(xmlReader, _ => throw new Exception(), _ => throw new Exception(), []);
using var mapReader = new DotTiled.Serialization.Tmx.TmxMapReader(xmlReader, _ => throw new NotSupportedException(), _ => throw new NotSupportedException(), _ => throw new NotSupportedException());
return mapReader.ReadMap();
}
[BenchmarkCategory("MapFromInMemoryTmjString")]
[Benchmark(Baseline = true, Description = "DotTiled")]
public DotTiled.Model.Map LoadWithDotTiledFromInMemoryTmjString()
public DotTiled.Map LoadWithDotTiledFromInMemoryTmjString()
{
using var mapReader = new DotTiled.Serialization.Tmj.TmjMapReader(_tmjContents, _ => throw new Exception(), _ => throw new Exception(), []);
using var mapReader = new DotTiled.Serialization.Tmj.TmjMapReader(_tmjContents, _ => throw new NotSupportedException(), _ => throw new NotSupportedException(), _ => throw new NotSupportedException());
return mapReader.ReadMap();
}
@ -84,11 +80,11 @@ namespace MyBenchmarks
{
public static void Main(string[] args)
{
var config = BenchmarkDotNet.Configs.DefaultConfig.Instance
var config = DefaultConfig.Instance
.WithArtifactsPath(args[0])
.WithOptions(ConfigOptions.DisableOptimizationsValidator)
.AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default);
var summary = BenchmarkRunner.Run<MapLoading>(config);
_ = BenchmarkRunner.Run<MapLoading>(config);
}
}
}

View file

@ -1,5 +1,3 @@
using DotTiled.Model.Layers;
namespace DotTiled.Tests;
public static partial class DotTiledAssert

View file

@ -1,5 +1,3 @@
using DotTiled.Model.Tilesets;
namespace DotTiled.Tests;
public static partial class DotTiledAssert

View file

@ -1,5 +1,3 @@
using DotTiled.Model.Layers;
namespace DotTiled.Tests;
public static partial class DotTiledAssert

View file

@ -1,6 +1,5 @@
using System.Collections;
using System.Numerics;
using DotTiled.Model;
namespace DotTiled.Tests;
@ -91,7 +90,7 @@ public static partial class DotTiledAssert
AssertEqual(expected.NextObjectID, actual.NextObjectID, nameof(Map.NextObjectID));
AssertEqual(expected.Infinite, actual.Infinite, nameof(Map.Infinite));
AssertProperties(actual.Properties, expected.Properties);
AssertProperties(expected.Properties, actual.Properties);
Assert.NotNull(actual.Tilesets);
AssertEqual(expected.Tilesets.Count, actual.Tilesets.Count, "Tilesets.Count");

View file

@ -1,22 +1,22 @@
using DotTiled.Model.Layers.Objects;
namespace DotTiled.Tests;
public static partial class DotTiledAssert
{
internal static void AssertObject(Model.Layers.Objects.Object expected, Model.Layers.Objects.Object actual)
internal static void AssertObject(DotTiled.Object expected, DotTiled.Object actual)
{
// Attributes
AssertEqual(expected.ID, actual.ID, nameof(Model.Layers.Objects.Object.ID));
AssertEqual(expected.Name, actual.Name, nameof(Model.Layers.Objects.Object.Name));
AssertEqual(expected.Type, actual.Type, nameof(Model.Layers.Objects.Object.Type));
AssertEqual(expected.X, actual.X, nameof(Model.Layers.Objects.Object.X));
AssertEqual(expected.Y, actual.Y, nameof(Model.Layers.Objects.Object.Y));
AssertEqual(expected.Width, actual.Width, nameof(Model.Layers.Objects.Object.Width));
AssertEqual(expected.Height, actual.Height, nameof(Model.Layers.Objects.Object.Height));
AssertEqual(expected.Rotation, actual.Rotation, nameof(Model.Layers.Objects.Object.Rotation));
AssertEqual(expected.Visible, actual.Visible, nameof(Model.Layers.Objects.Object.Visible));
AssertEqual(expected.Template, actual.Template, nameof(Model.Layers.Objects.Object.Template));
#pragma warning disable IDE0002
AssertEqual(expected.ID, actual.ID, nameof(DotTiled.Object.ID));
AssertEqual(expected.Name, actual.Name, nameof(DotTiled.Object.Name));
AssertEqual(expected.Type, actual.Type, nameof(DotTiled.Object.Type));
AssertEqual(expected.X, actual.X, nameof(DotTiled.Object.X));
AssertEqual(expected.Y, actual.Y, nameof(DotTiled.Object.Y));
AssertEqual(expected.Width, actual.Width, nameof(DotTiled.Object.Width));
AssertEqual(expected.Height, actual.Height, nameof(DotTiled.Object.Height));
AssertEqual(expected.Rotation, actual.Rotation, nameof(DotTiled.Object.Rotation));
AssertEqual(expected.Visible, actual.Visible, nameof(DotTiled.Object.Visible));
AssertEqual(expected.Template, actual.Template, nameof(DotTiled.Object.Template));
#pragma warning restore IDE0002
AssertProperties(expected.Properties, actual.Properties);
@ -24,30 +24,15 @@ public static partial class DotTiledAssert
AssertObject((dynamic)expected, (dynamic)actual);
}
private static void AssertObject(RectangleObject expected, RectangleObject actual)
{
Assert.True(true); // A rectangle object is the same as the abstract Object
}
private static void AssertObject(RectangleObject _, RectangleObject __) => Assert.True(true); // A rectangle object is the same as the abstract Object
private static void AssertObject(EllipseObject expected, EllipseObject actual)
{
Assert.True(true); // An ellipse object is the same as the abstract Object
}
private static void AssertObject(EllipseObject _, EllipseObject __) => Assert.True(true); // An ellipse object is the same as the abstract Object
private static void AssertObject(PointObject expected, PointObject actual)
{
Assert.True(true); // A point object is the same as the abstract Object
}
private static void AssertObject(PointObject _, PointObject __) => Assert.True(true); // A point object is the same as the abstract Object
private static void AssertObject(PolygonObject expected, PolygonObject actual)
{
AssertEqual(expected.Points, actual.Points, nameof(PolygonObject.Points));
}
private static void AssertObject(PolygonObject expected, PolygonObject actual) => AssertEqual(expected.Points, actual.Points, nameof(PolygonObject.Points));
private static void AssertObject(PolylineObject expected, PolylineObject actual)
{
AssertEqual(expected.Points, actual.Points, nameof(PolylineObject.Points));
}
private static void AssertObject(PolylineObject expected, PolylineObject actual) => AssertEqual(expected.Points, actual.Points, nameof(PolylineObject.Points));
private static void AssertObject(TextObject expected, TextObject actual)
{
@ -67,9 +52,5 @@ public static partial class DotTiledAssert
AssertEqual(expected.Text, actual.Text, nameof(TextObject.Text));
}
private static void AssertObject(TileObject expected, TileObject actual)
{
// Attributes
AssertEqual(expected.GID, actual.GID, nameof(TileObject.GID));
}
private static void AssertObject(TileObject expected, TileObject actual) => AssertEqual(expected.GID, actual.GID, nameof(TileObject.GID));
}

View file

@ -1,10 +1,8 @@
using DotTiled.Model.Properties;
namespace DotTiled.Tests;
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)
{
@ -14,58 +12,45 @@ public static partial class DotTiledAssert
Assert.NotNull(actual);
AssertEqual(expected.Count, actual.Count, "Properties.Count");
foreach (var kvp in expected)
foreach (var prop in expected)
{
Assert.Contains(kvp.Key, actual.Keys);
AssertProperty((dynamic)kvp.Value, (dynamic)actual[kvp.Key]);
Assert.Contains(actual, 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);
}
}
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");
}
private static void AssertProperty(FloatProperty expected, FloatProperty actual) => AssertEqual(expected.Value, actual.Value, "FloatProperty.Value");
private static void AssertProperty(FloatProperty expected, FloatProperty actual)
{
AssertEqual(expected.Value, actual.Value, "FloatProperty.Value");
}
private static void AssertProperty(BoolProperty expected, BoolProperty actual) => AssertEqual(expected.Value, actual.Value, "BoolProperty.Value");
private static void AssertProperty(BoolProperty expected, BoolProperty actual)
{
AssertEqual(expected.Value, actual.Value, "BoolProperty.Value");
}
private static void AssertProperty(ColorProperty expected, ColorProperty actual) => AssertEqual(expected.Value, actual.Value, "ColorProperty.Value");
private static void AssertProperty(ColorProperty expected, ColorProperty actual)
{
AssertEqual(expected.Value, actual.Value, "ColorProperty.Value");
}
private static void AssertProperty(FileProperty expected, FileProperty actual) => AssertEqual(expected.Value, actual.Value, "FileProperty.Value");
private static void AssertProperty(FileProperty expected, FileProperty actual)
{
AssertEqual(expected.Value, actual.Value, "FileProperty.Value");
}
private static void AssertProperty(ObjectProperty expected, ObjectProperty actual)
{
AssertEqual(expected.Value, actual.Value, "ObjectProperty.Value");
}
private static void AssertProperty(ObjectProperty expected, ObjectProperty actual) => AssertEqual(expected.Value, actual.Value, "ObjectProperty.Value");
private static void AssertProperty(ClassProperty expected, ClassProperty actual)
{
AssertEqual(expected.PropertyType, actual.PropertyType, "ClassProperty.PropertyType");
AssertProperties(expected.Properties, actual.Properties);
AssertProperties(expected.Value, actual.Value);
}
private static void AssertProperty(EnumProperty expected, EnumProperty actual)
{
AssertEqual(expected.PropertyType, actual.PropertyType, "EnumProperty.PropertyType");
AssertEqual(expected.Value.Count, actual.Value.Count, "EnumProperty.Value.Count");
foreach (var value in expected.Value)
{
Assert.Contains(actual.Value, v => v == value);
}
}
}

View file

@ -1,6 +1,3 @@
using DotTiled.Model.Layers;
using DotTiled.Model.Tilesets;
namespace DotTiled.Tests;
public static partial class DotTiledAssert
@ -142,9 +139,9 @@ public static partial class DotTiledAssert
AssertEqual(expected.Height, actual.Height, nameof(Tile.Height));
// Elements
AssertProperties(actual.Properties, expected.Properties);
AssertImage(actual.Image, expected.Image);
AssertLayer((BaseLayer?)actual.ObjectLayer, (BaseLayer?)expected.ObjectLayer);
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);

View file

@ -0,0 +1,49 @@
using DotTiled.Serialization;
namespace DotTiled.Tests;
public partial class MapReaderTests
{
public static IEnumerable<object[]> Maps => TestData.MapTests;
[Theory]
[MemberData(nameof(Maps))]
public void MapReaderReadMap_ValidFilesExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(
string testDataFile,
Func<string, Map> expectedMap,
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
{
// Arrange
string[] fileFormats = [".tmx", ".tmj"];
foreach (var fileFormat in fileFormats)
{
var testDataFileWithFormat = testDataFile + fileFormat;
var fileDir = Path.GetDirectoryName(testDataFileWithFormat);
var mapString = TestData.GetRawStringFor(testDataFileWithFormat);
Template ResolveTemplate(string source)
{
var templateString = TestData.GetRawStringFor($"{fileDir}/{source}");
using var templateReader = new TemplateReader(templateString, ResolveTileset, ResolveTemplate, ResolveCustomType);
return templateReader.ReadTemplate();
}
Tileset ResolveTileset(string source)
{
var tilesetString = TestData.GetRawStringFor($"{fileDir}/{source}");
using var tilesetReader = new TilesetReader(tilesetString, ResolveTileset, ResolveTemplate, ResolveCustomType);
return tilesetReader.ReadTileset();
}
ICustomTypeDefinition ResolveCustomType(string name)
{
return customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name)!;
}
using var mapReader = new MapReader(mapString, ResolveTileset, ResolveTemplate, ResolveCustomType);
// Act
var map = mapReader.ReadMap();
// Assert
Assert.NotNull(map);
DotTiledAssert.AssertMap(expectedMap(fileFormat[1..]), map);
}
}
}

View file

@ -1,6 +1,4 @@
using System.Xml;
using DotTiled.Model.Properties;
using DotTiled.Model.Properties.CustomTypes;
namespace DotTiled.Tests;
@ -33,49 +31,15 @@ public static partial class TestData
public static IEnumerable<object[]> MapTests =>
[
["Serialization/TestData/Map/default_map/default-map", (string f) => TestData.DefaultMap(), Array.Empty<CustomTypeDefinition>()],
["Serialization/TestData/Map/map_with_common_props/map-with-common-props", (string f) => TestData.MapWithCommonProps(), Array.Empty<CustomTypeDefinition>()],
["Serialization/TestData/Map/map_with_custom_type_props/map-with-custom-type-props", (string f) => TestData.MapWithCustomTypeProps(), TestData.MapWithCustomTypePropsCustomTypeDefinitions()],
["Serialization/TestData/Map/map_with_embedded_tileset/map-with-embedded-tileset", (string f) => TestData.MapWithEmbeddedTileset(), Array.Empty<CustomTypeDefinition>()],
["Serialization/TestData/Map/map_with_external_tileset/map-with-external-tileset", (string f) => TestData.MapWithExternalTileset(f), Array.Empty<CustomTypeDefinition>()],
["Serialization/TestData/Map/map_with_flippingflags/map-with-flippingflags", (string f) => TestData.MapWithFlippingFlags(f), Array.Empty<CustomTypeDefinition>()],
["Serialization/TestData/Map/map_external_tileset_multi/map-external-tileset-multi", (string f) => TestData.MapExternalTilesetMulti(f), Array.Empty<CustomTypeDefinition>()],
["Serialization/TestData/Map/map_external_tileset_wangset/map-external-tileset-wangset", (string f) => TestData.MapExternalTilesetWangset(f), Array.Empty<CustomTypeDefinition>()],
["Serialization/TestData/Map/map_with_many_layers/map-with-many-layers", (string f) => TestData.MapWithManyLayers(f), Array.Empty<CustomTypeDefinition>()],
];
private static CustomTypeDefinition[] typedefs = [
new CustomClassDefinition
{
Name = "TestClass",
ID = 1,
UseAs = CustomClassUseAs.Property,
Members = [
new StringProperty
{
Name = "Name",
Value = ""
},
new FloatProperty
{
Name = "Amount",
Value = 0f
}
]
},
new CustomClassDefinition
{
Name = "Test",
ID = 2,
UseAs = CustomClassUseAs.All,
Members = [
new ClassProperty
{
Name = "Yep",
PropertyType = "TestClass",
Properties = []
}
]
}
["Serialization/TestData/Map/default_map/default-map", (string f) => DefaultMap(), Array.Empty<ICustomTypeDefinition>()],
["Serialization/TestData/Map/map_with_common_props/map-with-common-props", (string f) => MapWithCommonProps(), Array.Empty<ICustomTypeDefinition>()],
["Serialization/TestData/Map/map_with_custom_type_props/map-with-custom-type-props", (string f) => MapWithCustomTypeProps(), MapWithCustomTypePropsCustomTypeDefinitions()],
["Serialization/TestData/Map/map_with_embedded_tileset/map-with-embedded-tileset", (string f) => MapWithEmbeddedTileset(), Array.Empty<ICustomTypeDefinition>()],
["Serialization/TestData/Map/map_with_external_tileset/map-with-external-tileset", (string f) => MapWithExternalTileset(f), Array.Empty<ICustomTypeDefinition>()],
["Serialization/TestData/Map/map_with_flippingflags/map-with-flippingflags", (string f) => MapWithFlippingFlags(f), Array.Empty<ICustomTypeDefinition>()],
["Serialization/TestData/Map/map_external_tileset_multi/map-external-tileset-multi", (string f) => MapExternalTilesetMulti(f), Array.Empty<ICustomTypeDefinition>()],
["Serialization/TestData/Map/map_external_tileset_wangset/map-external-tileset-wangset", (string f) => MapExternalTilesetWangset(f), Array.Empty<ICustomTypeDefinition>()],
["Serialization/TestData/Map/map_with_many_layers/map-with-many-layers", (string f) => MapWithManyLayers(f), Array.Empty<ICustomTypeDefinition>()],
["Serialization/TestData/Map/map_with_deep_props/map-with-deep-props", (string f) => MapWithDeepProps(), MapWithDeepPropsCustomTypeDefinitions()],
];
}

View file

@ -1,6 +1,3 @@
using DotTiled.Model;
using DotTiled.Model.Layers;
namespace DotTiled.Tests;
public partial class TestData

View file

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

View file

@ -1,7 +1,4 @@
using System.Globalization;
using DotTiled.Model;
using DotTiled.Model.Layers;
using DotTiled.Model.Tilesets;
namespace DotTiled.Tests;

View file

@ -1,7 +1,4 @@
using System.Globalization;
using DotTiled.Model;
using DotTiled.Model.Layers;
using DotTiled.Model.Properties;
namespace DotTiled.Tests;
@ -57,15 +54,15 @@ public partial class TestData
}
}
],
Properties = new Dictionary<string, IProperty>
{
["boolprop"] = new BoolProperty { Name = "boolprop", Value = true },
["colorprop"] = new ColorProperty { Name = "colorprop", Value = Color.Parse("#ff55ffff", CultureInfo.InvariantCulture) },
["fileprop"] = new FileProperty { Name = "fileprop", Value = "file.txt" },
["floatprop"] = new FloatProperty { Name = "floatprop", Value = 4.2f },
["intprop"] = new IntProperty { Name = "intprop", Value = 8 },
["objectprop"] = new ObjectProperty { Name = "objectprop", Value = 5 },
["stringprop"] = new StringProperty { Name = "stringprop", Value = "This is a string, hello world!" }
}
Properties =
[
new BoolProperty { Name = "boolprop", Value = true },
new ColorProperty { Name = "colorprop", Value = Color.Parse("#ff55ffff", CultureInfo.InvariantCulture) },
new FileProperty { Name = "fileprop", Value = "file.txt" },
new FloatProperty { Name = "floatprop", Value = 4.2f },
new IntProperty { Name = "intprop", Value = 8 },
new ObjectProperty { Name = "objectprop", Value = 5 },
new StringProperty { Name = "stringprop", Value = "This is a string, hello world!" }
]
};
}

View file

@ -1,8 +1,4 @@
using System.Globalization;
using DotTiled.Model;
using DotTiled.Model.Layers;
using DotTiled.Model.Properties;
using DotTiled.Model.Properties.CustomTypes;
namespace DotTiled.Tests;
@ -58,28 +54,50 @@ public partial class TestData
}
}
],
Properties = new Dictionary<string, IProperty>
{
["customclassprop"] = new ClassProperty
Properties = [
new ClassProperty
{
Name = "customclassprop",
PropertyType = "CustomClass",
Properties = new Dictionary<string, IProperty>
{
["boolinclass"] = new BoolProperty { Name = "boolinclass", Value = true },
["colorinclass"] = new ColorProperty { Name = "colorinclass", Value = Color.Parse("#000000ff", CultureInfo.InvariantCulture) },
["fileinclass"] = new FileProperty { Name = "fileinclass", Value = "" },
["floatinclass"] = new FloatProperty { Name = "floatinclass", Value = 13.37f },
["intinclass"] = new IntProperty { Name = "intinclass", Value = 0 },
["objectinclass"] = new ObjectProperty { Name = "objectinclass", Value = 0 },
["stringinclass"] = new StringProperty { Name = "stringinclass", Value = "This is a set string" }
}
Value = [
new BoolProperty { Name = "boolinclass", Value = true },
new ColorProperty { Name = "colorinclass", Value = Color.Parse("#000000ff", CultureInfo.InvariantCulture) },
new FileProperty { Name = "fileinclass", Value = "" },
new FloatProperty { Name = "floatinclass", Value = 13.37f },
new IntProperty { Name = "intinclass", Value = 0 },
new ObjectProperty { Name = "objectinclass", Value = 0 },
new StringProperty { Name = "stringinclass", Value = "This is a set string" }
]
},
new EnumProperty
{
Name = "customenumstringprop",
PropertyType = "CustomEnumString",
Value = new HashSet<string> { "CustomEnumString_2" }
},
new EnumProperty
{
Name = "customenumstringflagsprop",
PropertyType = "CustomEnumStringFlags",
Value = new HashSet<string> { "CustomEnumStringFlags_1", "CustomEnumStringFlags_2" }
},
new EnumProperty
{
Name = "customenumintprop",
PropertyType = "CustomEnumInt",
Value = new HashSet<string> { "CustomEnumInt_4" }
},
new EnumProperty
{
Name = "customenumintflagsprop",
PropertyType = "CustomEnumIntFlags",
Value = new HashSet<string> { "CustomEnumIntFlags_2", "CustomEnumIntFlags_3" }
}
}
]
};
// This comes from map-with-custom-type-props/propertytypes.json
public static IReadOnlyCollection<CustomTypeDefinition> MapWithCustomTypePropsCustomTypeDefinitions() => [
public static IReadOnlyCollection<ICustomTypeDefinition> MapWithCustomTypePropsCustomTypeDefinitions() => [
new CustomClassDefinition
{
Name = "CustomClass",
@ -121,6 +139,50 @@ public partial class TestData
Value = ""
}
]
},
new CustomEnumDefinition
{
Name = "CustomEnumString",
StorageType = CustomEnumStorageType.String,
ValueAsFlags = false,
Values = [
"CustomEnumString_1",
"CustomEnumString_2",
"CustomEnumString_3"
]
},
new CustomEnumDefinition
{
Name = "CustomEnumStringFlags",
StorageType = CustomEnumStorageType.String,
ValueAsFlags = true,
Values = [
"CustomEnumStringFlags_1",
"CustomEnumStringFlags_2"
]
},
new CustomEnumDefinition
{
Name = "CustomEnumInt",
StorageType = CustomEnumStorageType.Int,
ValueAsFlags = false,
Values = [
"CustomEnumInt_1",
"CustomEnumInt_2",
"CustomEnumInt_3",
"CustomEnumInt_4",
]
},
new CustomEnumDefinition
{
Name = "CustomEnumIntFlags",
StorageType = CustomEnumStorageType.Int,
ValueAsFlags = true,
Values = [
"CustomEnumIntFlags_1",
"CustomEnumIntFlags_2",
"CustomEnumIntFlags_3"
]
}
];
}

View file

@ -32,6 +32,30 @@
"floatinclass":13.37,
"stringinclass":"This is a set string"
}
},
{
"name":"customenumintflagsprop",
"propertytype":"CustomEnumIntFlags",
"type":"int",
"value":6
},
{
"name":"customenumintprop",
"propertytype":"CustomEnumInt",
"type":"int",
"value":3
},
{
"name":"customenumstringflagsprop",
"propertytype":"CustomEnumStringFlags",
"type":"string",
"value":"CustomEnumStringFlags_1,CustomEnumStringFlags_2"
},
{
"name":"customenumstringprop",
"propertytype":"CustomEnumString",
"type":"string",
"value":"CustomEnumString_2"
}],
"renderorder":"right-down",
"tiledversion":"1.11.0",

View file

@ -8,6 +8,10 @@
<property name="stringinclass" value="This is a set string"/>
</properties>
</property>
<property name="customenumintflagsprop" type="int" propertytype="CustomEnumIntFlags" value="6"/>
<property name="customenumintprop" type="int" propertytype="CustomEnumInt" value="3"/>
<property name="customenumstringflagsprop" propertytype="CustomEnumStringFlags" value="CustomEnumStringFlags_1,CustomEnumStringFlags_2"/>
<property name="customenumstringprop" propertytype="CustomEnumString" value="CustomEnumString_2"/>
</properties>
<layer id="1" name="Tile Layer 1" width="5" height="5">
<data encoding="csv">

View file

@ -0,0 +1,160 @@
using System.Globalization;
namespace DotTiled.Tests;
public partial class TestData
{
public static Map MapWithDeepProps() => new Map
{
Class = "",
Orientation = MapOrientation.Orthogonal,
Width = 5,
Height = 5,
TileWidth = 32,
TileHeight = 32,
Infinite = false,
HexSideLength = null,
StaggerAxis = null,
StaggerIndex = null,
ParallaxOriginX = 0,
ParallaxOriginY = 0,
RenderOrder = RenderOrder.RightDown,
CompressionLevel = -1,
BackgroundColor = Color.Parse("#00000000", CultureInfo.InvariantCulture),
Version = "1.10",
TiledVersion = "1.11.0",
NextLayerID = 2,
NextObjectID = 1,
Layers = [
new TileLayer
{
ID = 1,
Name = "Tile Layer 1",
Width = 5,
Height = 5,
Data = new Data
{
Encoding = DataEncoding.Csv,
Chunks = null,
Compression = null,
GlobalTileIDs = [
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0
],
FlippingFlags = [
FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None,
FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None,
FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None,
FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None,
FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None
]
}
}
],
Properties = [
new ClassProperty
{
Name = "customouterclassprop",
PropertyType = "CustomOuterClass",
Value = [
new ClassProperty
{
Name = "customclasspropinclass",
PropertyType = "CustomClass",
Value = [
new BoolProperty { Name = "boolinclass", Value = false },
new ColorProperty { Name = "colorinclass", Value = Color.Parse("#000000ff", CultureInfo.InvariantCulture) },
new FileProperty { Name = "fileinclass", Value = "" },
new FloatProperty { Name = "floatinclass", Value = 0f },
new IntProperty { Name = "intinclass", Value = 0 },
new ObjectProperty { Name = "objectinclass", Value = 0 },
new StringProperty { Name = "stringinclass", Value = "" }
]
}
]
},
new ClassProperty
{
Name = "customouterclasspropset",
PropertyType = "CustomOuterClass",
Value = [
new ClassProperty
{
Name = "customclasspropinclass",
PropertyType = "CustomClass",
Value = [
new BoolProperty { Name = "boolinclass", Value = true },
new ColorProperty { Name = "colorinclass", Value = Color.Parse("#000000ff", CultureInfo.InvariantCulture) },
new FileProperty { Name = "fileinclass", Value = "" },
new FloatProperty { Name = "floatinclass", Value = 13.37f },
new IntProperty { Name = "intinclass", Value = 0 },
new ObjectProperty { Name = "objectinclass", Value = 0 },
new StringProperty { Name = "stringinclass", Value = "" }
]
}
]
}
]
};
public static IReadOnlyCollection<ICustomTypeDefinition> MapWithDeepPropsCustomTypeDefinitions() => [
new CustomClassDefinition
{
Name = "CustomClass",
UseAs = CustomClassUseAs.Property,
Members = [
new BoolProperty
{
Name = "boolinclass",
Value = false
},
new ColorProperty
{
Name = "colorinclass",
Value = Color.Parse("#000000ff", CultureInfo.InvariantCulture)
},
new FileProperty
{
Name = "fileinclass",
Value = ""
},
new FloatProperty
{
Name = "floatinclass",
Value = 0f
},
new IntProperty
{
Name = "intinclass",
Value = 0
},
new ObjectProperty
{
Name = "objectinclass",
Value = 0
},
new StringProperty
{
Name = "stringinclass",
Value = ""
}
]
},
new CustomClassDefinition
{
Name = "CustomOuterClass",
UseAs = CustomClassUseAs.Property,
Members = [
new ClassProperty
{
Name = "customclasspropinclass",
PropertyType = "CustomClass",
Value = [] // So no overrides of defaults in CustomClass
}
]
}
];
}

View file

@ -0,0 +1,55 @@
{ "compressionlevel":-1,
"height":5,
"infinite":false,
"layers":[
{
"data":[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],
"height":5,
"id":1,
"name":"Tile Layer 1",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":5,
"x":0,
"y":0
}],
"nextlayerid":2,
"nextobjectid":1,
"orientation":"orthogonal",
"properties":[
{
"name":"customouterclassprop",
"propertytype":"CustomOuterClass",
"type":"class",
"value":
{
}
},
{
"name":"customouterclasspropset",
"propertytype":"CustomOuterClass",
"type":"class",
"value":
{
"customclasspropinclass":
{
"boolinclass":true,
"floatinclass":13.37
}
}
}],
"renderorder":"right-down",
"tiledversion":"1.11.0",
"tileheight":32,
"tilesets":[],
"tilewidth":32,
"type":"map",
"version":"1.10",
"width":5
}

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="5" height="5" tilewidth="32" tileheight="32" infinite="0" nextlayerid="2" nextobjectid="1">
<properties>
<property name="customouterclassprop" type="class" propertytype="CustomOuterClass"/>
<property name="customouterclasspropset" type="class" propertytype="CustomOuterClass">
<properties>
<property name="customclasspropinclass" type="class" propertytype="CustomClass">
<properties>
<property name="boolinclass" type="bool" value="true"/>
<property name="floatinclass" type="float" value="13.37"/>
</properties>
</property>
</properties>
</property>
</properties>
<layer id="1" name="Tile Layer 1" width="5" height="5">
<data encoding="csv">
0,0,0,0,0,
0,0,0,0,0,
0,0,0,0,0,
0,0,0,0,0,
0,0,0,0,0
</data>
</layer>
</map>

View file

@ -1,7 +1,4 @@
using System.Globalization;
using DotTiled.Model;
using DotTiled.Model.Layers;
using DotTiled.Model.Tilesets;
namespace DotTiled.Tests;

View file

@ -1,7 +1,4 @@
using System.Globalization;
using DotTiled.Model;
using DotTiled.Model.Layers;
using DotTiled.Model.Tilesets;
namespace DotTiled.Tests;

View file

@ -1,7 +1,4 @@
using System.Globalization;
using DotTiled.Model;
using DotTiled.Model.Layers;
using DotTiled.Model.Tilesets;
namespace DotTiled.Tests;

View file

@ -1,9 +1,4 @@
using System.Numerics;
using DotTiled.Model;
using DotTiled.Model.Layers;
using DotTiled.Model.Layers.Objects;
using DotTiled.Model.Properties;
using DotTiled.Model.Tilesets;
namespace DotTiled.Tests;
@ -99,10 +94,9 @@ public partial class TestData
new Vector2(35.6667f, 32.3333f)
],
Template = fileExt == "tmx" ? "poly.tx" : "poly.tj",
Properties = new Dictionary<string, IProperty>
{
["templateprop"] = new StringProperty { Name = "templateprop", Value = "helo there" }
}
Properties = [
new StringProperty { Name = "templateprop", Value = "helo there" }
]
},
new TileObject
{

View file

@ -1,6 +1,3 @@
using DotTiled.Model;
using DotTiled.Model.Properties.CustomTypes;
using DotTiled.Model.Tilesets;
using DotTiled.Serialization.Tmj;
namespace DotTiled.Tests;
@ -13,7 +10,7 @@ public partial class TmjMapReaderTests
public void TmxMapReaderReadMap_ValidTmjExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(
string testDataFile,
Func<string, Map> expectedMap,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
{
// Arrange
testDataFile += ".tmj";
@ -22,16 +19,20 @@ public partial class TmjMapReaderTests
Template ResolveTemplate(string source)
{
var templateJson = TestData.GetRawStringFor($"{fileDir}/{source}");
using var templateReader = new TjTemplateReader(templateJson, ResolveTileset, ResolveTemplate, customTypeDefinitions);
using var templateReader = new TjTemplateReader(templateJson, ResolveTileset, ResolveTemplate, ResolveCustomType);
return templateReader.ReadTemplate();
}
Tileset ResolveTileset(string source)
{
var tilesetJson = TestData.GetRawStringFor($"{fileDir}/{source}");
using var tilesetReader = new TsjTilesetReader(tilesetJson, ResolveTemplate, customTypeDefinitions);
using var tilesetReader = new TsjTilesetReader(tilesetJson, ResolveTileset, ResolveTemplate, ResolveCustomType);
return tilesetReader.ReadTileset();
}
using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate, customTypeDefinitions);
ICustomTypeDefinition ResolveCustomType(string name)
{
return customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name)!;
}
using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate, ResolveCustomType);
// Act
var map = mapReader.ReadMap();

View file

@ -1,7 +1,3 @@
using System.Xml;
using DotTiled.Model;
using DotTiled.Model.Properties.CustomTypes;
using DotTiled.Model.Tilesets;
using DotTiled.Serialization.Tmx;
namespace DotTiled.Tests;
@ -14,7 +10,7 @@ public partial class TmxMapReaderTests
public void TmxMapReaderReadMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(
string testDataFile,
Func<string, Map> expectedMap,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
{
// Arrange
testDataFile += ".tmx";
@ -23,16 +19,20 @@ public partial class TmxMapReaderTests
Template ResolveTemplate(string source)
{
using var xmlTemplateReader = TestData.GetXmlReaderFor($"{fileDir}/{source}");
using var templateReader = new TxTemplateReader(xmlTemplateReader, ResolveTileset, ResolveTemplate, customTypeDefinitions);
using var templateReader = new TxTemplateReader(xmlTemplateReader, ResolveTileset, ResolveTemplate, ResolveCustomType);
return templateReader.ReadTemplate();
}
Tileset ResolveTileset(string source)
{
using var xmlTilesetReader = TestData.GetXmlReaderFor($"{fileDir}/{source}");
using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTemplate, customTypeDefinitions);
using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTileset, ResolveTemplate, ResolveCustomType);
return tilesetReader.ReadTileset();
}
using var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate, customTypeDefinitions);
ICustomTypeDefinition ResolveCustomType(string name)
{
return customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name)!;
}
using var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate, ResolveCustomType);
// Act
var map = mapReader.ReadMap();

View file

@ -2,21 +2,55 @@ using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
namespace DotTiled.Model;
namespace DotTiled;
/// <summary>
/// Represents a Tiled color.
/// </summary>
public class Color : IParsable<Color>, IEquatable<Color>
{
/// <summary>
/// The red component of the color.
/// </summary>
public required byte R { get; set; }
/// <summary>
/// The green component of the color.
/// </summary>
public required byte G { get; set; }
/// <summary>
/// The blue component of the color.
/// </summary>
public required byte B { get; set; }
/// <summary>
/// The alpha component of the color.
/// </summary>
public byte A { get; set; } = 255;
/// <summary>
/// Attempts to parse the specified string into a <see cref="Color"/>. Expects strings in the format <c>#RRGGBB</c> or <c>#AARRGGBB</c>.
/// The leading <c>#</c> is optional.
/// </summary>
/// <param name="s">A string value to parse into a <see cref="Color"/></param>
/// <param name="provider">An object that supplies culture-specific information about the format of s.</param>
/// <returns>The parsed <see cref="Color"/></returns>
/// <exception cref="FormatException">Thrown in case the provided string <paramref name="s"/> is not in a valid format.</exception>
public static Color Parse(string s, IFormatProvider? provider)
{
TryParse(s, provider, out var result);
_ = TryParse(s, provider, out var result);
return result ?? throw new FormatException($"Invalid format for TiledColor: {s}");
}
/// <summary>
/// Attempts to parse the specified string into a <see cref="Color"/>. Expects strings in the format <c>#RRGGBB</c> or <c>#AARRGGBB</c>.
/// The leading <c>#</c> is optional.
/// </summary>
/// <param name="s">A string value to parse into a <see cref="Color"/></param>
/// <param name="provider">An object that supplies culture-specific information about the format of s.</param>
/// <param name="result">When this method returns, contains the parsed <see cref="Color"/> or <c>null</c> on failure.</param>
/// <returns><c>true</c> if <paramref name="s"/> was successfully parsed; otherwise, <c>false</c>.</returns>
public static bool TryParse(
[NotNullWhen(true)] string? s,
IFormatProvider? provider,
@ -26,7 +60,7 @@ public class Color : IParsable<Color>, IEquatable<Color>
return TryParse($"#{s}", provider, out result);
// Format: #RRGGBB or #AARRGGBB
if (s is null || s.Length != 7 && s.Length != 9 || s[0] != '#')
if (s is null || (s.Length != 7 && s.Length != 9) || s[0] != '#')
{
result = default;
return false;
@ -55,6 +89,7 @@ public class Color : IParsable<Color>, IEquatable<Color>
return true;
}
/// <inheritdoc/>
public bool Equals(Color? other)
{
if (other is null)
@ -63,9 +98,12 @@ public class Color : IParsable<Color>, IEquatable<Color>
return R == other.R && G == other.G && B == other.B && A == other.A;
}
/// <inheritdoc/>
public override bool Equals(object? obj) => obj is Color other && Equals(other);
/// <inheritdoc/>
public override int GetHashCode() => HashCode.Combine(R, G, B, A);
/// <inheritdoc/>
public override string ToString() => $"#{A:x2}{R:x2}{G:x2}{B:x2}";
}

View file

@ -3,6 +3,28 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup>
<PackageId>DotTiled</PackageId>
<Authors>dcronqvist</Authors>
<Title>DotTiled</Title>
<Description>DotTiled is a general-purpose Tiled map parser for all your .NET needs.</Description>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/dcronqvist/DotTiled</RepositoryUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageTags>gamedev;window;parser;tiled;mapeditor</PackageTags>
<PackageProjectUrl>https://github.com/dcronqvist/DotTiled</PackageProjectUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Copyright>Copyright © 2024 dcronqvist</Copyright>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<Version>0.1.0</Version>
</PropertyGroup>
<ItemGroup>
<None Include="../../README.md" Pack="true" PackagePath="" />
<None Include="../../LICENSE" Pack="true" PackagePath="" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,69 @@
using System.Collections.Generic;
namespace DotTiled;
/// <summary>
/// Base class for all layer types in a map.
/// 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.
/// </summary>
public abstract class BaseLayer : HasPropertiesBase
{
/// <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.
/// </summary>
public required uint ID { get; set; }
/// <summary>
/// The name of the layer.
/// </summary>
public string Name { get; set; } = "";
/// <summary>
/// The class of the layer.
/// </summary>
public string Class { get; set; } = "";
/// <summary>
/// The opacity of the layer as a value from 0 (fully transparent) to 1 (fully opaque).
/// </summary>
public float Opacity { get; set; } = 1.0f;
/// <summary>
/// Whether the layer is shown (true) or hidden (false).
/// </summary>
public bool Visible { get; set; } = true;
/// <summary>
/// A tint color that is multiplied with any tiles drawn by this layer.
/// </summary>
public Color? TintColor { get; set; }
/// <summary>
/// Horizontal offset for this layer in pixels.
/// </summary>
public float OffsetX { get; set; } = 0.0f;
/// <summary>
/// Vertical offset for this layer in pixels.
/// </summary>
public float OffsetY { get; set; } = 0.0f;
/// <summary>
/// Horizontal parallax factor for this layer.
/// </summary>
public float ParallaxX { get; set; } = 1.0f;
/// <summary>
/// Vertical parallax factor for this layer.
/// </summary>
public float ParallaxY { get; set; } = 1.0f;
/// <summary>
/// Layer properties.
/// </summary>
public List<IProperty> Properties { get; set; } = [];
/// <inheritdoc/>
public override IList<IProperty> GetProperties() => Properties;
}

144
src/DotTiled/Layers/Data.cs Normal file
View file

@ -0,0 +1,144 @@
using System;
namespace DotTiled;
/// <summary>
/// Specifies the encoding used to encode the tile layer data.
/// </summary>
public enum DataEncoding
{
/// <summary>
/// The data is stored as comma-separated values.
/// </summary>
Csv,
/// <summary>
/// The data is stored as base64-encoded binary data.
/// </summary>
Base64
}
/// <summary>
/// Specifies the compression algorithm used to compress the tile layer data.
/// </summary>
public enum DataCompression
{
/// <summary>
/// GZip compression.
/// </summary>
GZip,
/// <summary>
/// ZLib compression.
/// </summary>
ZLib,
/// <summary>
/// ZStandard compression. Currently not supported by DotTiled and will throw an exception if encountered.
/// </summary>
ZStd
}
/// <summary>
/// The flipping flags for a tile. These can be used to check how a tile is flipped or rotated. Uses the
/// <see href="https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-flagsattribute">FlagsAttribute, for which there is plenty of documentation.</see>
/// </summary>
[Flags]
public enum FlippingFlags : uint
{
/// <summary>
/// No flipping.
/// </summary>
None = 0,
/// <summary>
/// The tile is flipped horizontally.
/// </summary>
FlippedHorizontally = 0x80000000u,
/// <summary>
/// The tile is flipped vertically.
/// </summary>
FlippedVertically = 0x40000000u,
/// <summary>
/// The tile is flipped diagonally.
/// </summary>
FlippedDiagonally = 0x20000000u,
/// <summary>
/// In hexagonal maps, the tile is rotated 120 degrees clockwise.
/// </summary>
RotatedHexagonal120 = 0x10000000u
}
/// <summary>
/// Represents part of a tile layer of a map that is infinite.
/// </summary>
public class Chunk
{
/// <summary>
/// The X coordinate of the chunk in tiles.
/// </summary>
public required int X { get; set; }
/// <summary>
/// The Y coordinate of the chunk in tiles.
/// </summary>
public required int Y { get; set; }
/// <summary>
/// The width of the chunk in tiles.
/// </summary>
public required uint Width { get; set; }
/// <summary>
/// The height of the chunk in tiles.
/// </summary>
public required uint Height { get; set; }
/// <summary>
/// The parsed chunk 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 required uint[] GlobalTileIDs { get; set; }
/// <summary>
/// The parsed flipping flags for each tile in the chunk. Appear in the same order as the tiles in the layer in <see cref="GlobalTileIDs"/>.
/// </summary>
public required FlippingFlags[] FlippingFlags { get; set; }
}
/// <summary>
/// Represents the data of a tile layer.
/// </summary>
public class Data
{
/// <summary>
/// The encoding used to encode the tile layer data.
/// </summary>
public DataEncoding? Encoding { get; set; }
/// <summary>
/// The compression method used to compress the tile layer data.
/// </summary>
public DataCompression? Compression { get; set; }
/// <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; }
/// <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; }
/// <summary>
/// If the map is infinite, it will instead contain a list of chunks.
/// </summary>
public Chunk[]? Chunks { get; set; }
}

View file

@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace DotTiled;
/// <summary>
/// Represents a group of layers, to form a hierarchy.
/// </summary>
public class Group : BaseLayer
{
/// <summary>
/// The contained sub-layers in the group.
/// </summary>
public List<BaseLayer> Layers { get; set; } = [];
}

View file

@ -0,0 +1,32 @@
namespace DotTiled;
/// <summary>
/// Represents an image layer in a map.
/// </summary>
public class ImageLayer : BaseLayer
{
/// <summary>
/// The X position of the image layer in pixels.
/// </summary>
public uint X { get; set; } = 0;
/// <summary>
/// The Y position of the image layer in pixels.
/// </summary>
public 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;
/// <summary>
/// Whether the image drawn by this layer is repeated along the Y axis.
/// </summary>
public bool RepeatY { get; set; } = false;
/// <summary>
/// The image to be drawn by this image layer.
/// </summary>
public Image? Image { get; set; }
}

View file

@ -0,0 +1,60 @@
using System.Collections.Generic;
namespace DotTiled;
/// <summary>
/// Represents the order in which objects can be drawn.
/// </summary>
public enum DrawOrder
{
/// <summary>
/// Objects are drawn sorted by their Y coordinate.
/// </summary>
TopDown,
/// <summary>
/// Objects are drawn in the order of appearance in the object layer.
/// </summary>
Index
}
/// <summary>
/// Represents an object layer in a map. In Tiled documentation, it is often called an "object group".
/// </summary>
public class ObjectLayer : BaseLayer
{
/// <summary>
/// The X coordinate of the object layer in tiles.
/// </summary>
public uint X { get; set; } = 0;
/// <summary>
/// The Y coordinate of the object layer in tiles.
/// </summary>
public uint Y { get; set; } = 0;
/// <summary>
/// The width of the object layer in tiles. Meaningless.
/// </summary>
public uint? Width { get; set; }
/// <summary>
/// The height of the object layer in tiles. Meaningless.
/// </summary>
public uint? Height { get; set; }
/// <summary>
/// A color that is multiplied with any tile objects drawn by this layer.
/// </summary>
public Color? Color { get; set; }
/// <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"/>).
/// </summary>
public DrawOrder DrawOrder { get; set; } = DrawOrder.TopDown;
/// <summary>
/// The objects in the object layer.
/// </summary>
public required List<Object> Objects { get; set; }
}

View file

@ -0,0 +1,7 @@
namespace DotTiled;
/// <summary>
/// An ellipse object in a map. The existing <see cref="Object.X"/>, <see cref="Object.Y"/>, <see cref="Object.Width"/>,
/// and <see cref="Object.Height"/> properties are used to determine the size of the ellipse.
/// </summary>
public class EllipseObject : Object { }

View file

@ -0,0 +1,67 @@
using System.Collections.Generic;
namespace DotTiled;
/// <summary>
/// Base class for objects in object layers.
/// </summary>
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; }
/// <summary>
/// The name of the object. An arbitrary string.
/// </summary>
public string Name { get; set; } = "";
/// <summary>
/// The class of the object. An arbitrary string.
/// </summary>
public string Type { get; set; } = "";
/// <summary>
/// The X coordinate of the object in pixels.
/// </summary>
public float X { get; set; } = 0f;
/// <summary>
/// The Y coordinate of the object in pixels.
/// </summary>
public float Y { get; set; } = 0f;
/// <summary>
/// The width of the object in pixels.
/// </summary>
public float Width { get; set; } = 0f;
/// <summary>
/// The height of the object in pixels.
/// </summary>
public float Height { get; set; } = 0f;
/// <summary>
/// The rotation of the object in degrees clockwise around (X, Y).
/// </summary>
public float Rotation { get; set; } = 0f;
/// <summary>
/// Whether the object is shown (true) or hidden (false).
/// </summary>
public bool Visible { get; set; } = true;
/// <summary>
/// A reference to a template file.
/// </summary>
public string? Template { get; set; }
/// <summary>
/// Object properties.
/// </summary>
public List<IProperty> Properties { get; set; } = [];
/// <inheritdoc/>
public override IList<IProperty> GetProperties() => Properties;
}

View file

@ -0,0 +1,7 @@
namespace DotTiled;
/// <summary>
/// A point object in a map. The existing <see cref="Object.X"/> and <see cref="Object.Y"/> properties are used to
/// determine the position of the point.
/// </summary>
public class PointObject : Object { }

View file

@ -0,0 +1,17 @@
using System.Collections.Generic;
using System.Numerics;
namespace DotTiled;
/// <summary>
/// A polygon object in a map. The existing <see cref="Object.X"/> and <see cref="Object.Y"/> properties are used as
/// the origin of the polygon.
/// </summary>
public class PolygonObject : Object
{
/// <summary>
/// The points that make up the polygon.
/// <see cref="Object.X"/> and <see cref="Object.Y"/> are used as the origin of the polygon.
/// </summary>
public required List<Vector2> Points { get; set; }
}

View file

@ -0,0 +1,16 @@
using System.Collections.Generic;
using System.Numerics;
namespace DotTiled;
/// <summary>
/// A polyline object in a map. The existing <see cref="Object.X"/> and <see cref="Object.Y"/> properties are used as
/// the origin of the polyline.
/// </summary>
public class PolylineObject : Object
{
/// <summary>
/// The points that make up the polyline. <see cref="Object.X"/> and <see cref="Object.Y"/> are used as the origin of the polyline.
/// </summary>
public required List<Vector2> Points { get; set; }
}

View file

@ -0,0 +1,7 @@
namespace DotTiled;
/// <summary>
/// A rectangle object in a map. The existing <see cref="Object.X"/>, <see cref="Object.Y"/>, <see cref="Object.Width"/>,
/// and <see cref="Object.Height"/> properties are used to determine the size of the rectangle.
/// </summary>
public class RectangleObject : Object { }

View file

@ -0,0 +1,116 @@
using System.Globalization;
namespace DotTiled;
/// <summary>
/// The horizontal alignment of text.
/// </summary>
public enum TextHorizontalAlignment
{
/// <summary>
/// The text is aligned to the left.
/// </summary>
Left,
/// <summary>
/// The text is aligned to the center.
/// </summary>
Center,
/// <summary>
/// The text is aligned to the right.
/// </summary>
Right,
/// <summary>
/// The text is justified.
/// </summary>
Justify
}
/// <summary>
/// The vertical alignment of text.
/// </summary>
public enum TextVerticalAlignment
{
/// <summary>
/// The text is aligned to the top.
/// </summary>
Top,
/// <summary>
/// The text is aligned to the center.
/// </summary>
Center,
/// <summary>
/// The text is aligned to the bottom.
/// </summary>
Bottom
}
/// <summary>
/// A text object in a map.
/// </summary>
public class TextObject : Object
{
/// <summary>
/// The font family used for the text.
/// </summary>
public string FontFamily { get; set; } = "sans-serif";
/// <summary>
/// The size of the font in pixels.
/// </summary>
public int PixelSize { get; set; } = 16;
/// <summary>
/// Whether word wrapping is enabled.
/// </summary>
public bool Wrap { get; set; } = false;
/// <summary>
/// The color of the text.
/// </summary>
public Color Color { get; set; } = Color.Parse("#000000", CultureInfo.InvariantCulture);
/// <summary>
/// Whether the text is bold.
/// </summary>
public bool Bold { get; set; } = false;
/// <summary>
/// Whether the text is italic.
/// </summary>
public bool Italic { get; set; } = false;
/// <summary>
/// Whether a line should be drawn below the text.
/// </summary>
public bool Underline { get; set; } = false;
/// <summary>
/// Whether a line should be drawn through the text.
/// </summary>
public bool Strikeout { get; set; } = false;
/// <summary>
/// Whether kerning should be used while rendering the text.
/// </summary>
public bool Kerning { get; set; } = true;
/// <summary>
/// The horizontal alignment of the text.
/// </summary>
public TextHorizontalAlignment HorizontalAlignment { get; set; } = TextHorizontalAlignment.Left;
/// <summary>
/// The vertical alignment of the text.
/// </summary>
public TextVerticalAlignment VerticalAlignment { get; set; } = TextVerticalAlignment.Top;
/// <summary>
/// The text to be displayed.
/// </summary>
public string Text { get; set; } = "";
}

View file

@ -0,0 +1,12 @@
namespace DotTiled;
/// <summary>
/// A tile object in a map.
/// </summary>
public class TileObject : Object
{
/// <summary>
/// A reference to a tile.
/// </summary>
public uint GID { get; set; }
}

View file

@ -0,0 +1,32 @@
namespace DotTiled;
/// <summary>
/// Represents a tile layer in a map.
/// </summary>
public class TileLayer : BaseLayer
{
/// <summary>
/// The X coordinate of the layer in tiles.
/// </summary>
public uint X { get; set; } = 0;
/// <summary>
/// The Y coordinate of the layer in tiles.
/// </summary>
public uint Y { get; set; } = 0;
/// <summary>
/// The width of the layer in tiles. Always the same as the map width for fixed-size maps.
/// </summary>
public required uint Width { get; set; }
/// <summary>
/// The height of the layer in tiles. Always the same as the map height for fixed-size maps.
/// </summary>
public required uint Height { get; set; }
/// <summary>
/// The tile layer data.
/// </summary>
public Data? Data { get; set; }
}

208
src/DotTiled/Map.cs Normal file
View file

@ -0,0 +1,208 @@
using System.Collections.Generic;
using System.Globalization;
namespace DotTiled;
/// <summary>
/// Map orientation enumeration. The map orientation determines the alignment of the tiles in the map.
/// </summary>
public enum MapOrientation
{
/// <summary>
/// Orthogonal orientation. This is the typical top-down grid-based layout.
/// </summary>
Orthogonal,
/// <summary>
/// Isometric orientation. This is a type of axonometric projection where the tiles are shown as rhombuses, as seen from a side-on view.
/// </summary>
Isometric,
/// <summary>
/// Staggered orientation. This is an isometric projection with a side-on view where the tiles are arranged in a staggered grid.
/// </summary>
Staggered,
/// <summary>
/// Hexagonal orientation. This is a type of axial projection where the tiles are shown as hexagons, as seen from a top-down view.
/// </summary>
Hexagonal
}
/// <summary>
/// Render order enumeration. The order in which tiles on tile layers are rendered.
/// </summary>
public enum RenderOrder
{
/// <summary>
/// Right-down render order. Starts at top-left and proceeds right then down.
/// </summary>
RightDown,
/// <summary>
/// Right-up render order. Starts at bottom-left and proceeds right then up.
/// </summary>
RightUp,
/// <summary>
/// Left-down render order. Starts at top-right and proceeds left then down.
/// </summary>
LeftDown,
/// <summary>
/// Left-up render order. Starts at bottom-right and proceeds left then up.
/// </summary>
LeftUp
}
/// <summary>
/// Stagger axis enumeration. For staggered and hexagonal maps, determines which axis (X or Y) is staggered.
/// </summary>
public enum StaggerAxis
{
/// <summary>
/// X stagger axis.
/// </summary>
X,
/// <summary>
/// Y stagger axis.
/// </summary>
Y
}
/// <summary>
/// Stagger index enumeration. For staggered and hexagonal maps, determines whether the "even" or "odd" indexes along the staggered axis are shifted.
/// </summary>
public enum StaggerIndex
{
/// <summary>
/// Even stagger index.
/// </summary>
Odd,
/// <summary>
/// Odd stagger index.
/// </summary>
Even
}
/// <summary>
/// Represents a Tiled map.
/// </summary>
public class Map : HasPropertiesBase
{
/// <summary>
/// The TMX format version. Is incremented to match minor Tiled releases.
/// </summary>
public required string Version { get; set; }
/// <summary>
/// The Tiled version used to save the file.
/// </summary>
public required string TiledVersion { get; set; }
/// <summary>
/// The class of this map.
/// </summary>
public string Class { get; set; } = "";
/// <summary>
/// Map orientation.
/// </summary>
public required MapOrientation Orientation { get; set; }
/// <summary>
/// The order in which tiles on tile layers are rendered.
/// </summary>
public RenderOrder RenderOrder { get; set; } = RenderOrder.RightDown;
/// <summary>
/// The compression level to use for tile layer data (defaults to -1, which means to use the algorithm default).
/// Typically only useful for parsing, but may be interesting for certain use cases.
/// </summary>
public int CompressionLevel { get; set; } = -1;
/// <summary>
/// The width of the map in tiles.
/// </summary>
public required uint Width { get; set; }
/// <summary>
/// The height of the map in tiles.
/// </summary>
public required uint Height { get; set; }
/// <summary>
/// The width of a tile.
/// </summary>
public required uint TileWidth { get; set; }
/// <summary>
/// The height of a tile.
/// </summary>
public required uint TileHeight { get; set; }
/// <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; }
/// <summary>
/// For staggered and hexagonal maps, determines which axis (X or Y) is staggered.
/// </summary>
public StaggerAxis? StaggerAxis { get; set; }
/// <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; }
/// <summary>
/// X coordinate of the parallax origin in pixels.
/// </summary>
public float ParallaxOriginX { get; set; } = 0.0f;
/// <summary>
/// Y coordinate of the parallax origin in pixels.
/// </summary>
public float ParallaxOriginY { get; set; } = 0.0f;
/// <summary>
/// The background color of the map.
/// </summary>
public Color BackgroundColor { get; set; } = Color.Parse("#00000000", CultureInfo.InvariantCulture);
/// <summary>
/// Stores the next available ID for new layers. This number is used to prevent reuse of the same ID after layers have been removed.
/// </summary>
public required uint NextLayerID { get; set; }
/// <summary>
/// Stores the next available ID for new objects. This number is used to prevent reuse of the same ID after objects have been removed.
/// </summary>
public required uint NextObjectID { get; set; }
/// <summary>
/// Whether this map is infinite. An infinite map has no fixed size and can grow in all directions. Its layer data is stored in chunks.
/// </summary>
public bool Infinite { get; set; } = false;
/// <summary>
/// Map properties.
/// </summary>
public List<IProperty> Properties { get; set; } = [];
/// <inheritdoc/>
public override IList<IProperty> GetProperties() => Properties;
/// <summary>
/// List of tilesets used by the map.
/// </summary>
public List<Tileset> Tilesets { get; set; } = [];
/// <summary>
/// Hierarchical list of layers. <see cref="Group"/> is a layer type which can contain sub-layers to create a hierarchy.
/// </summary>
public List<BaseLayer> Layers { get; set; } = [];
}

View file

@ -1,22 +0,0 @@
using System.Collections.Generic;
using DotTiled.Model.Properties;
namespace DotTiled.Model.Layers;
public abstract class BaseLayer
{
// Attributes
public required uint ID { get; set; }
public string Name { get; set; } = "";
public string Class { get; set; } = "";
public float Opacity { get; set; } = 1.0f;
public bool Visible { get; set; } = true;
public Color? TintColor { get; set; }
public float OffsetX { get; set; } = 0.0f;
public float OffsetY { get; set; } = 0.0f;
public float ParallaxX { get; set; } = 1.0f;
public float ParallaxY { get; set; } = 1.0f;
// At most one of
public Dictionary<string, IProperty>? Properties { get; set; }
}

View file

@ -1,51 +0,0 @@
using System;
namespace DotTiled.Model.Layers;
public enum DataEncoding
{
Csv,
Base64
}
public enum DataCompression
{
GZip,
ZLib,
ZStd
}
[Flags]
public enum FlippingFlags : uint
{
None = 0,
FlippedHorizontally = 0x80000000u,
FlippedVertically = 0x40000000u,
FlippedDiagonally = 0x20000000u,
RotatedHexagonal120 = 0x10000000u
}
public class Chunk
{
// Attributes
public required int X { get; set; }
public required int Y { get; set; }
public required uint Width { get; set; }
public required uint Height { get; set; }
// Data
public required uint[] GlobalTileIDs { get; set; }
public required FlippingFlags[] FlippingFlags { get; set; }
}
public class Data
{
// Attributes
public DataEncoding? Encoding { get; set; }
public DataCompression? Compression { get; set; }
// Data
public uint[]? GlobalTileIDs { get; set; }
public FlippingFlags[]? FlippingFlags { get; set; }
public Chunk[]? Chunks { get; set; }
}

View file

@ -1,11 +0,0 @@
using System.Collections.Generic;
namespace DotTiled.Model.Layers;
public class Group : BaseLayer
{
// Uses same attributes as BaseLayer
// Any number of
public List<BaseLayer> Layers { get; set; } = [];
}

View file

@ -1,15 +0,0 @@
using DotTiled.Model.Tilesets;
namespace DotTiled.Model.Layers;
public class ImageLayer : BaseLayer
{
// Attributes
public uint X { get; set; } = 0;
public uint Y { get; set; } = 0;
public bool RepeatX { get; set; } = false;
public bool RepeatY { get; set; } = false;
// At most one of
public Image? Image { get; set; }
}

View file

@ -1,24 +0,0 @@
using System.Collections.Generic;
using DotTiled.Model.Layers.Objects;
namespace DotTiled.Model.Layers;
public enum DrawOrder
{
TopDown,
Index
}
public class ObjectLayer : BaseLayer
{
// Attributes
public uint X { get; set; } = 0;
public uint Y { get; set; } = 0;
public uint? Width { get; set; }
public uint? Height { get; set; }
public Color? Color { get; set; }
public DrawOrder DrawOrder { get; set; } = DrawOrder.TopDown;
// Elements
public required List<Object> Objects { get; set; }
}

View file

@ -1,3 +0,0 @@
namespace DotTiled.Model.Layers.Objects;
public class EllipseObject : Object { }

View file

@ -1,22 +0,0 @@
using System.Collections.Generic;
using DotTiled.Model.Properties;
namespace DotTiled.Model.Layers.Objects;
public abstract class Object
{
// Attributes
public uint? ID { get; set; }
public string Name { get; set; } = "";
public string Type { get; set; } = "";
public float X { get; set; } = 0f;
public float Y { get; set; } = 0f;
public float Width { get; set; } = 0f;
public float Height { get; set; } = 0f;
public float Rotation { get; set; } = 0f;
public bool Visible { get; set; } = true;
public string? Template { get; set; }
// Elements
public Dictionary<string, IProperty>? Properties { get; set; }
}

View file

@ -1,3 +0,0 @@
namespace DotTiled.Model.Layers.Objects;
public class PointObject : Object { }

View file

@ -1,10 +0,0 @@
using System.Collections.Generic;
using System.Numerics;
namespace DotTiled.Model.Layers.Objects;
public class PolygonObject : Object
{
// Attributes
public required List<Vector2> Points { get; set; }
}

View file

@ -1,10 +0,0 @@
using System.Collections.Generic;
using System.Numerics;
namespace DotTiled.Model.Layers.Objects;
public class PolylineObject : Object
{
// Attributes
public required List<Vector2> Points { get; set; }
}

View file

@ -1,3 +0,0 @@
namespace DotTiled.Model.Layers.Objects;
public class RectangleObject : Object { }

View file

@ -1,38 +0,0 @@
using System.Globalization;
namespace DotTiled.Model.Layers.Objects;
public enum TextHorizontalAlignment
{
Left,
Center,
Right,
Justify
}
public enum TextVerticalAlignment
{
Top,
Center,
Bottom
}
public class TextObject : Object
{
// Attributes
public string FontFamily { get; set; } = "sans-serif";
public int PixelSize { get; set; } = 16;
public bool Wrap { get; set; } = false;
public Color Color { get; set; } = Color.Parse("#000000", CultureInfo.InvariantCulture);
public bool Bold { get; set; } = false;
public bool Italic { get; set; } = false;
public bool Underline { get; set; } = false;
public bool Strikeout { get; set; } = false;
public bool Kerning { get; set; } = true;
public TextHorizontalAlignment HorizontalAlignment { get; set; } = TextHorizontalAlignment.Left;
public TextVerticalAlignment VerticalAlignment { get; set; } = TextVerticalAlignment.Top;
// Elements
public string Text { get; set; } = "";
}

View file

@ -1,6 +0,0 @@
namespace DotTiled.Model.Layers.Objects;
public class TileObject : Object
{
public uint GID { get; set; }
}

View file

@ -1,13 +0,0 @@
namespace DotTiled.Model.Layers;
public class TileLayer : BaseLayer
{
// Attributes
public uint X { get; set; } = 0;
public uint Y { get; set; } = 0;
public required uint Width { get; set; }
public required uint Height { get; set; }
// At most one of
public Data? Data { get; set; }
}

View file

@ -1,66 +0,0 @@
using System.Collections.Generic;
using System.Globalization;
using DotTiled.Model.Layers;
using DotTiled.Model.Properties;
using DotTiled.Model.Tilesets;
namespace DotTiled.Model;
public enum MapOrientation
{
Orthogonal,
Isometric,
Staggered,
Hexagonal
}
public enum RenderOrder
{
RightDown,
RightUp,
LeftDown,
LeftUp
}
public enum StaggerAxis
{
X,
Y
}
public enum StaggerIndex
{
Odd,
Even
}
public class Map
{
// Attributes
public required string Version { get; set; }
public required string TiledVersion { get; set; }
public string Class { get; set; } = "";
public required MapOrientation Orientation { get; set; }
public RenderOrder RenderOrder { get; set; } = RenderOrder.RightDown;
public int CompressionLevel { get; set; } = -1;
public required uint Width { get; set; }
public required uint Height { get; set; }
public required uint TileWidth { get; set; }
public required uint TileHeight { get; set; }
public uint? HexSideLength { get; set; }
public StaggerAxis? StaggerAxis { get; set; }
public StaggerIndex? StaggerIndex { get; set; }
public float ParallaxOriginX { get; set; } = 0.0f;
public float ParallaxOriginY { get; set; } = 0.0f;
public Color BackgroundColor { get; set; } = Color.Parse("#00000000", CultureInfo.InvariantCulture);
public required uint NextLayerID { get; set; }
public required uint NextObjectID { get; set; }
public bool Infinite { get; set; } = false;
// At most one of
public Dictionary<string, IProperty>? Properties { get; set; }
// Any number of
public List<Tileset> Tilesets { get; set; } = [];
public List<BaseLayer> Layers { get; set; } = [];
}

View file

@ -1,14 +0,0 @@
namespace DotTiled.Model.Properties;
public class BoolProperty : IProperty
{
public required string Name { get; set; }
public PropertyType Type => PropertyType.Bool;
public required bool Value { get; set; }
public IProperty Clone() => new BoolProperty
{
Name = Name,
Value = Value
};
}

View file

@ -1,19 +0,0 @@
using System.Collections.Generic;
using System.Linq;
namespace DotTiled.Model.Properties;
public class ClassProperty : IProperty
{
public required string Name { get; set; }
public PropertyType Type => Model.Properties.PropertyType.Class;
public required string PropertyType { get; set; }
public required Dictionary<string, IProperty> Properties { get; set; }
public IProperty Clone() => new ClassProperty
{
Name = Name,
PropertyType = PropertyType,
Properties = Properties.ToDictionary(p => p.Key, p => p.Value.Clone())
};
}

View file

@ -1,14 +0,0 @@
namespace DotTiled.Model.Properties;
public class ColorProperty : IProperty
{
public required string Name { get; set; }
public PropertyType Type => PropertyType.Color;
public required Color Value { get; set; }
public IProperty Clone() => new ColorProperty
{
Name = Name,
Value = Value
};
}

View file

@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
namespace DotTiled.Model.Properties.CustomTypes;
[Flags]
public enum CustomClassUseAs
{
Property,
Map,
Layer,
Object,
Tile,
Tileset,
WangColor,
Wangset,
Project,
All = Property | Map | Layer | Object | Tile | Tileset | WangColor | Wangset | Project
}
public class CustomClassDefinition : CustomTypeDefinition
{
public Color? Color { get; set; }
public bool DrawFill { get; set; }
public CustomClassUseAs UseAs { get; set; }
public List<IProperty> Members { get; set; } = [];
}

View file

@ -1,16 +0,0 @@
using System.Collections.Generic;
namespace DotTiled.Model.Properties.CustomTypes;
public enum CustomEnumStorageType
{
Int,
String
}
public class CustomEnumDefinition : CustomTypeDefinition
{
public CustomEnumStorageType StorageType { get; set; }
public List<string> Values { get; set; } = [];
public bool ValueAsFlags { get; set; }
}

View file

@ -1,7 +0,0 @@
namespace DotTiled.Model.Properties.CustomTypes;
public abstract class CustomTypeDefinition
{
public uint ID { get; set; }
public string Name { get; set; } = "";
}

View file

@ -1,14 +0,0 @@
namespace DotTiled.Model.Properties;
public class FileProperty : IProperty
{
public required string Name { get; set; }
public PropertyType Type => PropertyType.File;
public required string Value { get; set; }
public IProperty Clone() => new FileProperty
{
Name = Name,
Value = Value
};
}

View file

@ -1,14 +0,0 @@
namespace DotTiled.Model.Properties;
public class FloatProperty : IProperty
{
public required string Name { get; set; }
public PropertyType Type => PropertyType.Float;
public required float Value { get; set; }
public IProperty Clone() => new FloatProperty
{
Name = Name,
Value = Value
};
}

View file

@ -1,9 +0,0 @@
namespace DotTiled.Model.Properties;
public interface IProperty
{
public string Name { get; set; }
public PropertyType Type { get; }
IProperty Clone();
}

View file

@ -1,14 +0,0 @@
namespace DotTiled.Model.Properties;
public class IntProperty : IProperty
{
public required string Name { get; set; }
public PropertyType Type => PropertyType.Int;
public required int Value { get; set; }
public IProperty Clone() => new IntProperty
{
Name = Name,
Value = Value
};
}

View file

@ -1,14 +0,0 @@
namespace DotTiled.Model.Properties;
public class ObjectProperty : IProperty
{
public required string Name { get; set; }
public PropertyType Type => PropertyType.Object;
public required uint Value { get; set; }
public IProperty Clone() => new ObjectProperty
{
Name = Name,
Value = Value
};
}

View file

@ -1,13 +0,0 @@
namespace DotTiled.Model.Properties;
public enum PropertyType
{
String,
Int,
Float,
Bool,
Color,
File,
Object,
Class
}

View file

@ -1,14 +0,0 @@
namespace DotTiled.Model.Properties;
public class StringProperty : IProperty
{
public required string Name { get; set; }
public PropertyType Type => PropertyType.String;
public required string Value { get; set; }
public IProperty Clone() => new StringProperty
{
Name = Name,
Value = Value
};
}

View file

@ -1,11 +0,0 @@
using DotTiled.Model.Layers.Objects;
using DotTiled.Model.Tilesets;
namespace DotTiled.Model;
public class Template
{
// At most one of (if the template is a tile object)
public Tileset? Tileset { get; set; }
public required Object Object { get; set; }
}

View file

@ -1,8 +0,0 @@
namespace DotTiled.Model.Tilesets;
public class Frame
{
// Attributes
public required uint TileID { get; set; }
public required uint Duration { get; set; }
}

View file

@ -1,15 +0,0 @@
namespace DotTiled.Model.Tilesets;
public enum GridOrientation
{
Orthogonal,
Isometric
}
public class Grid
{
// Attributes
public GridOrientation Orientation { get; set; } = GridOrientation.Orthogonal;
public required uint Width { get; set; }
public required uint Height { get; set; }
}

View file

@ -1,19 +0,0 @@
namespace DotTiled.Model.Tilesets;
public enum ImageFormat
{
Png,
Gif,
Jpg,
Bmp
}
public class Image
{
// Attributes
public ImageFormat? Format { get; set; }
public string? Source { get; set; }
public Color? TransparentColor { get; set; }
public uint? Width { get; set; }
public uint? Height { get; set; }
}

View file

@ -1,23 +0,0 @@
using System.Collections.Generic;
using DotTiled.Model.Layers;
using DotTiled.Model.Properties;
namespace DotTiled.Model.Tilesets;
public class Tile
{
// Attributes
public required uint ID { get; set; }
public string Type { get; set; } = "";
public float Probability { get; set; } = 0f;
public uint X { get; set; } = 0;
public uint Y { get; set; } = 0;
public required uint Width { get; set; }
public required uint Height { get; set; }
// Elements
public Dictionary<string, IProperty>? Properties { get; set; }
public Image? Image { get; set; }
public ObjectLayer? ObjectLayer { get; set; }
public List<Frame>? Animation { get; set; }
}

View file

@ -1,8 +0,0 @@
namespace DotTiled.Model.Tilesets;
public class TileOffset
{
// Attributes
public float X { get; set; } = 0f;
public float Y { get; set; } = 0f;
}

View file

@ -1,62 +0,0 @@
using System.Collections.Generic;
using DotTiled.Model.Properties;
namespace DotTiled.Model.Tilesets;
public enum ObjectAlignment
{
Unspecified,
TopLeft,
Top,
TopRight,
Left,
Center,
Right,
BottomLeft,
Bottom,
BottomRight
}
public enum TileRenderSize
{
Tile,
Grid
}
public enum FillMode
{
Stretch,
PreserveAspectFit
}
public class Tileset
{
// Attributes
public string? Version { get; set; }
public string? TiledVersion { get; set; }
public uint? FirstGID { get; set; }
public string? Source { get; set; }
public string? Name { get; set; }
public string Class { get; set; } = "";
public uint? TileWidth { get; set; }
public uint? TileHeight { get; set; }
public float? Spacing { get; set; } = 0f;
public float? Margin { get; set; } = 0f;
public uint? TileCount { get; set; }
public uint? Columns { get; set; }
public ObjectAlignment ObjectAlignment { get; set; } = ObjectAlignment.Unspecified;
public TileRenderSize RenderSize { get; set; } = TileRenderSize.Tile;
public FillMode FillMode { get; set; } = FillMode.Stretch;
// At most one of
public Image? Image { get; set; }
public TileOffset? TileOffset { get; set; }
public Grid? Grid { get; set; }
public Dictionary<string, IProperty>? Properties { get; set; }
// public List<Terrain>? TerrainTypes { get; set; } TODO: Implement Terrain -> Wangset conversion during deserialization
public List<Wangset>? Wangsets { get; set; }
public Transformations? Transformations { get; set; }
// Any number of
public List<Tile> Tiles { get; set; } = [];
}

View file

@ -1,10 +0,0 @@
namespace DotTiled.Model.Tilesets;
public class Transformations
{
// Attributes
public bool HFlip { get; set; } = false;
public bool VFlip { get; set; } = false;
public bool Rotate { get; set; } = false;
public bool PreferUntransformed { get; set; } = false;
}

View file

@ -1,17 +0,0 @@
using System.Collections.Generic;
using DotTiled.Model.Properties;
namespace DotTiled.Model.Tilesets;
public class WangColor
{
// Attributes
public required string Name { get; set; }
public string Class { get; set; } = "";
public required Color Color { get; set; }
public required int Tile { get; set; }
public float Probability { get; set; } = 0f;
// Elements
public Dictionary<string, IProperty>? Properties { get; set; }
}

View file

@ -1,8 +0,0 @@
namespace DotTiled.Model.Tilesets;
public class WangTile
{
// Attributes
public required uint TileID { get; set; }
public required byte[] WangID { get; set; }
}

View file

@ -1,22 +0,0 @@
using System.Collections.Generic;
using DotTiled.Model.Properties;
namespace DotTiled.Model.Tilesets;
public class Wangset
{
// Attributes
public required string Name { get; set; }
public string Class { get; set; } = "";
public required int Tile { get; set; }
// Elements
// At most one of
public Dictionary<string, IProperty>? Properties { get; set; }
// Up to 254 Wang colors
public List<WangColor>? WangColors { get; set; } = [];
// Any number of
public List<WangTile> WangTiles { get; set; } = [];
}

View file

@ -0,0 +1,25 @@
namespace DotTiled;
/// <summary>
/// Represents a boolean property.
/// </summary>
public class BoolProperty : IProperty<bool>
{
/// <inheritdoc/>
public required string Name { get; set; }
/// <inheritdoc/>
public PropertyType Type => PropertyType.Bool;
/// <summary>
/// The boolean value of the property.
/// </summary>
public required bool Value { get; set; }
/// <inheritdoc/>
public IProperty Clone() => new BoolProperty
{
Name = Name,
Value = Value
};
}

View file

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace DotTiled;
/// <summary>
/// Represents a class property.
/// </summary>
public class ClassProperty : IHasProperties, IProperty<IList<IProperty>>
{
/// <inheritdoc/>
public required string Name { get; set; }
/// <inheritdoc/>
public PropertyType Type => DotTiled.PropertyType.Class;
/// <summary>
/// The type of the class property. This will be the name of a custom defined
/// type in Tiled.
/// </summary>
public required string PropertyType { get; set; }
/// <summary>
/// The properties of the class property.
/// </summary>
public required IList<IProperty> Value { get; set; }
/// <inheritdoc/>
public IProperty Clone() => new ClassProperty
{
Name = Name,
PropertyType = PropertyType,
Value = Value.Select(property => property.Clone()).ToList()
};
/// <inheritdoc/>
public IList<IProperty> GetProperties() => Value;
/// <inheritdoc/>
public T GetProperty<T>(string name) where T : IProperty
{
var property = Value.FirstOrDefault(_properties => _properties.Name == name) ?? throw new InvalidOperationException($"Property '{name}' not found.");
if (property is T prop)
{
return prop;
}
throw new InvalidOperationException($"Property '{name}' is not of type '{typeof(T).Name}'.");
}
/// <inheritdoc/>
public bool TryGetProperty<T>(string name, [NotNullWhen(true)] out T? property) where T : IProperty
{
if (Value.FirstOrDefault(_properties => _properties.Name == name) is T prop)
{
property = prop;
return true;
}
property = default;
return false;
}
}

Some files were not shown because too many files have changed in this diff Show more