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
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use crate::{
    layer::ObjectGroup,
    parsers::{parse_color, parse_path, parse_property},
    wangs::WangSet,
    Color, TiledValue, Vec2,
};
use serde::de::Error;
use serde::{Deserialize, Deserializer};
use std::collections::HashMap;
use std::fs::File;
use std::path::PathBuf;

/// A tileset that associates information with each tile.
///
/// A tileset associates information with each tile such as
/// image path or terrain type, may include a tiles array property.
/// Each tile in the `tiles` member has a local id property which
/// specifies the local ID within the tileset.
///
/// Tile sets may be internal to the map, or external files.
#[derive(Debug, PartialEq, Clone)]
pub struct TileSet {
    /// The number of tile columns in the tileset. Eg; dividing the
    /// associated image in to columns where each column is the width
    /// of the tile.
    pub columns: u32,
    /// GID corresponding to the first tile in the set
    pub first_gid: u32,
    /// Path to the image used for tiles in this set
    pub image: PathBuf,
    pub image_width: u32,
    pub image_height: u32,
    /// Buffer between image edge and first tile in pixels
    pub margin: u32,
    /// Spacing between adjacent tiles in image in pixels
    pub spacing: u32,
    pub name: String,
    pub properties: HashMap<String, TiledValue>,
    pub terrains: Option<Vec<Terrain>>,
    /// The tile count + the first GID enable finding the tile location
    /// on the image
    pub tile_count: u32,
    pub tile_height: u32,
    pub tile_width: u32,
    /// used to specify an offset in pixels, to be applied
    /// when drawing a tile from this tileset
    pub tile_offset: Option<Vec2<i32>>,
    /// Holds *extra* information for tiles such as terrain or animation
    pub tiles: Option<Vec<Tile>>,
    /// Defaults to 0,0,0,0 (rgba)
    pub transparent_color: Color,
    pub wang_sets: Option<Vec<WangSet>>,
}

impl<'de> Deserialize<'de> for TileSet {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        #[serde(rename_all = "lowercase")]
        struct External {
            #[serde(rename(deserialize = "firstgid"))]
            first_gid: u32,
            source: String,
        }

        #[derive(Deserialize)]
        struct Internal {
            columns: u32,
            #[serde(rename(deserialize = "firstgid"), default)]
            first_gid: u32,
            #[serde(deserialize_with = "parse_path")]
            image: PathBuf,
            #[serde(rename(deserialize = "imagewidth"))]
            image_width: u32,
            #[serde(rename(deserialize = "imageheight"))]
            image_height: u32,
            margin: u32,
            spacing: u32,
            name: String,
            #[serde(deserialize_with = "parse_property", default)]
            properties: HashMap<String, TiledValue>,
            terrains: Option<Vec<Terrain>>,
            #[serde(rename(deserialize = "tilecount"))]
            tile_count: u32,
            #[serde(rename(deserialize = "tileheight"))]
            tile_height: u32,
            #[serde(rename(deserialize = "tilewidth"))]
            tile_width: u32,
            #[serde(rename(deserialize = "tileoffset"))]
            tile_offset: Option<Vec2<i32>>,
            tiles: Option<Vec<Tile>>,
            #[serde(
                rename(deserialize = "transparentcolor"),
                deserialize_with = "parse_color",
                default
            )]
            transparent_color: Color,
            #[serde(rename(deserialize = "wangsets"))]
            wang_sets: Option<Vec<WangSet>>,
        }

        #[derive(Deserialize)]
        #[serde(untagged)]
        enum Helper {
            Internal(Internal),
            External(External),
        }

        let v = serde_json::Value::deserialize(deserializer)?;
        if let Ok(m) = Helper::deserialize(&v) {
            let t = match m {
                Helper::Internal(v) => v,
                Helper::External(v) => {
                    let path = PathBuf::from(v.source);
                    let file = File::open(path)
                        .map_err(|e| Error::custom(format!("{:?}", e)))?;
                    let mut set: Internal = serde_json::from_reader(file)
                        .map_err(|e| Error::custom(format!("{:?}", e)))?;
                    set.first_gid = v.first_gid;
                    set
                }
            };
            let tile_set = TileSet {
                columns: t.columns,
                first_gid: t.first_gid,
                image: t.image,
                image_width: t.image_width,
                image_height: t.image_height,
                margin: t.margin,
                spacing: t.spacing,
                name: t.name,
                properties: t.properties,
                terrains: t.terrains,
                tile_count: t.tile_count,
                tile_height: t.tile_height,
                tile_width: t.tile_width,
                tile_offset: t.tile_offset,
                tiles: t.tiles,
                transparent_color: t.transparent_color,
                wang_sets: t.wang_sets,
            };
            return Ok(tile_set);
        } else {
            Err(Error::custom("could not parse tile-set"))
        }
    }
}

/// Contains all possible data for a tile including an optional `ObjectGroup`
#[derive(Deserialize, Debug, PartialEq, Clone)]
pub struct Tile {
    pub animation: Option<Vec<Frame>>,
    /// Unlike the ID used in the `TileLayer`, this ID is
    /// local to the `TileSet` only and so starts at 0 (the
    /// tile layer ID starts a 1 for tiles with 0 being no-tile).
    pub id: u32,
    /// Image representing this tile if it uses a separate image
    pub image: Option<String>,
    /// Width of the tile image in pixels
    #[serde(rename(deserialize = "imagewidth"), default)]
    pub image_width: u32,
    /// Height of the tile image in pixels
    #[serde(rename(deserialize = "imageheight"), default)]
    pub image_height: u32,
    #[serde(rename(deserialize = "objectgroup"))]
    pub object_group: Option<ObjectGroup>,
    #[serde(deserialize_with = "parse_property", default)]
    pub properties: HashMap<String, TiledValue>,
    /// The order of indices is: top-left, top-right, bottom-left, bottom-right
    ///
    /// Each entry is the index number in to the Terrain array to get the
    /// specific terrain type for this tile. Typically used in conjunction
    /// with the tileset structure as the terrain tiles are stored within
    /// the data there.
    pub terrain: Option<[i8; 4]>,
    /// An optional string for describing a type
    #[serde(rename(deserialize = "type"))]
    pub tile_type: Option<String>,
}

/// Data for an individual frame of animation
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Frame {
    /// Frame duration in milliseconds
    pub duration: u32,
    /// Local tile ID representing this frame
    #[serde(rename(deserialize = "tileid"))]
    pub tile_id: u32,
}

#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Terrain {
    pub name: String,
    /// Local ID of the tile for this terrain within the tileset
    pub tile: u32,
}