1// SuperTux
2// Copyright (C) 2006 Matthias Braun <matze@braunis.de>
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/level.hpp"
18
19#include "badguy/goldbomb.hpp"
20#include "object/bonus_block.hpp"
21#include "object/coin.hpp"
22#include "physfs/util.hpp"
23#include "supertux/sector.hpp"
24#include "trigger/secretarea_trigger.hpp"
25#include "util/file_system.hpp"
26#include "util/log.hpp"
27#include "util/writer.hpp"
28
29#include <physfs.h>
30
31Level* Level::s_current = nullptr;
32
33Level::Level(bool worldmap) :
34 m_is_worldmap(worldmap),
35 m_name("noname"),
36 m_author("SuperTux Player"),
37 m_contact(),
38 m_license(),
39 m_filename(),
40 m_sectors(),
41 m_stats(),
42 m_target_time(),
43 m_tileset("images/tiles.strf")
44{
45 s_current = this;
46}
47
48Level::~Level()
49{
50 m_sectors.clear();
51}
52
53void
54Level::save(std::ostream& stream)
55{
56 Writer writer(stream);
57 save(writer);
58}
59
60void
61Level::save(const std::string& filepath, bool retry)
62{
63 //FIXME: It tests for directory in supertux/data, but saves into .supertux2.
64 try {
65 { // make sure the level directory exists
66 std::string dirname = FileSystem::dirname(filepath);
67 if (!PHYSFS_exists(dirname.c_str()))
68 {
69 if (!PHYSFS_mkdir(dirname.c_str()))
70 {
71 std::ostringstream msg;
72 msg << "Couldn't create directory for level '"
73 << dirname << "': " <<PHYSFS_getLastErrorCode();
74 throw std::runtime_error(msg.str());
75 }
76 }
77
78 if (!physfsutil::is_directory(dirname))
79 {
80 std::ostringstream msg;
81 msg << "Level path '" << dirname << "' is not a directory";
82 throw std::runtime_error(msg.str());
83 }
84 }
85
86 Writer writer(filepath);
87 save(writer);
88 log_warning << "Level saved as " << filepath << "." << std::endl;
89 } catch(std::exception& e) {
90 if (retry) {
91 std::stringstream msg;
92 msg << "Problem when saving level '" << filepath << "': " << e.what();
93 throw std::runtime_error(msg.str());
94 } else {
95 log_warning << "Failed to save the level, retrying..." << std::endl;
96 { // create the level directory again
97 std::string dirname = FileSystem::dirname(filepath);
98 if (!PHYSFS_mkdir(dirname.c_str()))
99 {
100 std::ostringstream msg;
101 msg << "Couldn't create directory for level '"
102 << dirname << "': " <<PHYSFS_getLastErrorCode();
103 throw std::runtime_error(msg.str());
104 }
105 }
106 save(filepath, true);
107 }
108 }
109}
110
111void
112Level::save(Writer& writer)
113{
114 writer.start_list("supertux-level");
115 // Starts writing to supertux level file. Keep this at the very beginning.
116
117 writer.write("version", 3);
118 writer.write("name", m_name, true);
119 writer.write("author", m_author, false);
120 if (!m_contact.empty()) {
121 writer.write("contact", m_contact, false);
122 }
123 if (!m_license.empty()) {
124 writer.write("license", m_license, false);
125 }
126 if (m_target_time != 0.0f){
127 writer.write("target-time", m_target_time);
128 }
129
130 for (auto& sector : m_sectors) {
131 sector->save(writer);
132 }
133
134 if (m_tileset != "images/tiles.strf")
135 writer.write("tileset", m_tileset, false);
136
137 // Ends writing to supertux level file. Keep this at the very end.
138 writer.end_list("supertux-level");
139}
140
141void
142Level::add_sector(std::unique_ptr<Sector> sector)
143{
144 Sector* test = get_sector(sector->get_name());
145 if (test != nullptr) {
146 throw std::runtime_error("Trying to add 2 sectors with same name");
147 } else {
148 m_sectors.push_back(std::move(sector));
149 }
150}
151
152Sector*
153Level::get_sector(const std::string& name_) const
154{
155 for (auto const& sector : m_sectors) {
156 if (sector->get_name() == name_) {
157 return sector.get();
158 }
159 }
160 return nullptr;
161}
162
163size_t
164Level::get_sector_count() const
165{
166 return m_sectors.size();
167}
168
169Sector*
170Level::get_sector(size_t num) const
171{
172 return m_sectors.at(num).get();
173}
174
175int
176Level::get_total_coins() const
177{
178 int total_coins = 0;
179 for (auto const& sector : m_sectors) {
180 for (const auto& o: sector->get_objects()) {
181 auto coin = dynamic_cast<Coin*>(o.get());
182 if (coin)
183 {
184 total_coins++;
185 continue;
186 }
187 auto block = dynamic_cast<BonusBlock*>(o.get());
188 if (block)
189 {
190 if (block->get_contents() == BonusBlock::Content::COIN)
191 {
192 total_coins += block->get_hit_counter();
193 continue;
194 } else if (block->get_contents() == BonusBlock::Content::RAIN ||
195 block->get_contents() == BonusBlock::Content::EXPLODE)
196 {
197 total_coins += 10;
198 continue;
199 }
200 }
201 auto goldbomb = dynamic_cast<GoldBomb*>(o.get());
202 if (goldbomb)
203 total_coins += 10;
204 }
205 }
206 return total_coins;
207}
208
209int
210Level::get_total_badguys() const
211{
212 int total_badguys = 0;
213 for (auto const& sector : m_sectors) {
214 total_badguys += sector->get_object_count<BadGuy>([] (const BadGuy& badguy) {
215 return badguy.m_countMe;
216 });
217 }
218 return total_badguys;
219}
220
221int
222Level::get_total_secrets() const
223{
224 int total_secrets = 0;
225 for (auto const& sector : m_sectors) {
226 total_secrets += sector->get_object_count<SecretAreaTrigger>();
227 }
228 return total_secrets;
229}
230
231void
232Level::reactivate()
233{
234 s_current = this;
235}
236
237/* EOF */
238