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 | |
33 | TileSetParser::TileSetParser(TileSet& tileset, const std::string& filename) : |
34 | m_tileset(tileset), |
35 | m_filename(filename), |
36 | m_tiles_path() |
37 | { |
38 | } |
39 | |
40 | void |
41 | TileSetParser::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 | |
85 | void |
86 | TileSetParser::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 | |
166 | void |
167 | TileSetParser::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 | |
320 | std::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 | |