summaryrefslogtreecommitdiff
path: root/src/lowlevel/tileset.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lowlevel/tileset.rs')
-rw-r--r--src/lowlevel/tileset.rs208
1 files changed, 193 insertions, 15 deletions
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 <tileset>.
- 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<String>,
+
/// 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<Vec<Terrain>>,
+ pub terrain_types: Vec<Terrain>,
/// (since 1.1)
- pub wangsets: Option<Vec<WangSet>>,
+ pub wangsets: Vec<WangSet>,
/// (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<TileSetElement> {
+ 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##"<tileset firstgid="1" name="hex mini" tilewidth="18" tileheight="18" tilecount="20" columns="5">
+ <tileoffset x="0" y="1"/>
+ <image source="hexmini.png" width="106" height="72"/>
+ </tileset>"##,
+ 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##"<tileset firstgid="1" source="beach_tileset.tsx"/>"##,
+ TileSetElement::External {
+ first_gid: 1,
+ source: "beach_tileset.tsx".to_owned()
+ }
+ );
+ }
+}