2 // Copyright (C) 2006 Matthias Braun <matze@braunis.de>
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.
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.
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/>.
19 #include "lisp/parser.hpp"
20 #include "lisp/writer.hpp"
21 #include "physfs/ifile_streambuf.hpp"
22 #include "scripting/serialize.hpp"
23 #include "scripting/squirrel_util.hpp"
24 #include "supertux/gameconfig.hpp"
25 #include "supertux/globals.hpp"
26 #include "supertux/player_status.hpp"
27 #include "supertux/screen_fade.hpp"
28 #include "supertux/screen_manager.hpp"
29 #include "supertux/world.hpp"
30 #include "util/file_system.hpp"
31 #include "util/reader.hpp"
32 #include "util/string_util.hpp"
33 #include "worldmap/worldmap.hpp"
35 std::unique_ptr<World>
36 World::load(const std::string& directory)
38 std::unique_ptr<World> world(new World);
40 world->load_(directory);
42 { // generate savegame filename
43 std::string worlddirname = FileSystem::basename(directory);
44 std::ostringstream stream;
45 stream << "profile" << g_config->profile << "/" << worlddirname << ".stsg";
46 std::string slotfile = stream.str();
47 world->m_savegame_filename = stream.str();
50 return std::move(world);
56 m_worldmap_filename(),
57 m_savegame_filename(),
61 m_player_status(new PlayerStatus),
62 m_hide_from_contribs(false),
65 sq_resetobject(&m_world_thread);
70 sq_release(scripting::global_vm, &m_world_thread);
74 World::load_(const std::string& directory)
76 m_basedir = directory;
77 m_worldmap_filename = m_basedir + "/worldmap.stwm";
80 const lisp::Lisp* root = parser.parse(m_basedir + "/info");
82 const lisp::Lisp* info = root->get_lisp("supertux-world");
84 info = root->get_lisp("supertux-level-subset");
86 throw std::runtime_error("File is not a world or levelsubset file");
88 m_hide_from_contribs = false;
91 info->get("title", m_title);
92 info->get("description", m_description);
93 info->get("levelset", m_is_levelset);
94 info->get("hide-from-contribs", m_hide_from_contribs);
96 // Level info file doesn't define any levels, so read the
97 // directory to see what we can find
99 char** files = PHYSFS_enumerateFiles(m_basedir.c_str());
102 log_warning << "Couldn't read subset dir '" << m_basedir << "'" << std::endl;
106 for(const char* const* filename = files; *filename != 0; ++filename)
108 if(StringUtil::has_suffix(*filename, ".stl"))
110 m_levels.push_back(*filename);
113 PHYSFS_freeList(files);
115 std::sort(m_levels.begin(), m_levels.end(), StringUtil::numeric_less);
121 // create new squirrel table for persistent game state
122 HSQUIRRELVM vm = scripting::global_vm;
124 sq_pushroottable(vm);
125 sq_pushstring(vm, "state", -1);
127 if(SQ_FAILED(sq_createslot(vm, -3)))
129 throw scripting::SquirrelError(vm, "Couldn't create state table");
137 std::string filename = m_basedir + "/world.nut";
140 IFileStreambuf ins(filename);
141 std::istream in(&ins);
143 sq_release(scripting::global_vm, &m_world_thread);
144 m_world_thread = scripting::create_thread(scripting::global_vm);
145 scripting::compile_and_run(scripting::object_to_vm(m_world_thread), in, filename);
147 catch(const std::exception& )
149 // fallback: try to load worldmap worldmap.stwm
150 g_screen_manager->push_screen(std::unique_ptr<Screen>(
151 new worldmap::WorldMap(m_worldmap_filename,
152 get_player_status())));
160 { // make sure the savegame directory exists
161 std::string dirname = FileSystem::dirname(m_savegame_filename);
162 if(!PHYSFS_exists(dirname.c_str()))
164 if(!PHYSFS_mkdir(dirname.c_str()))
166 std::ostringstream msg;
167 msg << "Couldn't create directory for savegames '"
168 << dirname << "': " <<PHYSFS_getLastError();
169 throw std::runtime_error(msg.str());
173 if(!PHYSFS_isDirectory(dirname.c_str()))
175 std::ostringstream msg;
176 msg << "Savegame path '" << dirname << "' is not a directory";
177 throw std::runtime_error(msg.str());
181 HSQUIRRELVM vm = scripting::global_vm;
183 lisp::Writer writer(m_savegame_filename);
185 writer.start_list("supertux-savegame");
186 writer.write("version", 1);
188 using namespace worldmap;
189 if(WorldMap::current() != NULL)
191 std::ostringstream title;
192 title << WorldMap::current()->get_title();
193 title << " (" << WorldMap::current()->solved_level_count()
194 << "/" << WorldMap::current()->level_count() << ")";
195 writer.write("title", title.str());
198 writer.start_list("tux");
199 m_player_status->write(writer);
200 writer.end_list("tux");
202 writer.start_list("state");
204 sq_pushroottable(vm);
205 sq_pushstring(vm, "state", -1);
206 if(SQ_SUCCEEDED(sq_get(vm, -2))) {
207 scripting::save_squirrel_table(vm, -1, writer);
211 writer.end_list("state");
213 writer.end_list("supertux-savegame");
219 if(!PHYSFS_exists(m_savegame_filename.c_str()))
221 log_info << m_savegame_filename << ": doesn't exist, not loading state" << std::endl;
227 HSQUIRRELVM vm = scripting::global_vm;
230 const lisp::Lisp* root = parser.parse(m_savegame_filename);
232 const lisp::Lisp* lisp = root->get_lisp("supertux-savegame");
234 throw std::runtime_error("file is not a supertux-savegame file");
237 lisp->get("version", version);
239 throw std::runtime_error("incompatible savegame version");
241 const lisp::Lisp* tux = lisp->get_lisp("tux");
243 throw std::runtime_error("No tux section in savegame");
244 m_player_status->read(*tux);
246 const lisp::Lisp* state = lisp->get_lisp("state");
248 throw std::runtime_error("No state section in savegame");
250 sq_pushroottable(vm);
251 sq_pushstring(vm, "state", -1);
252 if(SQ_FAILED(sq_deleteslot(vm, -2, SQFalse)))
255 sq_pushstring(vm, "state", -1);
257 scripting::load_squirrel_table(vm, -1, *state);
258 if(SQ_FAILED(sq_createslot(vm, -3)))
259 throw std::runtime_error("Couldn't create state table");
262 catch(const std::exception& e)
264 log_fatal << "Couldn't load savegame: " << e.what() << std::endl;
270 World::get_level_filename(unsigned int i) const
272 return FileSystem::join(m_basedir, m_levels[i]);
276 World::get_num_levels() const
278 return m_levels.size();
282 World::get_num_solved_levels() const
284 int num_solved_levels = 0;
286 HSQUIRRELVM vm = scripting::global_vm;
287 int oldtop = sq_gettop(vm);
289 sq_pushroottable(vm);
290 sq_pushstring(vm, "state", -1);
291 if(SQ_FAILED(sq_get(vm, -2)))
293 log_warning << "failed to get 'state' table" << std::endl;
297 sq_pushstring(vm, "worlds", -1);
298 if(SQ_FAILED(sq_get(vm, -2)))
300 log_warning << "failed to get 'state.worlds' table" << std::endl;
304 sq_pushstring(vm, m_worldmap_filename.c_str(), -1);
305 if(SQ_FAILED(sq_get(vm, -2)))
307 log_warning << "failed to get state.worlds['" << m_worldmap_filename << "']" << std::endl;
311 sq_pushstring(vm, "levels", -1);
312 if(SQ_FAILED(sq_get(vm, -2)))
314 log_warning << "failed to get state.worlds['" << m_worldmap_filename << "'].levels" << std::endl;
318 for(auto level : m_levels)
320 sq_pushstring(vm, level.c_str(), -1);
321 if(SQ_FAILED(sq_get(vm, -2)))
323 log_warning << "failed to get state.worlds['" << m_worldmap_filename << "'].levels['"
324 << level << "']" << std::endl;
328 bool solved = scripting::read_bool(vm, "solved");
331 num_solved_levels += 1;
341 sq_settop(vm, oldtop);
343 return num_solved_levels;
347 World::get_basedir() const
353 World::get_title() const