use std::{
collections::HashMap,
fmt,
path::{Path, PathBuf},
str::FromStr,
sync::Arc,
};
use xml::attribute::OwnedAttribute;
use crate::{
error::{Error, Result},
layers::{LayerData, LayerTag},
properties::{parse_properties, Color, Properties},
tileset::Tileset,
util::{get_attrs, parse_tag, XmlEventResult},
EmbeddedParseResultType, Layer, ResourceCache, ResourceReader,
};
pub(crate) struct MapTilesetGid {
pub first_gid: Gid,
pub tileset: Arc<Tileset>,
}
#[derive(PartialEq, Clone)]
pub struct Map {
version: String,
pub source: PathBuf,
pub orientation: Orientation,
pub width: u32,
pub height: u32,
pub tile_width: u32,
pub tile_height: u32,
pub hex_side_length: Option<i32>,
pub stagger_axis: StaggerAxis,
pub stagger_index: StaggerIndex,
tilesets: Vec<Arc<Tileset>>,
layers: Vec<LayerData>,
pub properties: Properties,
pub background_color: Option<Color>,
infinite: bool,
pub user_type: Option<String>,
}
impl fmt::Debug for Map {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Map")
.field("version", &self.version)
.field("orientation", &self.orientation)
.field("width", &self.width)
.field("height", &self.height)
.field("tile_width", &self.tile_width)
.field("tile_height", &self.tile_height)
.field("stagger_axis", &self.stagger_axis)
.field("stagger_index", &self.stagger_index)
.field("tilesets", &format!("{} tilesets", self.tilesets.len()))
.field("layers", &format!("{} layers", self.layers.len()))
.field("properties", &self.properties)
.field("background_color", &self.background_color)
.field("infinite", &self.infinite)
.field("user_type", &self.user_type)
.finish()
}
}
impl Map {
pub fn version(&self) -> &str {
self.version.as_ref()
}
pub fn infinite(&self) -> bool {
self.infinite
}
}
impl Map {
#[inline]
pub fn tilesets(&self) -> &[Arc<Tileset>] {
self.tilesets.as_ref()
}
#[inline]
pub fn layers(&self) -> impl ExactSizeIterator<Item = Layer> {
self.layers.iter().map(move |layer| Layer::new(self, layer))
}
pub fn get_layer(&self, index: usize) -> Option<Layer> {
self.layers.get(index).map(|data| Layer::new(self, data))
}
}
impl Map {
pub(crate) fn parse_xml(
parser: &mut impl Iterator<Item = XmlEventResult>,
attrs: Vec<OwnedAttribute>,
map_path: &Path,
reader: &mut impl ResourceReader,
cache: &mut impl ResourceCache,
) -> Result<Map> {
let (
(c, infinite, user_type, user_class, stagger_axis, stagger_index, hex_side_length),
(v, o, w, h, tw, th),
) = get_attrs!(
for v in attrs {
Some("backgroundcolor") => colour ?= v.parse(),
Some("infinite") => infinite = v == "1",
Some("type") => user_type ?= v.parse(),
Some("class") => user_class ?= v.parse(),
Some("staggeraxis") => stagger_axis ?= v.parse::<StaggerAxis>(),
Some("staggerindex") => stagger_index ?= v.parse::<StaggerIndex>(),
Some("hexsidelength") => hex_side_length ?= v.parse(),
"version" => version = v,
"orientation" => orientation ?= v.parse::<Orientation>(),
"width" => width ?= v.parse::<u32>(),
"height" => height ?= v.parse::<u32>(),
"tilewidth" => tile_width ?= v.parse::<u32>(),
"tileheight" => tile_height ?= v.parse::<u32>(),
}
((colour, infinite, user_type, user_class, stagger_axis, stagger_index, hex_side_length), (version, orientation, width, height, tile_width, tile_height))
);
let infinite = infinite.unwrap_or(false);
let user_type = user_type.or(user_class);
let stagger_axis = stagger_axis.unwrap_or_default();
let stagger_index = stagger_index.unwrap_or_default();
let mut layers = Vec::new();
let mut properties = HashMap::new();
let mut tilesets = Vec::new();
parse_tag!(parser, "map", {
"tileset" => |attrs: Vec<OwnedAttribute>| {
let res = Tileset::parse_xml_in_map(parser, &attrs, map_path, reader, cache)?;
match res.result_type {
EmbeddedParseResultType::ExternalReference { tileset_path } => {
let tileset = if let Some(ts) = cache.get_tileset(&tileset_path) {
ts
} else {
let tileset = Arc::new(crate::parse::xml::parse_tileset(&tileset_path, reader, cache)?);
cache.insert_tileset(tileset_path.clone(), tileset.clone());
tileset
};
tilesets.push(MapTilesetGid{first_gid: res.first_gid, tileset});
}
EmbeddedParseResultType::Embedded { tileset } => {
tilesets.push(MapTilesetGid{first_gid: res.first_gid, tileset: Arc::new(tileset)});
},
};
Ok(())
},
"layer" => |attrs| {
layers.push(LayerData::new(
parser,
attrs,
LayerTag::Tiles,
infinite,
map_path,
&tilesets,
None,
reader,
cache
)?);
Ok(())
},
"imagelayer" => |attrs| {
layers.push(LayerData::new(
parser,
attrs,
LayerTag::Image,
infinite,
map_path,
&tilesets,
None,
reader,
cache
)?);
Ok(())
},
"objectgroup" => |attrs| {
layers.push(LayerData::new(
parser,
attrs,
LayerTag::Objects,
infinite,
map_path,
&tilesets,
None,
reader,
cache
)?);
Ok(())
},
"group" => |attrs| {
layers.push(LayerData::new(
parser,
attrs,
LayerTag::Group,
infinite,
map_path,
&tilesets,
None,
reader,
cache
)?);
Ok(())
},
"properties" => |_| {
properties = parse_properties(parser)?;
Ok(())
},
});
let tilesets = tilesets.into_iter().map(|ts| ts.tileset).collect();
Ok(Map {
version: v,
source: map_path.to_owned(),
orientation: o,
width: w,
height: h,
tile_width: tw,
tile_height: th,
hex_side_length,
stagger_axis,
stagger_index,
tilesets,
layers,
properties,
background_color: c,
infinite,
user_type,
})
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Default)]
#[allow(missing_docs)]
pub enum StaggerIndex {
Even,
#[default]
Odd,
}
#[derive(Debug)]
pub struct StaggerIndexError {
pub str_found: String,
}
impl std::fmt::Display for StaggerIndexError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!(
"failed to parse stagger index, valid options are `even`, `odd` \
but got `{}` instead",
self.str_found
))
}
}
impl FromStr for StaggerIndex {
type Err = StaggerIndexError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"even" => Ok(StaggerIndex::Even),
"odd" => Ok(StaggerIndex::Odd),
_ => Err(StaggerIndexError {
str_found: s.to_owned(),
}),
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Default)]
#[allow(missing_docs)]
pub enum StaggerAxis {
X,
#[default]
Y,
}
#[derive(Debug)]
pub struct StaggerAxisError {
pub str_found: String,
}
impl std::fmt::Display for StaggerAxisError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!(
"failed to parse stagger axis, valid options are `x`, `y` \
but got `{}` instead",
self.str_found
))
}
}
impl FromStr for StaggerAxis {
type Err = StaggerAxisError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"x" => Ok(StaggerAxis::X),
"y" => Ok(StaggerAxis::Y),
_ => Err(StaggerAxisError {
str_found: s.to_owned(),
}),
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[allow(missing_docs)]
pub enum Orientation {
Orthogonal,
Isometric,
Staggered,
Hexagonal,
}
#[derive(Debug)]
pub struct OrientationParseError {
pub str_found: String,
}
impl std::fmt::Display for OrientationParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!("failed to parse orientation, valid options are `orthogonal`, `isometric`, `staggered` \
and `hexagonal` but got `{}` instead", self.str_found))
}
}
impl std::error::Error for OrientationParseError {}
impl FromStr for Orientation {
type Err = OrientationParseError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"orthogonal" => Ok(Orientation::Orthogonal),
"isometric" => Ok(Orientation::Isometric),
"staggered" => Ok(Orientation::Staggered),
"hexagonal" => Ok(Orientation::Hexagonal),
_ => Err(OrientationParseError {
str_found: s.to_owned(),
}),
}
}
}
impl fmt::Display for Orientation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Orientation::Orthogonal => write!(f, "orthogonal"),
Orientation::Isometric => write!(f, "isometric"),
Orientation::Staggered => write!(f, "staggered"),
Orientation::Hexagonal => write!(f, "hexagonal"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct Gid(pub u32);
impl Gid {
#[allow(dead_code)]
pub const EMPTY: Gid = Gid(0);
}