use crate::{ensure_element, ensure_tag_name, get_attribute}; use anyhow::anyhow; use rgb::RGBA; use super::property::Properties; use super::tileset::TileSetElement; // https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#map /// Map orientation /// Tiled supports “orthogonal”, “isometric”, “staggered” and “hexagonal” (since 0.11). #[derive(Debug)] pub enum MapOrientation { Orthogonal, Isometric, Staggered { /// For staggered and hexagonal maps, determines whether the “even” or “odd” indexes along the staggered axis are shifted. (since 0.11) stagger_axis: StaggerAxis, /// For staggered and hexagonal maps, determines which axis (“x” or “y”) is staggered. (since 0.11) stagger_index: StaggerIndex, }, Hexagonal { /// Only for hexagonal maps. Determines the width or height (depending on the staggered axis) of the tile’s edge, in pixels. hexside_length: i32, /// For staggered and hexagonal maps, determines whether the “even” or “odd” indexes along the staggered axis are shifted. (since 0.11) stagger_axis: StaggerAxis, /// For staggered and hexagonal maps, determines which axis (“x” or “y”) is staggered. (since 0.11) stagger_index: StaggerIndex, }, // thanks https://docs.rs/tmx/0.3.1/tmx/map/enum.Orientation.html for this format idea. } impl MapOrientation { pub fn from_xml_map_node(node: &roxmltree::Node) -> anyhow::Result { let orientation = node .attribute("orientation") .ok_or(anyhow!("map orientation missing"))?; match orientation { "orthogonal" => Ok(MapOrientation::Orthogonal), "isometric" => Ok(MapOrientation::Isometric), "staggered" => { let stagger_axis = StaggerAxis::from_string(node.attribute("staggeraxis").ok_or( anyhow!("map.staggeraxis missing, it is required for orientation=staggered"), )?)?; let stagger_index = StaggerIndex::from_string(node.attribute("staggerindex").ok_or(anyhow!( "map.staggerindex missing, it is required for orientation=staggered" ))?)?; Ok(MapOrientation::Staggered { stagger_axis, stagger_index, }) } "hexagonal" => { let stagger_axis = StaggerAxis::from_string(node.attribute("staggeraxis").ok_or( anyhow!("map.staggeraxis missing, it is required for orientation=hexagonal"), )?)?; let stagger_index = StaggerIndex::from_string(node.attribute("staggerindex").ok_or(anyhow!( "map.staggerindex missing, it is required for orientation=hexagonal" ))?)?; let hexside_length: i32 = node .attribute("hexsidelength") .ok_or(anyhow!( "map.hexsidelength missing, it is required for orientation=hexagonal" ))? .parse()?; Ok(MapOrientation::Hexagonal { stagger_axis, stagger_index, hexside_length, }) } _ => Err(anyhow!("Unknown MapOrientation: {}", orientation)), } } pub fn to_string(self) -> String { match self { MapOrientation::Orthogonal => "orthogonal".to_owned(), MapOrientation::Isometric => "isometric".to_owned(), MapOrientation::Staggered { .. } => "staggered".to_owned(), MapOrientation::Hexagonal { .. } => "hexagonal".to_owned(), } } } /// The order in which tiles on tile layers are rendered. /// In all cases, the map is drawn row-by-row. (only supported for orthogonal maps at the moment) #[derive(Debug)] pub enum MapRenderOrder { /// RightDown - The default RightDown, RightUp, LeftDown, LeftUp, } impl Default for MapRenderOrder { fn default() -> Self { MapRenderOrder::RightDown } } impl MapRenderOrder { pub fn from_string(string: &str) -> anyhow::Result { match string { "right-down" => Ok(MapRenderOrder::RightDown), "right-up" => Ok(MapRenderOrder::RightUp), "left-down" => Ok(MapRenderOrder::LeftDown), "left-up" => Ok(MapRenderOrder::LeftUp), _ => Err(anyhow!("Unknown MapRenderOrder: {}", string)), } } pub fn to_string(self) -> String { match self { MapRenderOrder::RightDown => "right-down".to_owned(), MapRenderOrder::RightUp => "right-up".to_owned(), MapRenderOrder::LeftDown => "left-down".to_owned(), MapRenderOrder::LeftUp => "left-up".to_owned(), } } } /// For staggered and hexagonal maps, determines which axis (“x” or “y”) is staggered. (since 0.11) #[derive(Debug)] pub enum StaggerAxis { X, Y, } impl StaggerAxis { pub fn from_string(string: &str) -> anyhow::Result { match string { "x" => Ok(StaggerAxis::X), "y" => Ok(StaggerAxis::Y), _ => Err(anyhow!("invalid StaggerAxis: {}", string)), } } pub fn to_string(self) -> String { match self { StaggerAxis::X => "x".to_owned(), StaggerAxis::Y => "y".to_owned(), } } } /// For staggered and hexagonal maps, determines whether the “even” or “odd” indexes along the staggered axis are shifted. (since 0.11) #[derive(Debug)] pub enum StaggerIndex { Even, Odd, } impl StaggerIndex { pub fn from_string(string: &str) -> anyhow::Result { match string { "even" => Ok(StaggerIndex::Even), "odd" => Ok(StaggerIndex::Odd), _ => Err(anyhow!("invalid StaggerIndex: {}", string)), } } pub fn to_string(self) -> String { match self { StaggerIndex::Even => "even".to_owned(), StaggerIndex::Odd => "odd".to_owned(), } } } #[derive(Debug)] pub struct Map { /// The TMX format version. Was “1.0” so far, and will be incremented to match minor Tiled releases. pub version: String, /// Map orientation. Tiled supports “orthogonal”, “isometric”, “staggered” and “hexagonal” (since 0.11). pub orientation: MapOrientation, /// The order in which tiles on tile layers are rendered. pub render_order: MapRenderOrder, /// The compression level to use for tile layer data (defaults to -1, which means to use the algorithm default). pub compression_level: isize, // The map width in tiles. pub width: usize, /// The map height in tiles. pub height: usize, /// The width of a tile. pub tile_width: usize, /// The height of a tile. pub tile_height: usize, /// The background color of the map. (optional, may include alpha value since 0.15 in the form #AARRGGBB. Defaults to fully transparent.) pub background_color: Option>, /// Stores the next available ID for new layers. This number is stored to prevent reuse of the same ID after layers have been removed. (since 1.2) (defaults to the highest layer id in the file + 1) pub next_layer_id: usize, // TODO set default in funtions /// Stores the next available ID for new objects. This number is stored to prevent reuse of the same ID after objects have been removed. (since 0.11) (defaults to the highest object id in the file + 1) pub next_object_id: usize, /// 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. (0 for false, 1 for true, defaults to 0) pub infinite: bool, // xml child elements pub properties: super::property::Properties, pub tilesets: Vec, pub layer: Vec, // TODO Can contain any number: , , (since 1.0), (since 1.3) } impl Map { pub fn from_xml(doc: roxmltree::Document) -> anyhow::Result { let node = doc.root_element(); ensure_element!(node); ensure_tag_name!(node, "map"); let version = get_attribute!(node, "version")?.to_owned(); let orientation = MapOrientation::from_xml_map_node(&node)?; let render_order = MapRenderOrder::from_string(get_attribute!(node, "renderorder")?)?; let compression_level = if let Ok(clevel) = get_attribute!(node, "compressionlevel") { clevel.parse()? } else { -1 }; let infinite = if let Ok(inf) = get_attribute!(node, "infinite") { match inf { "1" => true, "0" | _ => false, } } else { false }; let properties: Properties = match node.children().find(|&c| c.has_tag_name("properties")) { Some(child_node) => Properties::from_xml(child_node)?, None => Properties::default(), }; let mut tilesets = Vec::new(); for tileset_node in node.children().filter(|n| n.has_tag_name("tileset")) { println!("->{:?}", &tileset_node); tilesets.push(TileSetElement::from_xml(tileset_node)?); } Ok(Map { version, orientation, render_order, compression_level, width: get_attribute!(node, "width")?.parse()?, height: get_attribute!(node, "height")?.parse()?, tile_width: get_attribute!(node, "tilewidth")?.parse()?, tile_height: get_attribute!(node, "tileheight")?.parse()?, background_color: None, // TODO next_layer_id: 4242, // TODO next_object_id: 4242, // TODO infinite, properties, tilesets, layer: vec![], // TODO }) } }