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
44static const std::string DEFAULT_BG = "images/background/arctis2.png";
45
46std::unique_ptr<Sector>
47SectorParser::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
56std::unique_ptr<Sector>
57SectorParser::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
66std::unique_ptr<Sector>
67SectorParser::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
76SectorParser::SectorParser(Sector& sector, bool editable) :
77 m_sector(sector),
78 m_editable(editable)
79{
80}
81
82std::unique_ptr<GameObject>
83SectorParser::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
97void
98SectorParser::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
151void
152SectorParser::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
306void
307SectorParser::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