summaryrefslogtreecommitdiff
path: root/src/lowlevel/map.rs
blob: 9d0b715ff035e7afe426b101f699dc7f715412d3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
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<MapOrientation> {
        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<MapRenderOrder> {
        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<StaggerAxis> {
        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<StaggerIndex> {
        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<RGBA<u8>>,

    /// 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<super::tileset::TileSetElement>,

    pub layer: Vec<super::layer::LayerData>,
    // TODO Can contain any number: <objectgroup>, <imagelayer>, <group> (since 1.0), <editorsettings> (since 1.3)
}

impl Map {
    pub fn from_xml(doc: roxmltree::Document) -> anyhow::Result<Map> {
        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
        })
    }
}