1// SuperTux
2// Copyright (C) 2018 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 "worldmap/worldmap_parser.hpp"
18
19#include <physfs.h>
20
21#include "object/ambient_light.hpp"
22#include "object/background.hpp"
23#include "object/decal.hpp"
24#include "object/music_object.hpp"
25#include "object/path_gameobject.hpp"
26#include "object/tilemap.hpp"
27#include "physfs/physfs_file_system.hpp"
28#include "physfs/util.hpp"
29#include "supertux/tile_manager.hpp"
30#include "util/file_system.hpp"
31#include "util/log.hpp"
32#include "util/reader.hpp"
33#include "util/reader_document.hpp"
34#include "util/reader_mapping.hpp"
35#include "util/reader_object.hpp"
36#include "worldmap/level_tile.hpp"
37#include "worldmap/spawn_point.hpp"
38#include "worldmap/special_tile.hpp"
39#include "worldmap/sprite_change.hpp"
40#include "worldmap/teleporter.hpp"
41#include "worldmap/tux.hpp"
42#include "worldmap/worldmap.hpp"
43#include "worldmap/worldmap_parser.hpp"
44#include "worldmap/worldmap_screen.hpp"
45
46namespace worldmap {
47
48WorldMapParser::WorldMapParser(WorldMap& worldmap) :
49 m_worldmap(worldmap)
50{
51}
52
53void
54WorldMapParser::load_worldmap(const std::string& filename)
55{
56 m_worldmap.m_map_filename = physfsutil::realpath(filename);
57 m_worldmap.m_levels_path = FileSystem::dirname(m_worldmap.m_map_filename);
58
59 try {
60 register_translation_directory(m_worldmap.m_map_filename);
61 auto doc = ReaderDocument::from_file(m_worldmap.m_map_filename);
62 auto root = doc.get_root();
63
64 if (root.get_name() != "supertux-level")
65 throw std::runtime_error("file isn't a supertux-level file.");
66
67 auto level_ = root.get_mapping();
68
69 level_.get("name", m_worldmap.m_name);
70
71 std::string tileset_name;
72 if (level_.get("tileset", tileset_name)) {
73 if (m_worldmap.m_tileset != nullptr) {
74 log_warning << "multiple tilesets specified in level_" << std::endl;
75 } else {
76 m_worldmap.m_tileset = TileManager::current()->get_tileset(tileset_name);
77 }
78 }
79 /* load default tileset */
80 if (m_worldmap.m_tileset == nullptr) {
81 m_worldmap.m_tileset = TileManager::current()->get_tileset("images/worldmap.strf");
82 }
83
84 boost::optional<ReaderMapping> sector;
85 if (!level_.get("sector", sector)) {
86 throw std::runtime_error("No sector specified in worldmap file.");
87 } else {
88 auto iter = sector->get_iter();
89 while (iter.next()) {
90 if (iter.get_key() == "tilemap") {
91 m_worldmap.add<TileMap>(m_worldmap.m_tileset, iter.as_mapping());
92 } else if (iter.get_key() == "background") {
93 m_worldmap.add<Background>(iter.as_mapping());
94 } else if (iter.get_key() == "music") {
95 const auto& sx = iter.get_sexp();
96 if (sx.is_array() && sx.as_array().size() == 2 && sx.as_array()[1].is_string()) {
97 std::string value;
98 iter.get(value);
99 m_worldmap.add<MusicObject>().set_music(value);
100 } else {
101 m_worldmap.add<MusicObject>(iter.as_mapping());
102 }
103 } else if (iter.get_key() == "init-script") {
104 iter.get(m_worldmap.m_init_script);
105 } else if (iter.get_key() == "worldmap-spawnpoint") {
106 auto sp = std::make_unique<SpawnPoint>(iter.as_mapping());
107 m_worldmap.m_spawn_points.push_back(std::move(sp));
108 } else if (iter.get_key() == "level") {
109 auto& level = m_worldmap.add<LevelTile>(m_worldmap.m_levels_path, iter.as_mapping());
110 load_level_information(level);
111 } else if (iter.get_key() == "special-tile") {
112 m_worldmap.add<SpecialTile>(iter.as_mapping());
113 } else if (iter.get_key() == "sprite-change") {
114 m_worldmap.add<SpriteChange>(iter.as_mapping());
115 } else if (iter.get_key() == "teleporter") {
116 m_worldmap.add<Teleporter>(iter.as_mapping());
117 } else if (iter.get_key() == "decal") {
118 m_worldmap.add<Decal>(iter.as_mapping());
119 } else if (iter.get_key() == "path") {
120 m_worldmap.add<PathGameObject>(iter.as_mapping());
121 } else if (iter.get_key() == "ambient-light") {
122 const auto& sx = iter.get_sexp();
123 if (sx.is_array() && sx.as_array().size() >= 3 &&
124 sx.as_array()[1].is_real() && sx.as_array()[2].is_real() && sx.as_array()[3].is_real())
125 {
126 // for backward compatibilty
127 std::vector<float> vColor;
128 bool hasColor = sector->get("ambient-light", vColor);
129 if (vColor.size() < 3 || !hasColor) {
130 log_warning << "(ambient-light) requires a color as argument" << std::endl;
131 } else {
132 m_worldmap.add<AmbientLight>(Color(vColor));
133 }
134 } else {
135 // modern format
136 m_worldmap.add<AmbientLight>(iter.as_mapping());
137 }
138 } else if (iter.get_key() == "name") {
139 // skip
140 } else {
141 log_warning << "Unknown token '" << iter.get_key() << "' in worldmap" << std::endl;
142 }
143 }
144 }
145
146 m_worldmap.flush_game_objects();
147
148 if (m_worldmap.get_solid_tilemaps().empty())
149 throw std::runtime_error("No solid tilemap specified");
150
151 m_worldmap.move_to_spawnpoint("main");
152
153 } catch(std::exception& e) {
154 std::stringstream msg;
155 msg << "Problem when parsing worldmap '" << m_worldmap.m_map_filename << "': " <<
156 e.what();
157 throw std::runtime_error(msg.str());
158 }
159
160 m_worldmap.finish_construction();
161}
162
163void
164WorldMapParser::load_level_information(LevelTile& level)
165{
166 /** get special_tile's title */
167 level.m_title = _("<no title>");
168 level.m_target_time = 0.0f;
169
170 try {
171 std::string filename = m_worldmap.m_levels_path + level.get_level_filename();
172
173 if (m_worldmap.m_levels_path == "./")
174 filename = level.get_level_filename();
175
176 if (!PHYSFS_exists(filename.c_str()))
177 {
178 log_warning << "Level file '" << filename << "' does not exist. Skipping." << std::endl;
179 return;
180 }
181 if (physfsutil::is_directory(filename))
182 {
183 log_warning << "Level file '" << filename << "' is a directory. Skipping." << std::endl;
184 return;
185 }
186
187 register_translation_directory(filename);
188 auto doc = ReaderDocument::from_file(filename);
189 auto root = doc.get_root();
190 if (root.get_name() != "supertux-level") {
191 return;
192 } else {
193 auto level_mapping = root.get_mapping();
194 level_mapping.get("name", level.m_title);
195 level_mapping.get("target-time", level.m_target_time);
196 }
197 } catch(std::exception& e) {
198 log_warning << "Problem when reading level information: " << e.what() << std::endl;
199 return;
200 }
201}
202
203} // namespace worldmap
204
205/* EOF */
206