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