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 | |
31 | std::unique_ptr<World> |
32 | World::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 | |
69 | std::unique_ptr<World> |
70 | World::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 | |
101 | World::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 | |
110 | void |
111 | World::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 | |
172 | std::string |
173 | World::get_worldmap_filename() const |
174 | { |
175 | return FileSystem::join(m_basedir, "worldmap.stwm" ); |
176 | } |
177 | |
178 | std::string |
179 | World::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 | |