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