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