From a595ed8d9fbe6ff4a7a6f13c279b5412aeb41ab4 Mon Sep 17 00:00:00 2001 From: LawnCable Date: Fri, 15 Apr 2022 08:37:05 +0200 Subject: layer tiles, draft for object parsing and more --- src/lowlevel/tileset.rs | 208 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 193 insertions(+), 15 deletions(-) (limited to 'src/lowlevel/tileset.rs') diff --git a/src/lowlevel/tileset.rs b/src/lowlevel/tileset.rs index 9db86c8..ab96af4 100644 --- a/src/lowlevel/tileset.rs +++ b/src/lowlevel/tileset.rs @@ -1,5 +1,5 @@ use crate::{ensure_element, ensure_tag_name, get_attribute}; -use anyhow::anyhow; +use anyhow::{anyhow, bail, Context}; use super::image::Image; use super::property::Properties; @@ -49,10 +49,10 @@ impl ObjectAlignment { #[derive(Debug, PartialEq)] pub struct Frame { /// The local ID of a tile within the parent . - tileid: u32, + pub tileid: u32, // How long (in milliseconds) this frame should be displayed before advancing to the next frame. - duration: u32, + pub duration: u32, } impl Frame { @@ -413,7 +413,7 @@ pub struct Terrain { /// The name of the terrain type. pub name: String, /// The local tile-id of the tile that represents the terrain visually. - pub tile: u32, + pub tile: i32, // TODO find out why `-1` is allowed here and maybe if there is a better way to represent it in rust? pub properties: Properties, } @@ -437,7 +437,6 @@ impl Terrain { } } - /// This element is used to describe which transformations can be applied to the tiles /// (e.g. to extend a Wang set by transforming existing tiles). #[derive(PartialEq, Debug)] @@ -538,6 +537,9 @@ mod test_transformations { #[derive(Debug, PartialEq)] pub struct TileSet { + /// The TSX format version. + pub version: Option, + /// The name of this tileset. pub name: String, /// The (maximum) width of the tiles in this tileset. @@ -579,10 +581,10 @@ pub struct TileSet { pub properties: super::property::Properties, - pub terrain_types: Option>, + pub terrain_types: Vec, /// (since 1.1) - pub wangsets: Option>, + pub wangsets: Vec, /// (since 1.5) pub transformations: TileSetTransformations, @@ -637,22 +639,116 @@ impl TileSet { None => TileSetTransformations::default(), }; + let mut wangsets = Vec::new(); + + if let Some(wangsets_node) = node.children().find(|&c| c.has_tag_name("wangsets")) { + for wangset_node in wangsets_node + .children() + .filter(|n| n.has_tag_name("wangset")) + { + wangsets.push(WangSet::from_xml(wangset_node)?); + } + } + + let mut terrain_types = Vec::new(); + + if let Some(terraintypes_node) = node.children().find(|&c| c.has_tag_name("terraintypes")) { + for terrain_node in terraintypes_node + .children() + .filter(|n| n.has_tag_name("terrain")) + { + terrain_types.push(Terrain::from_xml(terrain_node).with_context(|| { + format!("Failed to parse terraintype from:\n{:?}", terrain_node,) + })?); + } + } + + let version = if let Ok(v) = get_attribute!(node, "version") { + Some(v.to_owned()) + } else { + None + }; + + let tile_width = get_attribute!(node, "tilewidth")?.parse()?; + let tile_height = get_attribute!(node, "tileheight")?.parse()?; + + let tile_count: usize = match get_attribute!(node, "tilecount") { + Ok(tc_string) => tc_string.parse()?, + Err(err) => { + if let Some(img) = &image { + // there is no tilecount, but an image so we can guess/calculate the tilecount + // TODO TEST for this + if spacing != 0 { + bail!("tilecount not set, and calculationg it with spacing enabled is not implemented yet."); + } + + if let Some(image_width) = img.width { + if let Some(image_heigth) = img.height { + let real_tile_with = tile_width + (margin * 2); + let real_tile_height = tile_height + (margin * 2); + + let columns = image_width / real_tile_with; + let rows = image_heigth / real_tile_height; + columns * rows + } else { + bail!("tilecount not set, calculating failed: image has no height"); + } + } else { + bail!("tilecount not set, calculating failed: image has no width"); + } + } else { + return Err(err); + } + } + }; + + let columns: usize = match get_attribute!(node, "columns") { + Ok(c) => c.parse()?, + Err(err) => { + if let Some(img) = &image { + // there is no columns attribute, but an image so we can guess/calculate the columns + // TODO TEST for this + if spacing != 0 { + bail!("columns not set, and calculationg it with spacing enabled is not implemented yet."); + } + + if let Some(image_width) = img.width { + if let Some(image_heigth) = img.height { + let real_tile_with = tile_width + (margin * 2); + let real_tile_height = tile_height + (margin * 2); + + let columns = image_width / real_tile_with; + let rows = image_heigth / real_tile_height; + columns * rows + } else { + bail!("columns not set, calculating failed: image has no height"); + } + } else { + bail!("columns not set, calculating failed: image has no width"); + } + } else { + return Err(err); + } + } + }; + Ok(TileSet { + version, name: get_attribute!(node, "name")?.to_owned(), - tile_width: get_attribute!(node, "tilewidth")?.parse()?, - tile_height: get_attribute!(node, "tileheight")?.parse()?, + tile_width, + tile_height, spacing, margin, - tile_count: get_attribute!(node, "tilecount")?.parse()?, - columns: get_attribute!(node, "columns")?.parse()?, + tile_count, + columns, object_alignment, tile: tiles, image, tile_offset, grid, properties, - terrain_types: None, // TODO - wangsets: None, // TODO + terrain_types, + wangsets, transformations, }) } @@ -673,6 +769,7 @@ mod test_tileset { assert_eq!( tile_set, TileSet { + version: None, name: "beach_tileset".to_owned(), tile_width: 16, tile_height: 16, @@ -742,8 +839,8 @@ mod test_tileset { tile_offset: TileOffset::default(), grid: None, properties: Properties::default(), - terrain_types: None, - wangsets: None, + terrain_types: vec![], + wangsets: vec![], transformations: TileSetTransformations::default() } ); @@ -752,7 +849,88 @@ mod test_tileset { // todo more tests } +/// represents a tileset element inside of a tilemap +#[derive(Debug, PartialEq)] pub enum TileSetElement { Embedded { first_gid: usize, tileset: TileSet }, External { first_gid: usize, source: String }, } + +impl TileSetElement { + pub fn from_xml(node: roxmltree::Node) -> anyhow::Result { + ensure_element!(node); + ensure_tag_name!(node, "tileset"); + + let first_gid = get_attribute!(node, "firstgid")?.parse()?; + + if let Ok(source) = get_attribute!(node, "source") { + Ok(TileSetElement::External { + first_gid, + source: source.to_owned(), + }) + } else { + Ok(TileSetElement::Embedded { + first_gid, + tileset: TileSet::from_xml(&node)?, + }) + } + } +} + +#[cfg(test)] +mod test_tileset_element { + use crate::lowlevel::image::*; + use crate::lowlevel::tileset::*; + + #[test] + fn embedded_tilemap() { + crate::parse_property_test!( + TileSetElement, + r##" + + + "##, + TileSetElement::Embedded { + first_gid: 1, + tileset: TileSet { + version: None, + name: "hex mini".to_owned(), + tile_width: 18, + tile_height: 18, + tile_offset: TileOffset { x: 0, y: 1 }, + terrain_types: vec![], + wangsets: vec![], + spacing: 0, + margin: 0, + tile_count: 20, + columns: 5, + object_alignment: ObjectAlignment::default(), + tile: vec![], + image: Some(Image { + source: ImageSource::External { + source: "hexmini.png".to_owned() + }, + transparent_color: None, + width: Some(106), + height: Some(72) + }), + grid: None, + properties: Properties::default(), + transformations: TileSetTransformations::default(), + } + } + ); + } + + #[test] + fn external_tilemap() { + crate::parse_property_test!( + TileSetElement, + r##""##, + TileSetElement::External { + first_gid: 1, + source: "beach_tileset.tsx".to_owned() + } + ); + } +} -- cgit v1.2.3-60-g2f50