Moved parsing of "tiles" section into separate function
[supertux.git] / src / supertux / tile_set_parser.cpp
1 //  SuperTux
2 //  Copyright (C) 2008 Matthias Braun <matze@braunis.de>
3 //                     Ingo Ruhnke <grumbel@gmx.de>
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 <stdexcept>
21 #include <sstream>
22
23 #include "lisp/list_iterator.hpp"
24 #include "lisp/parser.hpp"
25 #include "supertux/tile_set.hpp"
26 #include "util/file_system.hpp"
27
28 TileSetParser::TileSetParser(TileSet& tileset, const std::string& filename) :
29   m_tileset(tileset),
30   m_filename(filename),
31   m_tiles_path()
32 {  
33 }
34
35 void
36 TileSetParser::parse()
37 {
38   m_tiles_path = FileSystem::dirname(m_filename);
39
40   m_tileset.tiles.resize(1, 0);
41   m_tileset.tiles[0] = new Tile(m_tileset);
42
43   lisp::Parser parser;
44   const lisp::Lisp* root = parser.parse(m_filename);
45
46   const lisp::Lisp* tiles_lisp = root->get_lisp("supertux-tiles");
47   if(!tiles_lisp)
48     throw std::runtime_error("file is not a supertux tiles file.");
49
50   lisp::ListIterator iter(tiles_lisp);
51   while(iter.next()) 
52   {
53     if (iter.item() == "tile") 
54     {
55       std::auto_ptr<Tile> tile(new Tile(m_tileset));
56       uint32_t id = parse_tile(*tile, *iter.lisp());
57
58       if (id >= m_tileset.tiles.size())
59         m_tileset.tiles.resize(id+1, 0);
60
61       if (m_tileset.tiles[id] != 0) 
62       {
63         log_warning << "Tile with ID " << id << " redefined" << std::endl;
64       } 
65       else 
66       {
67         m_tileset.tiles[id] = tile.release();
68       }
69     } 
70     else if (iter.item() == "tilegroup") 
71     {
72       /* tilegroups are only interesting for the editor */
73     } 
74     else if (iter.item() == "tiles") 
75     {
76       parse_tiles(*iter.lisp());
77     }
78     else 
79     {
80       log_warning << "Unknown symbol '" << iter.item() << "' in tileset file" << std::endl;
81     }
82   }
83 }
84
85 uint32_t
86 TileSetParser::parse_tile(Tile& tile, const Reader& reader)
87 {
88   uint32_t id;
89   if (!reader.get("id", id)) 
90   {
91     throw std::runtime_error("Missing tile-id.");
92   }
93
94   bool value = false;
95   if(reader.get("solid", value) && value)
96     tile.attributes |= Tile::SOLID;
97   if(reader.get("unisolid", value) && value)
98     tile.attributes |= Tile::UNISOLID | Tile::SOLID;
99   if(reader.get("brick", value) && value)
100     tile.attributes |= Tile::BRICK;
101   if(reader.get("ice", value) && value)
102     tile.attributes |= Tile::ICE;
103   if(reader.get("water", value) && value)
104     tile.attributes |= Tile::WATER;
105   if(reader.get("hurts", value) && value)
106     tile.attributes |= Tile::HURTS;
107   if(reader.get("fire", value) && value)
108     tile.attributes |= Tile::FIRE;
109   if(reader.get("fullbox", value) && value)
110     tile.attributes |= Tile::FULLBOX;
111   if(reader.get("coin", value) && value)
112     tile.attributes |= Tile::COIN;
113   if(reader.get("goal", value) && value)
114     tile.attributes |= Tile::GOAL;
115
116   if(reader.get("north", value) && value)
117     tile.data |= Tile::WORLDMAP_NORTH;
118   if(reader.get("south", value) && value)
119     tile.data |= Tile::WORLDMAP_SOUTH;
120   if(reader.get("west", value) && value)
121     tile.data |= Tile::WORLDMAP_WEST;
122   if(reader.get("east", value) && value)
123     tile.data |= Tile::WORLDMAP_EAST;
124   if(reader.get("stop", value) && value)
125     tile.data |= Tile::WORLDMAP_STOP;
126
127   reader.get("data", tile.data);
128   reader.get("anim-fps", tile.anim_fps);
129
130   if(reader.get("slope-type", tile.data)) {
131     tile.attributes |= Tile::SOLID | Tile::SLOPE;
132   }
133
134   const lisp::Lisp* images;
135 #ifndef NDEBUG
136   images = reader.get_lisp("editor-images");
137   if(images)
138     parse_tile_images(tile, *images);
139   else {
140 #endif
141     images = reader.get_lisp("images");
142     if(images)
143       parse_tile_images(tile, *images);
144 #ifndef NDEBUG
145   }
146 #endif
147
148   tile.correct_attributes();
149
150   return id;
151 }
152
153 void
154 TileSetParser::parse_tile_images(Tile& tile, const Reader& images_lisp)
155 {
156   const lisp::Lisp* list = &images_lisp;
157   while(list) 
158   {
159     const lisp::Lisp* cur = list->get_car();
160
161     if(cur->get_type() == lisp::Lisp::TYPE_STRING) 
162     {
163       std::string file;
164       cur->get(file);
165       tile.imagespecs.push_back(Tile::ImageSpec(m_tiles_path + file, Rect(0, 0, 0, 0)));
166     }
167     else if(cur->get_type() == lisp::Lisp::TYPE_CONS &&
168             cur->get_car()->get_type() == lisp::Lisp::TYPE_SYMBOL &&
169             cur->get_car()->get_symbol() == "region") 
170     {
171       const lisp::Lisp* ptr = cur->get_cdr();
172
173       std::string file;
174       float x = 0;
175       float y = 0;
176       float w = 0;
177       float h = 0;
178       ptr->get_car()->get(file); ptr = ptr->get_cdr();
179       ptr->get_car()->get(x); ptr = ptr->get_cdr();
180       ptr->get_car()->get(y); ptr = ptr->get_cdr();
181       ptr->get_car()->get(w); ptr = ptr->get_cdr();
182       ptr->get_car()->get(h);
183       tile.imagespecs.push_back(Tile::ImageSpec(m_tiles_path + file, Rect(x, y, x+w, y+h)));
184     } 
185     else 
186     {
187       log_warning << "Expected string or list in images tag" << std::endl;
188     }
189
190     list = list->get_cdr();
191   }
192 }
193
194 void
195 TileSetParser::parse_tiles(const Reader& reader)
196 {
197   // List of ids (use 0 if the tile should be ignored)
198   std::vector<uint32_t> ids;
199   // List of attributes of the tile
200   std::vector<uint32_t> attributes;
201   // List of data for the tiles
202   std::vector<uint32_t> datas;
203   //List of frames that the tiles come in
204   std::vector<std::string> images;
205
206   // width and height of the image in tile units, this is used for two
207   // purposes:
208   //  a) so we don't have to load the image here to know its dimensions
209   //  b) so that the resulting 'tiles' entry is more robust,
210   //  ie. enlarging the image won't break the tile id mapping
211   // FIXME: height is actually not used, since width might be enough for
212   // all purposes, still feels somewhat more natural this way
213   unsigned int width  = 0;
214   unsigned int height = 0;
215
216   reader.get("ids",        ids);
217   bool has_attributes = reader.get("attributes", attributes);
218   bool has_datas = reader.get("datas", datas);
219
220   if (!reader.get("image",      images))
221   {
222     reader.get( "images",      images);
223   }
224
225   // make the image path absolute
226   for(std::vector<std::string>::iterator i = images.begin(); i != images.end(); ++i)
227   {
228     *i = m_tiles_path + *i;
229   }
230
231   reader.get("width",      width);
232   reader.get("height",     height);
233
234   float animfps = 10;
235   reader.get("anim-fps",     animfps);
236
237   if (images.size() <= 0) 
238   {
239     throw std::runtime_error("No images in tile.");
240   }
241   else if (animfps < 0) 
242   {
243     throw std::runtime_error("Negative fps.");
244   }
245   else if (ids.size() != width*height) 
246   {
247     std::ostringstream err;
248     err << "Number of ids (" << ids.size() <<  ") and size of image (" << width*height
249         << ") mismatch for image '" << images[0] << "', but must be equal";
250     throw std::runtime_error(err.str());
251   }
252   else if (has_attributes && ids.size() != attributes.size()) 
253   {
254     std::ostringstream err;
255     err << "Number of ids (" << ids.size() <<  ") and attributes (" << attributes.size()
256         << ") mismatch for image '" << images[0] << "', but must be equal";
257     throw std::runtime_error(err.str());
258   }
259   else if (has_datas && ids.size() != datas.size()) 
260   {        
261     std::ostringstream err;
262     err << "Number of ids (" << ids.size() <<  ") and datas (" << datas.size()
263         << ") mismatch for image '" << images[0] << "', but must be equal";
264     throw std::runtime_error(err.str());
265   }
266   else
267   {
268     for(std::vector<uint32_t>::size_type i = 0; i < ids.size() && i < width*height; ++i) 
269     {
270       if (ids[i] != 0)
271       {
272         if (ids[i] >= m_tileset.tiles.size())
273           m_tileset.tiles.resize(ids[i]+1, 0);
274
275         int x = 32*(i % width);
276         int y = 32*(i / width);
277         std::auto_ptr<Tile> tile(new Tile(m_tileset, images, Rect(x, y, x + 32, y + 32),
278                                           (has_attributes ? attributes[i] : 0), (has_datas ? datas[i] : 0), animfps));
279         if (m_tileset.tiles[ids[i]] == 0) {
280           m_tileset.tiles[ids[i]] = tile.release();
281         } else {
282           log_warning << "Tile with ID " << ids[i] << " redefined" << std::endl;
283         }
284       }
285     }
286   }  
287 }
288
289 /* EOF */