1 | // SuperTux |
2 | // Copyright (C) 2015 Ingo Ruhnke <grumbel@gmail.com> |
3 | // |
4 | // This program is free software: you can redistribute it and/or modify |
5 | // it under the terms of the GNU General Public License as published by |
6 | // the Free Software Foundation, either version 3 of the License, or |
7 | // (at your option) any later version. |
8 | // |
9 | // This program is distributed in the hope that it will be useful, |
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | // GNU General Public License for more details. |
13 | // |
14 | // You should have received a copy of the GNU General Public License |
15 | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | |
17 | #include "supertux/sector_parser.hpp" |
18 | |
19 | #include <iostream> |
20 | #include <physfs.h> |
21 | #include <sexp/value.hpp> |
22 | |
23 | #include "badguy/jumpy.hpp" |
24 | #include "editor/editor.hpp" |
25 | #include "editor/worldmap_objects.hpp" |
26 | #include "object/ambient_light.hpp" |
27 | #include "object/background.hpp" |
28 | #include "object/camera.hpp" |
29 | #include "object/cloud_particle_system.hpp" |
30 | #include "object/gradient.hpp" |
31 | #include "object/music_object.hpp" |
32 | #include "object/rain_particle_system.hpp" |
33 | #include "object/snow_particle_system.hpp" |
34 | #include "object/spawnpoint.hpp" |
35 | #include "object/tilemap.hpp" |
36 | #include "supertux/game_object_factory.hpp" |
37 | #include "supertux/level.hpp" |
38 | #include "supertux/sector.hpp" |
39 | #include "supertux/tile.hpp" |
40 | #include "supertux/tile_manager.hpp" |
41 | #include "util/reader_collection.hpp" |
42 | #include "util/reader_mapping.hpp" |
43 | |
44 | static const std::string DEFAULT_BG = "images/background/arctis2.png" ; |
45 | |
46 | std::unique_ptr<Sector> |
47 | SectorParser::from_reader(Level& level, const ReaderMapping& reader, bool editable) |
48 | { |
49 | auto sector = std::make_unique<Sector>(level); |
50 | BIND_SECTOR(*sector); |
51 | SectorParser parser(*sector, editable); |
52 | parser.parse(reader); |
53 | return sector; |
54 | } |
55 | |
56 | std::unique_ptr<Sector> |
57 | SectorParser::from_reader_old_format(Level& level, const ReaderMapping& reader, bool editable) |
58 | { |
59 | auto sector = std::make_unique<Sector>(level); |
60 | BIND_SECTOR(*sector); |
61 | SectorParser parser(*sector, editable); |
62 | parser.parse_old_format(reader); |
63 | return sector; |
64 | } |
65 | |
66 | std::unique_ptr<Sector> |
67 | SectorParser::from_nothing(Level& level) |
68 | { |
69 | auto sector = std::make_unique<Sector>(level); |
70 | BIND_SECTOR(*sector); |
71 | SectorParser parser(*sector, false); |
72 | parser.create_sector(); |
73 | return sector; |
74 | } |
75 | |
76 | SectorParser::SectorParser(Sector& sector, bool editable) : |
77 | m_sector(sector), |
78 | m_editable(editable) |
79 | { |
80 | } |
81 | |
82 | std::unique_ptr<GameObject> |
83 | SectorParser::parse_object(const std::string& name_, const ReaderMapping& reader) |
84 | { |
85 | if (name_ == "money" ) { // for compatibility with old maps |
86 | return std::make_unique<Jumpy>(reader); |
87 | } else { |
88 | try { |
89 | return GameObjectFactory::instance().create(name_, reader); |
90 | } catch(std::exception& e) { |
91 | log_warning << e.what() << "" << std::endl; |
92 | return {}; |
93 | } |
94 | } |
95 | } |
96 | |
97 | void |
98 | SectorParser::parse(const ReaderMapping& sector) |
99 | { |
100 | auto iter = sector.get_iter(); |
101 | while (iter.next()) { |
102 | if (iter.get_key() == "name" ) { |
103 | std::string value; |
104 | iter.get(value); |
105 | m_sector.set_name(value); |
106 | } else if (iter.get_key() == "gravity" ) { |
107 | float value; |
108 | iter.get(value); |
109 | m_sector.set_gravity(value); |
110 | } else if (iter.get_key() == "music" ) { |
111 | const auto& sx = iter.get_sexp(); |
112 | if (sx.is_array() && sx.as_array().size() == 2 && sx.as_array()[1].is_string()) { |
113 | std::string value; |
114 | iter.get(value); |
115 | m_sector.add<MusicObject>().set_music(value); |
116 | } else { |
117 | m_sector.add<MusicObject>(iter.as_mapping()); |
118 | } |
119 | } else if (iter.get_key() == "init-script" ) { |
120 | std::string value; |
121 | iter.get(value); |
122 | m_sector.set_init_script(value); |
123 | } else if (iter.get_key() == "ambient-light" ) { |
124 | const auto& sx = iter.get_sexp(); |
125 | if (sx.is_array() && sx.as_array().size() >= 3 && |
126 | sx.as_array()[1].is_real() && sx.as_array()[2].is_real() && sx.as_array()[3].is_real()) |
127 | { |
128 | // for backward compatibilty |
129 | std::vector<float> vColor; |
130 | bool hasColor = sector.get("ambient-light" , vColor); |
131 | if (vColor.size() < 3 || !hasColor) { |
132 | log_warning << "(ambient-light) requires a color as argument" << std::endl; |
133 | } else { |
134 | m_sector.add<AmbientLight>(Color(vColor)); |
135 | } |
136 | } else { |
137 | // modern format |
138 | m_sector.add<AmbientLight>(iter.as_mapping()); |
139 | } |
140 | } else { |
141 | auto object = parse_object(iter.get_key(), iter.as_mapping()); |
142 | if (object) { |
143 | m_sector.add_object(std::move(object)); |
144 | } |
145 | } |
146 | } |
147 | |
148 | m_sector.finish_construction(m_editable); |
149 | } |
150 | |
151 | void |
152 | SectorParser::parse_old_format(const ReaderMapping& reader) |
153 | { |
154 | m_sector.set_name("main" ); |
155 | |
156 | float gravity; |
157 | if (reader.get("gravity" , gravity)) |
158 | m_sector.set_gravity(gravity); |
159 | |
160 | std::string backgroundimage; |
161 | if (reader.get("background" , backgroundimage) && (!backgroundimage.empty())) { |
162 | if (backgroundimage == "arctis.png" ) backgroundimage = "arctis.jpg" ; |
163 | if (backgroundimage == "arctis2.jpg" ) backgroundimage = "arctis.jpg" ; |
164 | if (backgroundimage == "ocean.png" ) backgroundimage = "ocean.jpg" ; |
165 | backgroundimage = "images/background/" + backgroundimage; |
166 | if (!PHYSFS_exists(backgroundimage.c_str())) { |
167 | log_warning << "Background image \"" << backgroundimage << "\" not found. Ignoring." << std::endl; |
168 | backgroundimage = "" ; |
169 | } |
170 | } |
171 | |
172 | float bgspeed = .5; |
173 | reader.get("bkgd_speed" , bgspeed); |
174 | bgspeed /= 100; |
175 | |
176 | Color bkgd_top, bkgd_bottom; |
177 | int r = 0, g = 0, b = 128; |
178 | reader.get("bkgd_red_top" , r); |
179 | reader.get("bkgd_green_top" , g); |
180 | reader.get("bkgd_blue_top" , b); |
181 | bkgd_top.red = static_cast<float> (r) / 255.0f; |
182 | bkgd_top.green = static_cast<float> (g) / 255.0f; |
183 | bkgd_top.blue = static_cast<float> (b) / 255.0f; |
184 | |
185 | reader.get("bkgd_red_bottom" , r); |
186 | reader.get("bkgd_green_bottom" , g); |
187 | reader.get("bkgd_blue_bottom" , b); |
188 | bkgd_bottom.red = static_cast<float> (r) / 255.0f; |
189 | bkgd_bottom.green = static_cast<float> (g) / 255.0f; |
190 | bkgd_bottom.blue = static_cast<float> (b) / 255.0f; |
191 | |
192 | if (!backgroundimage.empty()) { |
193 | auto& background = m_sector.add<Background>(); |
194 | background.set_image(backgroundimage); |
195 | background.set_speed(bgspeed); |
196 | } else { |
197 | auto& gradient = m_sector.add<Gradient>(); |
198 | gradient.set_gradient(bkgd_top, bkgd_bottom); |
199 | } |
200 | |
201 | std::string particlesystem; |
202 | reader.get("particle_system" , particlesystem); |
203 | if (particlesystem == "clouds" ) |
204 | m_sector.add<CloudParticleSystem>(); |
205 | else if (particlesystem == "snow" ) |
206 | m_sector.add<SnowParticleSystem>(); |
207 | else if (particlesystem == "rain" ) |
208 | m_sector.add<RainParticleSystem>(); |
209 | |
210 | Vector startpos(100, 170); |
211 | reader.get("start_pos_x" , startpos.x); |
212 | reader.get("start_pos_y" , startpos.y); |
213 | |
214 | m_sector.add<SpawnPointMarker>("main" , startpos); |
215 | |
216 | m_sector.add<MusicObject>().set_music("music/chipdisko.ogg" ); |
217 | // skip reading music filename. It's all .ogg now, anyway |
218 | /* |
219 | reader.get("music", music); |
220 | m_sector.set_music("music/" + m_sector.get_music()); |
221 | */ |
222 | |
223 | int width = 30, height = 15; |
224 | reader.get("width" , width); |
225 | reader.get("height" , height); |
226 | |
227 | std::vector<unsigned int> tiles; |
228 | if (reader.get("interactive-tm" , tiles) |
229 | || reader.get("tilemap" , tiles)) { |
230 | auto* tileset = TileManager::current()->get_tileset(m_sector.get_level().get_tileset()); |
231 | auto& tilemap = m_sector.add<TileMap>(tileset); |
232 | tilemap.set(width, height, tiles, LAYER_TILES, true); |
233 | |
234 | // replace tile id 112 (old invisible tile) with 1311 (new invisible tile) |
235 | for (int x=0; x < tilemap.get_width(); ++x) { |
236 | for (int y=0; y < tilemap.get_height(); ++y) { |
237 | uint32_t id = tilemap.get_tile_id(x, y); |
238 | if (id == 112) |
239 | tilemap.change(x, y, 1311); |
240 | } |
241 | } |
242 | |
243 | if (height < 19) tilemap.resize(width, 19); |
244 | } |
245 | |
246 | if (reader.get("background-tm" , tiles)) { |
247 | auto* tileset = TileManager::current()->get_tileset(m_sector.get_level().get_tileset()); |
248 | auto& tilemap = m_sector.add<TileMap>(tileset); |
249 | tilemap.set(width, height, tiles, LAYER_BACKGROUNDTILES, false); |
250 | if (height < 19) tilemap.resize(width, 19); |
251 | } |
252 | |
253 | if (reader.get("foreground-tm" , tiles)) { |
254 | auto* tileset = TileManager::current()->get_tileset(m_sector.get_level().get_tileset()); |
255 | auto& tilemap = m_sector.add<TileMap>(tileset); |
256 | tilemap.set(width, height, tiles, LAYER_FOREGROUNDTILES, false); |
257 | |
258 | // fill additional space in foreground with tiles of ID 2035 (lightmap/black) |
259 | if (height < 19) tilemap.resize(width, 19, 2035); |
260 | } |
261 | |
262 | // read reset-points (now spawn-points) |
263 | boost::optional<ReaderMapping> resetpoints; |
264 | if (reader.get("reset-points" , resetpoints)) { |
265 | auto iter = resetpoints->get_iter(); |
266 | while (iter.next()) { |
267 | if (iter.get_key() == "point" ) { |
268 | Vector sp_pos; |
269 | if (reader.get("x" , sp_pos.x) && reader.get("y" , sp_pos.y)) |
270 | { |
271 | m_sector.add<SpawnPointMarker>("main" , sp_pos); |
272 | } |
273 | } else { |
274 | log_warning << "Unknown token '" << iter.get_key() << "' in reset-points." << std::endl; |
275 | } |
276 | } |
277 | } |
278 | |
279 | // read objects |
280 | boost::optional<ReaderCollection> objects; |
281 | if (reader.get("objects" , objects)) { |
282 | for (auto const& obj : objects->get_objects()) |
283 | { |
284 | auto object = parse_object(obj.get_name(), obj.get_mapping()); |
285 | if (object) { |
286 | m_sector.add_object(std::move(object)); |
287 | } else { |
288 | log_warning << "Unknown object '" << obj.get_name() << "' in level." << std::endl; |
289 | } |
290 | } |
291 | } |
292 | |
293 | // add a camera |
294 | auto camera_ = std::make_unique<Camera>("Camera" ); |
295 | m_sector.add_object(std::move(camera_)); |
296 | |
297 | m_sector.flush_game_objects(); |
298 | |
299 | if (m_sector.get_solid_tilemaps().empty()) { |
300 | log_warning << "sector '" << m_sector.get_name() << "' does not contain a solid tile layer." << std::endl; |
301 | } |
302 | |
303 | m_sector.finish_construction(m_editable); |
304 | } |
305 | |
306 | void |
307 | SectorParser::create_sector() |
308 | { |
309 | auto tileset = TileManager::current()->get_tileset(m_sector.get_level().get_tileset()); |
310 | bool worldmap = m_sector.get_level().is_worldmap(); |
311 | if (!worldmap) |
312 | { |
313 | auto& background = m_sector.add<Background>(); |
314 | background.set_image(DEFAULT_BG); |
315 | background.set_speed(0.5); |
316 | |
317 | auto& bkgrd = m_sector.add<TileMap>(tileset); |
318 | bkgrd.resize(100, 35); |
319 | bkgrd.set_layer(-100); |
320 | bkgrd.set_solid(false); |
321 | |
322 | auto& frgrd = m_sector.add<TileMap>(tileset); |
323 | frgrd.resize(100, 35); |
324 | frgrd.set_layer(100); |
325 | frgrd.set_solid(false); |
326 | } |
327 | |
328 | auto& intact = m_sector.add<TileMap>(tileset); |
329 | if (worldmap) { |
330 | intact.resize(100, 100, 9); |
331 | } else { |
332 | intact.resize(100, 35, 0); |
333 | } |
334 | intact.set_layer(0); |
335 | intact.set_solid(true); |
336 | |
337 | if (worldmap) { |
338 | m_sector.add<worldmap_editor::WorldmapSpawnPoint>("main" , Vector(4, 4)); |
339 | } else { |
340 | m_sector.add<SpawnPointMarker>("main" , Vector(64, 480)); |
341 | } |
342 | |
343 | m_sector.add<Camera>("Camera" ); |
344 | m_sector.add<MusicObject>(); |
345 | |
346 | m_sector.flush_game_objects(); |
347 | |
348 | m_sector.finish_construction(m_editable); |
349 | } |
350 | |
351 | /* EOF */ |
352 | |