1 //  SuperTux
2 //  Copyright (C) 2008-2020 A. Semphris <semphris@protonmail.com>,
3 //                          Matthias Braun <matze@braunis.de>,
4 //                          Ingo Ruhnke <grumbel@gmail.com>
5 //
6 //  This program is free software: you can redistribute it and/or modify
7 //  it under the terms of the GNU General Public License as published by
8 //  the Free Software Foundation, either version 3 of the License, or
9 //  (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 
19 #include "supertux/autotile_parser.hpp"
20 
21 #include <sstream>
22 #include <sexp/value.hpp>
23 #include <sexp/io.hpp>
24 
25 #include "supertux/gameconfig.hpp"
26 #include "supertux/globals.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 
AutotileParser(std::vector<AutotileSet * > * autotilesets,const std::string & filename)32 AutotileParser::AutotileParser(std::vector<AutotileSet*>* autotilesets, const std::string& filename) :
33   m_autotilesets(autotilesets),
34   m_filename(filename),
35   m_tiles_path()
36 {
37 }
38 
39 void
parse()40 AutotileParser::parse()
41 {
42   m_tiles_path = FileSystem::dirname(m_filename);
43 
44   auto doc = ReaderDocument::from_file(m_filename);
45   auto root = doc.get_root();
46 
47   if (root.get_name() != "supertux-autotiles") {
48     throw std::runtime_error("file is not a supertux autotile configuration file.");
49   }
50 
51   auto iter = root.get_mapping().get_iter();
52   while (iter.next())
53   {
54     if (iter.get_key() == "autotileset")
55     {
56       ReaderMapping tile_mapping = iter.as_mapping();
57       parse_autotileset(tile_mapping, false);
58     }
59     else if (iter.get_key() == "autotileset-corner")
60     {
61       ReaderMapping tile_mapping = iter.as_mapping();
62       parse_autotileset(tile_mapping, true);
63     }
64     else
65     {
66       log_warning << "Unknown symbol '" << iter.get_key() << "' in autotile config file" << std::endl;
67     }
68   }
69 }
70 
71 void
parse_autotileset(const ReaderMapping & reader,bool corner)72 AutotileParser::parse_autotileset(const ReaderMapping& reader, bool corner)
73 {
74   std::vector<Autotile*>* autotiles = new std::vector<Autotile*>();
75 
76   std::string name = "[unnamed]";
77   if (!reader.get("name", name))
78   {
79     log_warning << "Unnamed autotileset parsed" << std::endl;
80   }
81 
82   uint32_t default_id = 0;
83   if (!reader.get("default", default_id))
84   {
85     log_warning << "No default tile for autotileset " << name << std::endl;
86   }
87 
88   auto iter = reader.get_iter();
89   while (iter.next())
90   {
91     if (iter.get_key() == "autotile")
92     {
93       ReaderMapping tile_mapping = iter.as_mapping();
94       autotiles->push_back(parse_autotile(tile_mapping, corner));
95     }
96     else if (iter.get_key() != "name" && iter.get_key() != "default")
97     {
98       log_warning << "Unknown symbol '" << iter.get_key() << "' in autotile config file" << std::endl;
99     }
100   }
101 
102   AutotileSet* autotileset = new AutotileSet(*autotiles, default_id, name, corner);
103 
104   if (g_config->developer_mode)
105   {
106     autotileset->validate();
107   }
108 
109   m_autotilesets->push_back(autotileset);
110 }
111 
112 Autotile*
parse_autotile(const ReaderMapping & reader,bool corner)113 AutotileParser::parse_autotile(const ReaderMapping& reader, bool corner)
114 {
115   std::vector<AutotileMask*> autotile_masks;
116   std::vector<std::pair<uint32_t, float>> alt_ids;
117 
118   uint32_t tile_id;
119   if (!reader.get("id", tile_id))
120   {
121     throw std::runtime_error("Missing 'id' parameter in autotileset config file.");
122   }
123 
124   bool solid;
125   if (!reader.get("solid", solid))
126   {
127     if (!corner)
128       throw std::runtime_error("Missing 'solid' parameter in autotileset config file.");
129   }
130   else
131   {
132     if (corner)
133       throw std::runtime_error("'solid' parameter not needed for corner-based tiles in autotileset config file.");
134   }
135 
136   auto iter = reader.get_iter();
137   while (iter.next())
138   {
139     if (iter.get_key() == "mask")
140     {
141       std::string mask;
142       iter.get(mask);
143 
144       if (corner)
145       {
146         parse_mask_corner(mask, &autotile_masks);
147       }
148       else
149       {
150         parse_mask(mask, &autotile_masks, solid);
151       }
152     }
153     else if (iter.get_key() == "alt-id")
154     {
155       ReaderMapping alt_reader = iter.as_mapping();
156 
157       uint32_t alt_id = 0;
158       if (!alt_reader.get("id", alt_id))
159       {
160         log_warning << "No alt tile for autotileset" << std::endl;
161       }
162 
163       float weight = 0.0f;
164       if (!alt_reader.get("weight", weight))
165       {
166         log_warning << "No weight for alt tile id" << std::endl;
167       }
168 
169       if (alt_id != 0 && weight != 0.0f)
170       {
171         alt_ids.push_back(std::pair<uint32_t, float>(alt_id, weight));
172       }
173     }
174     else if (iter.get_key() != "id" && iter.get_key() != "solid")
175     {
176       log_warning << "Unknown symbol '" << iter.get_key() << "' in autotile config file" << std::endl;
177     }
178   }
179 
180   return new Autotile(tile_id, alt_ids, autotile_masks, !!solid);
181 }
182 
183 void
parse_mask(std::string mask,std::vector<AutotileMask * > * autotile_masks,bool solid)184 AutotileParser::parse_mask(std::string mask, std::vector<AutotileMask*>* autotile_masks, bool solid)
185 {
186   if (mask.size() != 8)
187   {
188     throw std::runtime_error("Autotile config : mask isn't exactly 8 characters.");
189   }
190 
191   std::vector<uint8_t> masks;
192 
193   masks.push_back(0);
194 
195   for (int i = 0; i < 8; i++)
196   {
197     std::vector<uint8_t> new_masks;
198     switch (mask[i])
199     {
200     case '0':
201       for (uint8_t val : masks)
202       {
203         new_masks.push_back(static_cast<uint8_t>(val * 2));
204       }
205       break;
206     case '1':
207       for (uint8_t val : masks)
208       {
209         new_masks.push_back(static_cast<uint8_t>(val * 2 + 1));
210       }
211       break;
212     case '*':
213       for (uint8_t val : masks)
214       {
215         new_masks.push_back(static_cast<uint8_t>(val * 2));
216         new_masks.push_back(static_cast<uint8_t>(val * 2 + 1));
217       }
218       break;
219     default:
220       throw std::runtime_error("Autotile config : unrecognized character");
221     }
222     masks = new_masks;
223   }
224 
225   for (uint8_t val : masks)
226   {
227     autotile_masks->push_back(new AutotileMask(val, solid));
228   }
229 }
230 
231 void
parse_mask_corner(std::string mask,std::vector<AutotileMask * > * autotile_masks)232 AutotileParser::parse_mask_corner(std::string mask, std::vector<AutotileMask*>* autotile_masks)
233 {
234   if (mask.size() != 4)
235   {
236     throw std::runtime_error("Autotile config : corner-based mask isn't exactly 4 characters.");
237   }
238 
239   std::vector<uint8_t> masks;
240 
241   masks.push_back(0);
242 
243   for (int i = 0; i < 4; i++)
244   {
245     std::vector<uint8_t> new_masks;
246     switch (mask[i])
247     {
248     case '0':
249       for (uint8_t val : masks)
250       {
251         new_masks.push_back(static_cast<uint8_t>(val * 2));
252       }
253       break;
254     case '1':
255       for (uint8_t val : masks)
256       {
257         new_masks.push_back(static_cast<uint8_t>(val * 2 + 1));
258       }
259       break;
260     case '*':
261       for (uint8_t val : masks)
262       {
263         new_masks.push_back(static_cast<uint8_t>(val * 2));
264         new_masks.push_back(static_cast<uint8_t>(val * 2 + 1));
265       }
266       break;
267     default:
268       throw std::runtime_error("Autotile config : unrecognized character");
269     }
270     masks = new_masks;
271   }
272 
273   for (uint8_t val : masks)
274   {
275     autotile_masks->push_back(new AutotileMask(val, true));
276   }
277 }
278 
279 /* EOF */
280