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