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