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
use std::str;
use std::path::PathBuf;
use std::ffi::OsStr;
use std::fs::File;
use std::collections::HashMap;
use {GlobalTile, LocalTile};
use serde::{Deserialize, Deserializer};
use serde_json;
use serde_json::Value as JsonValue;
use serde_json::Error as JsonError;
/// Tiled Tileset, containing everything we need to render tiles from
/// this set as well as decide how to do collision checks
#[derive(Clone, Debug, Deserialize)]
pub struct Tileset {
/// Name of the tileset specified by its creator
pub name: String,
/// Global ID of the first tile which is part of this set. Global IDs
/// are meaningless unless applied to a list of Tilesets associated with
/// the correct map.
pub firstgid: GlobalTile,
/// Number of tiles contained in this map
pub tilecount: u32,
/// Height in pixels of each tile
pub tileheight: u32,
/// Width in pixels of each tile
pub tilewidth: u32,
/// The number of tiles per row in the image
pub columns: u32,
/// Path to the image representing this tileset
/// TODO: Support multi-image sets?
pub image: PathBuf,
/// Expected height in pixels of the image
pub imageheight: u32,
/// Expected width in pixels of the image
pub imagewidth: u32,
/// Margin in the image between the edges and where the first tile starts
pub margin: u32,
/// Number of pixels between each tile
pub spacing: u32,
/// Key-Value pair properties specified for this tileset (game-specific data)
pub properties: Option<HashMap<String, String>>,
/// List of all the terrain types defined in this tileset. The values inside
/// the `tiles` member correspond to indices in this array
pub terrains: Vec<Terrain>,
/// Key-Value pair properties associated with specific tiles in this set
pub tileproperties: TileProperties,
/// List of tiles that are associated with specific terrain, and which
/// corners belong to which terrain type.
pub tiles: TileTerrain,
}
impl Tileset {
/// Given a JsonValue for a tileset, and the path of the level it is a member of,
/// try to parse the tileset or load and parse it from an external file.
pub fn load<P: AsRef<OsStr>>(data: JsonValue, data_path: &P) -> Result<Tileset, JsonError> {
use serde::de::Error;
// The data we're deserializing here must be a Json table
let mut data = match data {
JsonValue::Object(data) => data,
_ => return Err(JsonError::custom("Tileset data was not an Object")),
};
// If data contains a "source" field, we're dealing with an
// external tileset, and we must load that file.
Ok(match data.remove("source") {
Some(JsonValue::String(source)) => {
// firstgid is not stored in the external data, so we
// must save it from here for later
let firstgid = match data.remove("firstgid").and_then(|i| i.as_u64()) {
Some(i) => i as u32,
None => return Err(JsonError::custom("Tileset had no firstgid")),
};
// Start with the path to the level
let mut path = PathBuf::from(data_path);
path.pop(); // Path is now the level directory
path.push(source); // Path is the tileset to load
// Try to open the file! We can just use the try!() macro
// because serde_json::Error has a From converion from io::Error
let mut file = try!(File::open(&path));
// Parse the tileset file into an ExternalTileset structure
let ext: ExternalTileset = try!(serde_json::from_reader(&mut file));
path.pop();
path.push(&ext.image);
Tileset {
name: ext.name,
firstgid: GlobalTile(firstgid),
tilecount: ext.tilecount,
tileheight: ext.tileheight,
tilewidth: ext.tilewidth,
columns: ext.columns,
image: path,
imageheight: ext.imageheight,
imagewidth: ext.imagewidth,
margin: ext.margin,
spacing: ext.spacing,
properties: ext.properties,
terrains: ext.terrains,
tileproperties: ext.tileproperties,
tiles: ext.tiles,
}
},
// The tileset is inlined in the level, just parse its data
_ => {
let mut tileset: Tileset = try!(serde_json::from_value(JsonValue::Object(data)));
let mut path = PathBuf::from(data_path);
path.pop();
path.push(&tileset.image);
tileset.image = path;
tileset
}
})
}
pub fn contains_tile(&self, id: GlobalTile) -> bool {
if id.0 < self.firstgid.0 { return false; }
let local = id.0 - self.firstgid.0;
local < self.tilecount
}
}
#[derive(Clone, Debug, Deserialize)]
struct ExternalTileset {
name: String,
tilecount: u32,
tileheight: u32,
tilewidth: u32,
columns: u32,
image: PathBuf,
imageheight: u32,
imagewidth: u32,
margin: u32,
spacing: u32,
properties: Option<HashMap<String, String>>,
terrains: Vec<Terrain>,
tileproperties: TileProperties,
tiles: TileTerrain,
}
#[derive(Clone, Debug)]
pub struct TileProperties {
pub tiles: HashMap<LocalTile, HashMap<String, String>>,
}
impl Deserialize for TileProperties {
fn deserialize<D: Deserializer>(d: &mut D) -> Result<Self, D::Error> {
// Tiled uses string keys because it's a sparse array,
// so we're just going to parse it like that and then
// convert them to LocalTiles
let data: HashMap<String, HashMap<String, String>>;
data = try!(Deserialize::deserialize(d));
let mut props = HashMap::new();
for (k, v) in data {
// Allows us to return an error when a bad key is present
use serde::de::Error;
// We'll return an error if the key isn't a valid integer
let id: u32 = match str::parse(&k) {
Ok(id) => id,
Err(_) => return Err(D::Error::custom("tileproperties contained a non-integer key"))
};
props.insert(LocalTile(id), v);
}
Ok(TileProperties {
tiles: props,
})
}
}
#[derive(Clone, Debug)]
pub struct TileTerrain {
pub tiles: HashMap<LocalTile, [u32; 4]>
}
impl Deserialize for TileTerrain {
fn deserialize<D: Deserializer>(d: &mut D) -> Result<Self, D::Error> {
#[derive(Deserialize)]
struct Data {
terrain: [u32; 4]
}
// Tiled uses string keys because it's a sparse array,
// so we're just going to parse it like that and then
// convert them to LocalTiles
let data: HashMap<String, Data>;
data = try!(Deserialize::deserialize(d));
let mut terrains = HashMap::new();
for (k, v) in data {
// Allows us to return an error when a bad key is present
use serde::de::Error;
// We'll return an error if the key isn't a valid integer
let id: u32 = match str::parse(&k) {
Ok(id) => id,
Err(_) => return Err(D::Error::custom("tileproperties contained a non-integer key"))
};
terrains.insert(LocalTile(id), v.terrain);
}
Ok(TileTerrain {
tiles: terrains,
})
}
}
#[derive(Clone, Debug, Deserialize)]
pub struct Terrain {
pub name: String,
pub tile: LocalTile,
}
/// Test to ensure we can deserialize an ExternalTileset
#[test]
fn deserialize_external() {
use serde_json::from_str;
let data = include_str!("../test-assets/tilesets/goodly-2x.json");
let _: ExternalTileset = from_str(data).unwrap();
}