diff --git a/CHANGELOG.md b/CHANGELOG.md index fe131d46..f4e87efa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] +### Added +- Support for Wang sets. + ## [0.10.2] ### Added - Map-wrapped chunks: `ChunkWrapper`. diff --git a/assets/tiled_csv_wangsets.tmx b/assets/tiled_csv_wangsets.tmx new file mode 100644 index 00000000..91aca7bb --- /dev/null +++ b/assets/tiled_csv_wangsets.tmx @@ -0,0 +1,28 @@ + + + + + +47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47, +47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47, +47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47, +47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47, +47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47, +47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47, +47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47, +47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47, +47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47, +47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47, +47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47, +47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47, +47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47, +47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47, +47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47, +47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47, +47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47, +47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47, +47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47, +47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47 + + + diff --git a/assets/tilesheet_wangsets.tsx b/assets/tilesheet_wangsets.tsx new file mode 100644 index 00000000..3e6247f1 --- /dev/null +++ b/assets/tilesheet_wangsets.tsx @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/error.rs b/src/error.rs index 32b06fab..66c1f5d1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -49,6 +49,11 @@ pub enum Error { /// Supported types are `string`, `int`, `float`, `bool`, `color`, `file` and `object`. type_name: String, }, + /// Found a WangId that was not properly formatted. + InvalidWangIdEncoding { + /// Stores the wrongly parsed String. + read_string: String, + }, } /// A result with an error variant of [`crate::Error`]. @@ -93,6 +98,8 @@ impl fmt::Display for Error { write!(fmt, "Invalid property value: {}", description), Error::UnknownPropertyType { type_name } => write!(fmt, "Unknown property value type '{}'", type_name), + Error::InvalidWangIdEncoding{read_string} => + write!(fmt, "\"{}\" is not a valid WangId format", read_string), } } } diff --git a/src/tileset.rs b/src/tileset.rs index 441be445..ed3d06c8 100644 --- a/src/tileset.rs +++ b/src/tileset.rs @@ -10,6 +10,9 @@ use crate::properties::{parse_properties, Properties}; use crate::tile::TileData; use crate::{util::*, Gid, Tile, TileId}; +mod wangset; +pub use wangset::{WangColor, WangId, WangSet, WangTile}; + /// A collection of tiles for usage in maps and template objects. /// /// Also see the [TMX docs](https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#tileset). @@ -53,6 +56,9 @@ pub struct Tileset { /// All the tiles present in this tileset, indexed by their local IDs. tiles: HashMap, + /// All the wangsets present in this tileset. + pub wang_sets: Vec, + /// The custom properties of the tileset. pub properties: Properties, } @@ -235,6 +241,7 @@ impl Tileset { let mut image = Option::None; let mut tiles = HashMap::with_capacity(prop.tilecount as usize); let mut properties = HashMap::new(); + let mut wang_sets = Vec::new(); parse_tag!(parser, "tileset", { "image" => |attrs| { @@ -250,6 +257,11 @@ impl Tileset { tiles.insert(id, tile); Ok(()) }, + "wangset" => |attrs| { + let set = WangSet::new(parser, attrs)?; + wang_sets.push(set); + Ok(()) + }, }); // A tileset is considered an image collection tileset if there is no image attribute (because its tiles do). @@ -278,6 +290,7 @@ impl Tileset { tilecount: prop.tilecount, image, tiles, + wang_sets, properties, }) } diff --git a/src/tileset/wangset.rs b/src/tileset/wangset.rs new file mode 100644 index 00000000..6b84f288 --- /dev/null +++ b/src/tileset/wangset.rs @@ -0,0 +1,101 @@ +use std::collections::HashMap; + +use xml::attribute::OwnedAttribute; + +use crate::{ + error::Error, + properties::{parse_properties, Properties}, + util::{get_attrs, parse_tag, XmlEventResult}, + Result, TileId, +}; + +mod wang_color; +pub use wang_color::WangColor; +mod wang_tile; +pub use wang_tile::{WangId, WangTile}; + +/// Wang set's terrain brush connection type. +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum WangSetType { + Corner, + Edge, + Mixed, +} + +impl Default for WangSetType { + fn default() -> Self { + WangSetType::Mixed + } +} + +/// Raw data belonging to a WangSet. +#[derive(Debug, PartialEq, Clone)] +pub struct WangSet { + /// The name of the Wang set. + pub name: String, + /// Type of Wang set. + pub wang_set_type: WangSetType, + /// The tile ID of the tile representing this Wang set. + pub tile: Option, + /// The colors color that can be used to define the corner and/or edge of each Wang tile. + pub wang_colors: Vec, + /// All the Wang tiles present in this Wang set, indexed by their local IDs. + pub wang_tiles: HashMap, + /// The custom properties of this Wang set. + pub properties: Properties, +} + +impl WangSet { + /// Reads data from XML parser to create a WangSet. + pub fn new( + parser: &mut impl Iterator, + attrs: Vec, + ) -> Result { + // Get common data + let (name, wang_set_type, tile) = get_attrs!( + for v in attrs { + "name" => name ?= v.parse::(), + "type" => wang_set_type ?= v.parse::(), + "tile" => tile ?= v.parse::(), + } + (name, wang_set_type, tile) + ); + + let wang_set_type = match wang_set_type.as_str() { + "corner" => WangSetType::Corner, + "edge" => WangSetType::Edge, + _ => WangSetType::default(), + }; + let tile = if tile >= 0 { Some(tile as u32) } else { None }; + + // Gather variable data + let mut wang_colors = Vec::new(); + let mut wang_tiles = HashMap::new(); + let mut properties = HashMap::new(); + parse_tag!(parser, "wangset", { + "wangcolor" => |attrs| { + let color = WangColor::new(parser, attrs)?; + wang_colors.push(color); + Ok(()) + }, + "wangtile" => |attrs| { + let (id, t) = WangTile::new(parser, attrs)?; + wang_tiles.insert(id, t); + Ok(()) + }, + "properties" => |_| { + properties = parse_properties(parser)?; + Ok(()) + }, + }); + + Ok(WangSet { + name, + wang_set_type, + tile, + wang_colors, + wang_tiles, + properties, + }) + } +} diff --git a/src/tileset/wangset/wang_color.rs b/src/tileset/wangset/wang_color.rs new file mode 100644 index 00000000..21a274b5 --- /dev/null +++ b/src/tileset/wangset/wang_color.rs @@ -0,0 +1,63 @@ +use std::collections::HashMap; + +use xml::attribute::OwnedAttribute; + +use crate::{ + error::Error, + properties::{parse_properties, Color, Properties}, + util::{get_attrs, parse_tag, XmlEventResult}, + Result, TileId, +}; + +/// Stores the data of the Wang color. +#[derive(Debug, PartialEq, Clone)] +pub struct WangColor { + /// The name of this color. + pub name: String, + #[allow(missing_docs)] + pub color: Color, + /// The tile ID of the tile representing this color. + pub tile: Option, + /// The relative probability that this color is chosen over others in case of multiple options. (defaults to 0) + pub probability: f32, + /// The custom properties of this color. + pub properties: Properties, +} + +impl WangColor { + /// Reads data from XML parser to create a WangColor. + pub fn new( + parser: &mut impl Iterator, + attrs: Vec, + ) -> Result { + // Get common data + let (name, color, tile, probability) = get_attrs!( + for v in attrs { + "name" => name ?= v.parse::(), + "color" => color ?= v.parse(), + "tile" => tile ?= v.parse::(), + "probability" => probability ?= v.parse::(), + } + (name, color, tile, probability) + ); + + let tile = if tile >= 0 { Some(tile as u32) } else { None }; + + // Gather variable data + let mut properties = HashMap::new(); + parse_tag!(parser, "wangcolor", { + "properties" => |_| { + properties = parse_properties(parser)?; + Ok(()) + }, + }); + + Ok(WangColor { + name, + color, + tile, + probability, + properties, + }) + } +} diff --git a/src/tileset/wangset/wang_tile.rs b/src/tileset/wangset/wang_tile.rs new file mode 100644 index 00000000..d0c591fb --- /dev/null +++ b/src/tileset/wangset/wang_tile.rs @@ -0,0 +1,64 @@ +use std::str::FromStr; + +use xml::attribute::OwnedAttribute; + +use crate::{ + error::Error, + util::{get_attrs, XmlEventResult}, + Result, TileId, +}; + +/** +The Wang ID, stored as an array of 8 u8 values. +*/ +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct WangId(pub [u8; 8]); + +impl FromStr for WangId { + type Err = Error; + + fn from_str(s: &str) -> std::result::Result { + let mut ret = [0u8; 8]; + let values: Vec<&str> = s + .trim_start_matches('[') + .trim_end_matches(']') + .split(',') + .collect(); + if values.len() != 8 { + return Err(Error::InvalidWangIdEncoding { + read_string: s.to_string(), + }); + } + for i in 0..8 { + ret[i] = values[i].parse::().unwrap_or(0); + } + + Ok(WangId(ret)) + } +} + +/// Stores the Wang ID. +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct WangTile { + #[allow(missing_docs)] + pub wang_id: WangId, +} + +impl WangTile { + /// Reads data from XML parser to create a WangTile. + pub(crate) fn new( + _parser: &mut impl Iterator, + attrs: Vec, + ) -> Result<(TileId, WangTile)> { + // Get common data + let (tile_id, wang_id) = get_attrs!( + for v in attrs { + "tileid" => tile_id ?= v.parse::(), + "wangid" => wang_id ?= v.parse(), + } + (tile_id, wang_id) + ); + + Ok((tile_id, WangTile { wang_id })) + } +} diff --git a/tests/lib.rs b/tests/lib.rs index c0faf29e..1dac048c 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use tiled::{ Color, FiniteTileLayer, GroupLayer, Layer, LayerType, Loader, Map, ObjectLayer, PropertyValue, - ResourceCache, TileLayer, + ResourceCache, TileLayer, WangId, }; fn as_tile_layer<'map>(layer: Layer<'map>) -> TileLayer<'map> { @@ -419,3 +419,23 @@ fn test_group_layers() { layer_tile_3.properties.get("key") ); } + +#[test] +fn test_reading_wang_sets() { + let mut loader = Loader::new(); + let map = loader + .load_tmx_map("assets/tiled_csv_wangsets.tmx") + .unwrap(); + + // We will pick some random data from the wangsets for tessting + let tileset = map.tilesets().get(0).unwrap(); + assert_eq!(tileset.wang_sets.len(), 3); + let wangset_2 = tileset.wang_sets.get(1).unwrap(); + let tile_10 = wangset_2.wang_tiles.get(&10).unwrap(); + assert_eq!(tile_10.wang_id, WangId([2u8, 2, 0, 2, 0, 2, 2, 2])); + let wangset_3 = tileset.wang_sets.get(2).unwrap(); + let color_2 = wangset_3.wang_colors.get(1).unwrap(); + let readed_damage = color_2.properties.get("Damage").unwrap(); + let damage_value = &PropertyValue::FloatValue(32.1); + assert_eq!(readed_damage, damage_value); +}