1 // SuperTux
2 // Copyright (C) 2008 Matthias Braun <matze@braunis.de>
3 // Ingo Ruhnke <grumbel@gmail.com>
4 //
5 // This program is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 #include "supertux/tile_set_parser.hpp"
19
20 #include <sstream>
21 #include <sexp/value.hpp>
22 #include <sexp/io.hpp>
23
24 #include "supertux/autotile_parser.hpp"
25 #include "supertux/gameconfig.hpp"
26 #include "supertux/globals.hpp"
27 #include "supertux/tile_set.hpp"
28 #include "util/log.hpp"
29 #include "util/reader_document.hpp"
30 #include "util/reader_mapping.hpp"
31 #include "util/file_system.hpp"
32 #include "video/surface.hpp"
33
TileSetParser(TileSet & tileset,const std::string & filename)34 TileSetParser::TileSetParser(TileSet& tileset, const std::string& filename) :
35 m_tileset(tileset),
36 m_filename(filename),
37 m_tiles_path()
38 {
39 }
40
41 void
parse()42 TileSetParser::parse()
43 {
44 m_tiles_path = FileSystem::dirname(m_filename);
45
46 auto doc = ReaderDocument::from_file(m_filename);
47 auto root = doc.get_root();
48
49 if (root.get_name() != "supertux-tiles") {
50 throw std::runtime_error("file is not a supertux tiles file.");
51 }
52
53 auto iter = root.get_mapping().get_iter();
54 while (iter.next())
55 {
56 if (iter.get_key() == "tile")
57 {
58 ReaderMapping tile_mapping = iter.as_mapping();
59 parse_tile(tile_mapping);
60 }
61 else if (iter.get_key() == "tilegroup")
62 {
63 /* tilegroups are only interesting for the editor */
64 ReaderMapping reader = iter.as_mapping();
65 Tilegroup tilegroup;
66 reader.get("name", tilegroup.name);
67 reader.get("tiles", tilegroup.tiles);
68 m_tileset.add_tilegroup(tilegroup);
69 }
70 else if (iter.get_key() == "tiles")
71 {
72 ReaderMapping tiles_mapping = iter.as_mapping();
73 parse_tiles(tiles_mapping);
74 }
75 else if (iter.get_key() == "autotileset")
76 {
77 ReaderMapping reader = iter.as_mapping();
78 std::string autotile_filename;
79 if (!reader.get("source", autotile_filename))
80 {
81 log_warning << "No source path for autotiles in file '" << m_filename << "'" << std::endl;
82 }
83 else
84 {
85 AutotileParser* parser = new AutotileParser(m_tileset.m_autotilesets,
86 FileSystem::normalize(m_tiles_path + autotile_filename));
87 parser->parse();
88 }
89 }
90 else
91 {
92 log_warning << "Unknown symbol '" << iter.get_key() << "' in tileset file" << std::endl;
93 }
94 }
95 if (g_config->developer_mode)
96 {
97 m_tileset.add_unassigned_tilegroup();
98 }
99 }
100
101 void
parse_tile(const ReaderMapping & reader)102 TileSetParser::parse_tile(const ReaderMapping& reader)
103 {
104 uint32_t id;
105 if (!reader.get("id", id))
106 {
107 throw std::runtime_error("Missing tile-id.");
108 }
109
110 uint32_t attributes = 0;
111
112 bool value = false;
113 if (reader.get("solid", value) && value)
114 attributes |= Tile::SOLID;
115 if (reader.get("unisolid", value) && value)
116 attributes |= Tile::UNISOLID | Tile::SOLID;
117 if (reader.get("brick", value) && value)
118 attributes |= Tile::BRICK;
119 if (reader.get("ice", value) && value)
120 attributes |= Tile::ICE;
121 if (reader.get("water", value) && value)
122 attributes |= Tile::WATER;
123 if (reader.get("hurts", value) && value)
124 attributes |= Tile::HURTS;
125 if (reader.get("fire", value) && value)
126 attributes |= Tile::FIRE;
127 if (reader.get("walljump", value) && value)
128 attributes |= Tile::WALLJUMP;
129 if (reader.get("fullbox", value) && value)
130 attributes |= Tile::FULLBOX;
131 if (reader.get("coin", value) && value)
132 attributes |= Tile::COIN;
133 if (reader.get("goal", value) && value)
134 attributes |= Tile::GOAL;
135
136 uint32_t data = 0;
137
138 if (reader.get("north", value) && value)
139 data |= Tile::WORLDMAP_NORTH;
140 if (reader.get("south", value) && value)
141 data |= Tile::WORLDMAP_SOUTH;
142 if (reader.get("west", value) && value)
143 data |= Tile::WORLDMAP_WEST;
144 if (reader.get("east", value) && value)
145 data |= Tile::WORLDMAP_EAST;
146 if (reader.get("stop", value) && value)
147 data |= Tile::WORLDMAP_STOP;
148
149 reader.get("data", data);
150
151 float fps = 10;
152 reader.get("fps", fps);
153
154 std::string object_name, object_data;
155 reader.get("object-name", object_name);
156 reader.get("object-data", object_data);
157
158 if (reader.get("slope-type", data))
159 {
160 attributes |= Tile::SOLID | Tile::SLOPE;
161 }
162
163 std::vector<SurfacePtr> editor_surfaces;
164 boost::optional<ReaderMapping> editor_images_mapping;
165 if (reader.get("editor-images", editor_images_mapping)) {
166 editor_surfaces = parse_imagespecs(*editor_images_mapping);
167 }
168
169 std::vector<SurfacePtr> surfaces;
170 boost::optional<ReaderMapping> images_mapping;
171 if (reader.get("images", images_mapping)) {
172 surfaces = parse_imagespecs(*images_mapping);
173 }
174
175 bool deprecated = false;
176 reader.get("deprecated", deprecated);
177
178 auto tile = std::make_unique<Tile>(surfaces, editor_surfaces,
179 attributes, data, fps,
180 object_name, object_data, deprecated);
181 m_tileset.add_tile(id, std::move(tile));
182 }
183
184 void
parse_tiles(const ReaderMapping & reader)185 TileSetParser::parse_tiles(const ReaderMapping& reader)
186 {
187 // List of ids (use 0 if the tile should be ignored)
188 std::vector<uint32_t> ids;
189 // List of attributes of the tile
190 std::vector<uint32_t> attributes;
191 // List of data for the tiles
192 std::vector<uint32_t> datas;
193
194 // width and height of the image in tile units, this is used for two
195 // purposes:
196 // a) so we don't have to load the image here to know its dimensions
197 // b) so that the resulting 'tiles' entry is more robust,
198 // ie. enlarging the image won't break the tile id mapping
199 // FIXME: height is actually not used, since width might be enough for
200 // all purposes, still feels somewhat more natural this way
201 unsigned int width = 0;
202 unsigned int height = 0;
203
204 bool has_ids = reader.get("ids", ids);
205 bool has_attributes = reader.get("attributes", attributes);
206 bool has_datas = reader.get("datas", datas);
207
208 reader.get("width", width);
209 reader.get("height", height);
210
211 bool shared_surface = false;
212 reader.get("shared-surface", shared_surface);
213
214 float fps = 10;
215 reader.get("fps", fps);
216
217 if (ids.empty() || !has_ids)
218 {
219 throw std::runtime_error("No IDs specified.");
220 }
221 if (width == 0)
222 {
223 throw std::runtime_error("Width is zero.");
224 }
225 else if (height == 0)
226 {
227 throw std::runtime_error("Height is zero.");
228 }
229 else if (fps < 0)
230 {
231 throw std::runtime_error("Negative fps.");
232 }
233 else if (ids.size() != width*height)
234 {
235 std::ostringstream err;
236 err << "Number of ids (" << ids.size() << ") and "
237 "dimensions of image (" << width << "x" << height << " = " << width*height << ") "
238 "differ";
239 throw std::runtime_error(err.str());
240 }
241 else if (has_attributes && (ids.size() != attributes.size()))
242 {
243 std::ostringstream err;
244 err << "Number of ids (" << ids.size() << ") and attributes (" << attributes.size()
245 << ") mismatch, but must be equal";
246 throw std::runtime_error(err.str());
247 }
248 else if (has_datas && ids.size() != datas.size())
249 {
250 std::ostringstream err;
251 err << "Number of ids (" << ids.size() << ") and datas (" << datas.size()
252 << ") mismatch, but must be equal";
253 throw std::runtime_error(err.str());
254 }
255 else
256 {
257 if (shared_surface)
258 {
259 std::vector<SurfacePtr> editor_surfaces;
260 boost::optional<ReaderMapping> editor_surfaces_mapping;
261 if (reader.get("editor-images", editor_surfaces_mapping)) {
262 editor_surfaces = parse_imagespecs(*editor_surfaces_mapping);
263 }
264
265 std::vector<SurfacePtr> surfaces;
266 boost::optional<ReaderMapping> surfaces_mapping;
267 if (reader.get("image", surfaces_mapping) ||
268 reader.get("images", surfaces_mapping)) {
269 surfaces = parse_imagespecs(*surfaces_mapping);
270 }
271
272 for (size_t i = 0; i < ids.size(); ++i)
273 {
274 if (ids[i] != 0)
275 {
276 const int x = static_cast<int>(32 * (i % width));
277 const int y = static_cast<int>(32 * (i / width));
278
279 std::vector<SurfacePtr> regions;
280 regions.reserve(surfaces.size());
281 std::transform(surfaces.begin(), surfaces.end(), std::back_inserter(regions),
282 [x, y] (const SurfacePtr& surface) {
283 return surface->region(Rect(x, y, Size(32, 32)));
284 });
285
286 std::vector<SurfacePtr> editor_regions;
287 editor_regions.reserve(editor_surfaces.size());
288 std::transform(editor_surfaces.begin(), editor_surfaces.end(), std::back_inserter(editor_regions),
289 [x, y] (const SurfacePtr& surface) {
290 return surface->region(Rect(x, y, Size(32, 32)));
291 });
292
293 auto tile = std::make_unique<Tile>(regions,
294 editor_regions,
295 (has_attributes ? attributes[i] : 0),
296 (has_datas ? datas[i] : 0),
297 fps);
298
299 m_tileset.add_tile(ids[i], std::move(tile));
300 }
301 }
302 }
303 else // (!shared_surface)
304 {
305 for (size_t i = 0; i < ids.size(); ++i)
306 {
307 if (ids[i] != 0)
308 {
309 int x = static_cast<int>(32 * (i % width));
310 int y = static_cast<int>(32 * (i / width));
311
312 std::vector<SurfacePtr> surfaces;
313 boost::optional<ReaderMapping> surfaces_mapping;
314 if (reader.get("image", surfaces_mapping) ||
315 reader.get("images", surfaces_mapping)) {
316 surfaces = parse_imagespecs(*surfaces_mapping, Rect(x, y, Size(32, 32)));
317 }
318
319 std::vector<SurfacePtr> editor_surfaces;
320 boost::optional<ReaderMapping> editor_surfaces_mapping;
321 if (reader.get("editor-images", editor_surfaces_mapping)) {
322 editor_surfaces = parse_imagespecs(*editor_surfaces_mapping, Rect(x, y, Size(32, 32)));
323 }
324
325 auto tile = std::make_unique<Tile>(surfaces,
326 editor_surfaces,
327 (has_attributes ? attributes[i] : 0),
328 (has_datas ? datas[i] : 0),
329 fps);
330
331 m_tileset.add_tile(ids[i], std::move(tile));
332 }
333 }
334 }
335 }
336 }
337
338 std::vector<SurfacePtr>
parse_imagespecs(const ReaderMapping & images_mapping,const boost::optional<Rect> & surface_region) const339 TileSetParser::parse_imagespecs(const ReaderMapping& images_mapping,
340 const boost::optional<Rect>& surface_region) const
341 {
342 std::vector<SurfacePtr> surfaces;
343
344 // (images "foo.png" "foo.bar" ...)
345 // (images (region "foo.png" 0 0 32 32))
346 auto iter = images_mapping.get_iter();
347 while (iter.next())
348 {
349 if (iter.is_string())
350 {
351 std::string file = iter.as_string_item();
352 surfaces.push_back(Surface::from_file(FileSystem::join(m_tiles_path, file), surface_region));
353 }
354 else if (iter.is_pair() && iter.get_key() == "surface")
355 {
356 surfaces.push_back(Surface::from_reader(iter.as_mapping(), surface_region));
357 }
358 else if (iter.is_pair() && iter.get_key() == "region")
359 {
360 auto const& sx = iter.as_mapping().get_sexp();
361 auto const& arr = sx.as_array();
362 if (arr.size() != 6)
363 {
364 log_warning << "(region X Y WIDTH HEIGHT) tag malformed: " << sx << std::endl;
365 }
366 else
367 {
368 const std::string file = arr[1].as_string();
369 const int x = arr[2].as_int();
370 const int y = arr[3].as_int();
371 const int w = arr[4].as_int();
372 const int h = arr[5].as_int();
373
374 Rect rect(x, y, x + w, y + h);
375
376 if (surface_region)
377 {
378 rect.left += surface_region->left;
379 rect.top += surface_region->top;
380
381 rect.right = rect.left + surface_region->get_width();
382 rect.bottom = rect.top + surface_region->get_height();
383 }
384
385 surfaces.push_back(Surface::from_file(FileSystem::join(m_tiles_path, file),
386 rect));
387 }
388 }
389 else
390 {
391 log_warning << "Expected string or list in images tag" << std::endl;
392 }
393 }
394
395 return surfaces;
396 }
397
398 /* EOF */
399