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