use std::{
collections::HashMap,
fmt,
path::{Path, PathBuf},
str::FromStr,
sync::Arc,
};
use crate::{
EmbeddedParseResultType, Layer, ResourceCache, ResourceReader,
error::Result,
layers::{LayerData, LayerTag},
properties::{Color, Properties, parse_properties},
tileset::Tileset,
util::{get_attrs, parse_tag},
};
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 render_order: RenderOrder,
pub width: u32,
pub height: u32,
pub tile_width: u32,
pub tile_height: u32,
pub skew_x: i32,
pub skew_y: i32,
pub parallax_origin_x: f32,
pub parallax_origin_y: f32,
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: 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("render_order", &self.render_order)
.field("width", &self.width)
.field("height", &self.height)
.field("tile_width", &self.tile_width)
.field("tile_height", &self.tile_height)
.field("skew_x", &self.skew_x)
.field("skew_y", &self.skew_y)
.field("parallax_origin_x", &self.parallax_origin_x)
.field("parallax_origin_y", &self.parallax_origin_y)
.field("hex_side_length", &self.hex_side_length)
.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<R: std::io::BufRead>(
elem: crate::util::XmlElement<'_, R>,
map_path: &Path,
reader: &mut impl ResourceReader,
cache: &mut impl ResourceCache,
) -> Result<Map> {
let (
(
c,
infinite,
user_type,
user_class,
render_order,
skew_x,
skew_y,
parallax_origin_x,
parallax_origin_y,
stagger_axis,
stagger_index,
hex_side_length,
),
(v, o, w, h, tw, th),
) = get_attrs!(
for v in (elem.attrs) {
Some("backgroundcolor") => colour ?= v.parse(),
Some("infinite") => infinite = v == "1",
Some("type") => user_type ?= v.parse(),
Some("class") => user_class ?= v.parse(),
Some("renderorder") => render_order ?= v.parse::<RenderOrder>(),
Some("skewx") => skew_x ?= v.parse::<i32>(),
Some("skewy") => skew_y ?= v.parse::<i32>(),
Some("parallaxoriginx") => parallax_origin_x ?= v.parse::<f32>(),
Some("parallaxoriginy") => parallax_origin_y ?= v.parse::<f32>(),
Some("staggeraxis") => stagger_axis ?= v.parse::<StaggerAxis>(),
Some("staggerindex") => stagger_index ?= v.parse::<StaggerIndex>(),
Some("hexsidelength") => hex_side_length ?= v.parse(),
"version" => version = v.to_string(),
"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, render_order, skew_x, skew_y, parallax_origin_x, parallax_origin_y, 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).unwrap_or_default();
let render_order = render_order.unwrap_or_default();
let skew_x = skew_x.unwrap_or(0);
let skew_y = skew_y.unwrap_or(0);
let parallax_origin_x = parallax_origin_x.unwrap_or(0.0);
let parallax_origin_y = parallax_origin_y.unwrap_or(0.0);
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!(elem, {
"tileset" => |elem| {
let res = Tileset::parse_xml_in_map(elem, 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" => |elem| {
layers.push(LayerData::new(
elem,
LayerTag::Tiles,
infinite,
map_path,
&tilesets,
None,
reader,
cache
)?);
Ok(())
},
"imagelayer" => |elem| {
layers.push(LayerData::new(
elem,
LayerTag::Image,
infinite,
map_path,
&tilesets,
None,
reader,
cache
)?);
Ok(())
},
"objectgroup" => |elem| {
layers.push(LayerData::new(
elem,
LayerTag::Objects,
infinite,
map_path,
&tilesets,
None,
reader,
cache
)?);
Ok(())
},
"group" => |elem| {
layers.push(LayerData::new(
elem,
LayerTag::Group,
infinite,
map_path,
&tilesets,
None,
reader,
cache
)?);
Ok(())
},
"properties" => |elem| {
properties = parse_properties(elem)?;
Ok(())
},
});
let tilesets = tilesets.into_iter().map(|ts| ts.tileset).collect();
Ok(Map {
version: v,
source: map_path.to_owned(),
orientation: o,
render_order,
width: w,
height: h,
tile_width: tw,
tile_height: th,
skew_x,
skew_y,
parallax_origin_x,
parallax_origin_y,
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, Default, PartialEq, Eq, Copy, Clone)]
#[allow(missing_docs)]
pub enum RenderOrder {
#[default]
RightDown,
RightUp,
LeftDown,
LeftUp,
}
#[derive(Debug)]
pub struct RenderOrderParseError {
pub str_found: String,
}
impl std::fmt::Display for RenderOrderParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!(
"failed to parse render order, valid options are `right-down`, `right-up`, \
`left-down` and `left-up` but got `{}` instead",
self.str_found
))
}
}
impl FromStr for RenderOrder {
type Err = RenderOrderParseError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"right-down" => Ok(RenderOrder::RightDown),
"right-up" => Ok(RenderOrder::RightUp),
"left-down" => Ok(RenderOrder::LeftDown),
"left-up" => Ok(RenderOrder::LeftUp),
_ => Err(RenderOrderParseError {
str_found: s.to_owned(),
}),
}
}
}
impl fmt::Display for RenderOrder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RenderOrder::RightDown => write!(f, "right-down"),
RenderOrder::RightUp => write!(f, "right-up"),
RenderOrder::LeftDown => write!(f, "left-down"),
RenderOrder::LeftUp => write!(f, "left-up"),
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[allow(missing_docs)]
pub enum Orientation {
Orthogonal,
Isometric,
Staggered,
Hexagonal,
Oblique,
}
#[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`, \
`hexagonal` and `oblique` 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),
"oblique" => Ok(Orientation::Oblique),
_ => 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"),
Orientation::Oblique => write!(f, "oblique"),
}
}
}
#[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);
}