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/world.hpp"
18
19#include <physfs.h>
20
21#include "physfs/util.hpp"
22#include "supertux/gameconfig.hpp"
23#include "supertux/globals.hpp"
24#include "util/file_system.hpp"
25#include "util/log.hpp"
26#include "util/reader.hpp"
27#include "util/reader_document.hpp"
28#include "util/reader_mapping.hpp"
29#include "util/writer.hpp"
30
31std::unique_ptr<World>
32World::from_directory(const std::string& directory)
33{
34 std::unique_ptr<World> world(new World(directory));
35
36 std::string info_filename = FileSystem::join(directory, "info");
37
38 try
39 {
40 register_translation_directory(info_filename);
41 auto doc = ReaderDocument::from_file(info_filename);
42 auto root = doc.get_root();
43
44 if (root.get_name() != "supertux-world" &&
45 root.get_name() != "supertux-level-subset")
46 {
47 throw std::runtime_error("File is not a world or levelsubset file");
48 }
49
50 auto info = root.get_mapping();
51
52 info.get("title", world->m_title);
53 info.get("description", world->m_description);
54 info.get("levelset", world->m_is_levelset, true);
55 info.get("hide-from-contribs", world->m_hide_from_contribs, false);
56
57 return world;
58 }
59 catch (const std::exception& err)
60 {
61 log_warning << "Failed to load " << info_filename << ":" << err.what() << std::endl;
62
63 world->m_hide_from_contribs = true;
64
65 return world;
66 }
67}
68
69std::unique_ptr<World>
70World::create(const std::string& title, const std::string& desc)
71{
72 // Limit the charset to numbers and alphabet.
73 std::string base = title;
74
75 for (size_t i = 0; i < base.length(); i++) {
76 if (!isalnum(base[i])) {
77 base[i] = '_';
78 }
79 }
80
81 base = FileSystem::join("levels", base);
82
83 // Find a non-existing fitting directory name
84 std::string dirname = base;
85 if (PHYSFS_exists(dirname.c_str())) {
86 int num = 1;
87 do {
88 num++;
89 dirname = base + std::to_string(num);
90 } while (PHYSFS_exists(dirname.c_str()));
91 }
92
93 std::unique_ptr<World> world(new World(dirname));
94
95 world->m_title = title;
96 world->m_description = desc;
97
98 return world;
99}
100
101World::World(const std::string& directory) :
102 m_title(),
103 m_description(),
104 m_is_levelset(true),
105 m_basedir(directory),
106 m_hide_from_contribs(false)
107{
108}
109
110void
111World::save(bool retry)
112{
113 std::string filepath = FileSystem::join(m_basedir, "/info");
114
115 try
116 {
117 { // make sure the levelset directory exists
118 std::string dirname = FileSystem::dirname(filepath);
119 if (!PHYSFS_exists(dirname.c_str()))
120 {
121 if (!PHYSFS_mkdir(dirname.c_str()))
122 {
123 std::ostringstream msg;
124 msg << "Couldn't create directory for levelset '"
125 << dirname << "': " <<PHYSFS_getLastErrorCode();
126 throw std::runtime_error(msg.str());
127 }
128 }
129
130 if (!physfsutil::is_directory(dirname))
131 {
132 std::ostringstream msg;
133 msg << "Levelset path '" << dirname << "' is not a directory";
134 throw std::runtime_error(msg.str());
135 }
136 }
137
138 Writer writer(filepath);
139 writer.start_list("supertux-level-subset");
140
141 writer.write("title", m_title, true);
142 writer.write("description", m_description, true);
143 writer.write("levelset", m_is_levelset);
144 writer.write("hide-from-contribs", m_hide_from_contribs);
145
146 writer.end_list("supertux-level-subset");
147 log_warning << "Levelset info saved as " << filepath << "." << std::endl;
148 }
149 catch(std::exception& e)
150 {
151 if (retry) {
152 std::stringstream msg;
153 msg << "Problem when saving levelset info '" << filepath << "': " << e.what();
154 throw std::runtime_error(msg.str());
155 } else {
156 log_warning << "Failed to save the levelset info, retrying..." << std::endl;
157 { // create the levelset directory again
158 std::string dirname = FileSystem::dirname(filepath);
159 if (!PHYSFS_mkdir(dirname.c_str()))
160 {
161 std::ostringstream msg;
162 msg << "Couldn't create directory for levelset '"
163 << dirname << "': " <<PHYSFS_getLastErrorCode();
164 throw std::runtime_error(msg.str());
165 }
166 }
167 save(true);
168 }
169 }
170}
171
172std::string
173World::get_worldmap_filename() const
174{
175 return FileSystem::join(m_basedir, "worldmap.stwm");
176}
177
178std::string
179World::get_savegame_filename() const
180{
181 const std::string worlddirname = FileSystem::basename(m_basedir);
182 std::ostringstream stream;
183 stream << "profile" << g_config->profile << "/" << worlddirname << ".stsg";
184 return stream.str();
185}
186
187/* EOF */
188