2 // Copyright (C) 2006 Matthias Braun <matze@braunis.de>
3 // 2014 Ingo Ruhnke <grumbel@gmx.de>
5 // This program is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program. If not, see <http://www.gnu.org/licenses/>.
18 #include "supertux/savegame.hpp"
22 #include "lisp/lisp.hpp"
23 #include "lisp/parser.hpp"
24 #include "lisp/writer.hpp"
25 #include "physfs/ifile_streambuf.hpp"
26 #include "scripting/scripting.hpp"
27 #include "scripting/serialize.hpp"
28 #include "scripting/squirrel_util.hpp"
29 #include "supertux/player_status.hpp"
30 #include "util/file_system.hpp"
31 #include "util/log.hpp"
32 #include "worldmap/worldmap.hpp"
36 void get_table_entry(HSQUIRRELVM vm, const std::string& name)
38 sq_pushstring(vm, name.c_str(), -1);
39 if(SQ_FAILED(sq_get(vm, -2)))
41 throw std::runtime_error("failed to get '" + name + "' table entry");
45 // successfully placed result on stack
49 void get_or_create_table_entry(HSQUIRRELVM vm, const std::string& name)
51 sq_pushstring(vm, name.c_str(), -1);
52 if(SQ_FAILED(sq_get(vm, -2)))
54 sq_pushstring(vm, name.c_str(), -1);
56 if(SQ_FAILED(sq_createslot(vm, -3)))
58 throw std::runtime_error("failed to create '" + name + "' table entry");
62 get_table_entry(vm, name);
67 // successfully placed result on stack
71 std::vector<std::string> get_table_keys(HSQUIRRELVM vm)
73 std::vector<std::string> worlds;
76 while(SQ_SUCCEEDED(sq_next(vm, -2)))
78 //here -1 is the value and -2 is the key
80 if(SQ_FAILED(sq_getstring(vm, -2, &result)))
82 std::ostringstream msg;
83 msg << "Couldn't get string value for key";
84 throw scripting::SquirrelError(vm, msg.str());
88 worlds.push_back(result);
91 // pops key and val before the next iteration
98 std::vector<LevelState> get_level_states(HSQUIRRELVM vm)
100 std::vector<LevelState> results;
103 while(SQ_SUCCEEDED(sq_next(vm, -2)))
105 //here -1 is the value and -2 is the key
107 if(SQ_FAILED(sq_getstring(vm, -2, &result)))
109 std::ostringstream msg;
110 msg << "Couldn't get string value";
111 throw scripting::SquirrelError(vm, msg.str());
115 LevelState level_state;
116 level_state.filename = result;
117 scripting::get_bool(vm, "solved", level_state.solved);
118 scripting::get_bool(vm, "perfect", level_state.perfect);
120 results.push_back(level_state);
123 // pops key and val before the next iteration
133 LevelsetState::store_level_state(const LevelState& in_state)
135 auto it = std::find_if(level_states.begin(), level_states.end(),
136 [&in_state](const LevelState& state)
138 return state.filename == in_state.filename;
140 if (it != level_states.end())
146 level_states.push_back(in_state);
151 LevelsetState::get_level_state(const std::string& filename)
153 auto it = std::find_if(level_states.begin(), level_states.end(),
154 [filename](const LevelState& state)
156 return state.filename == filename;
158 if (it != level_states.end())
164 log_warning << "failed to retrieve level state for " << filename << std::endl;
166 state.filename = filename;
171 Savegame::Savegame(const std::string& filename) :
172 m_filename(filename),
173 m_player_status(new PlayerStatus)
177 Savegame::~Savegame()
184 if (m_filename.empty())
186 log_debug << "no filename set for savegame, skipping load" << std::endl;
192 if(!PHYSFS_exists(m_filename.c_str()))
194 log_info << m_filename << ": doesn't exist, not loading state" << std::endl;
198 log_debug << "loading savegame from " << m_filename << std::endl;
202 HSQUIRRELVM vm = scripting::global_vm;
205 const lisp::Lisp* root = parser.parse(m_filename);
207 const lisp::Lisp* lisp = root->get_lisp("supertux-savegame");
210 throw std::runtime_error("file is not a supertux-savegame file");
215 lisp->get("version", version);
218 throw std::runtime_error("incompatible savegame version");
222 const lisp::Lisp* tux = lisp->get_lisp("tux");
225 throw std::runtime_error("No tux section in savegame");
228 m_player_status->read(*tux);
231 const lisp::Lisp* state = lisp->get_lisp("state");
234 throw std::runtime_error("No state section in savegame");
238 sq_pushroottable(vm);
239 get_table_entry(vm, "state");
240 scripting::load_squirrel_table(vm, -1, *state);
246 catch(const std::exception& e)
248 log_fatal << "Couldn't load savegame: " << e.what() << std::endl;
254 Savegame::clear_state_table()
256 HSQUIRRELVM vm = scripting::global_vm;
258 // delete existing state table, if it exists
259 sq_pushroottable(vm);
261 // create a new empty state table
262 sq_pushstring(vm, "state", -1);
264 if(SQ_FAILED(sq_createslot(vm, -3)))
266 throw std::runtime_error("Couldn't create state table");
275 if (m_filename.empty())
277 log_debug << "no filename set for savegame, skipping save" << std::endl;
281 log_debug << "saving savegame to " << m_filename << std::endl;
283 { // make sure the savegame directory exists
284 std::string dirname = FileSystem::dirname(m_filename);
285 if(!PHYSFS_exists(dirname.c_str()))
287 if(!PHYSFS_mkdir(dirname.c_str()))
289 std::ostringstream msg;
290 msg << "Couldn't create directory for savegames '"
291 << dirname << "': " <<PHYSFS_getLastError();
292 throw std::runtime_error(msg.str());
296 if(!PHYSFS_isDirectory(dirname.c_str()))
298 std::ostringstream msg;
299 msg << "Savegame path '" << dirname << "' is not a directory";
300 throw std::runtime_error(msg.str());
304 HSQUIRRELVM vm = scripting::global_vm;
306 lisp::Writer writer(m_filename);
308 writer.start_list("supertux-savegame");
309 writer.write("version", 1);
311 using namespace worldmap;
312 if(WorldMap::current() != NULL)
314 std::ostringstream title;
315 title << WorldMap::current()->get_title();
316 title << " (" << WorldMap::current()->solved_level_count()
317 << "/" << WorldMap::current()->level_count() << ")";
318 writer.write("title", title.str());
321 writer.start_list("tux");
322 m_player_status->write(writer);
323 writer.end_list("tux");
325 writer.start_list("state");
327 sq_pushroottable(vm);
328 sq_pushstring(vm, "state", -1);
329 if(SQ_SUCCEEDED(sq_get(vm, -2)))
331 scripting::save_squirrel_table(vm, -1, writer);
335 writer.end_list("state");
337 writer.end_list("supertux-savegame");
340 std::vector<std::string>
341 Savegame::get_worldmaps()
343 std::vector<std::string> worlds;
345 HSQUIRRELVM vm = scripting::global_vm;
346 int oldtop = sq_gettop(vm);
350 sq_pushroottable(vm);
351 get_table_entry(vm, "state");
352 get_table_entry(vm, "worlds");
353 worlds = get_table_keys(vm);
355 catch(const std::exception& err)
357 log_warning << err.what() << std::endl;
360 sq_settop(vm, oldtop);
366 Savegame::get_worldmap_state(const std::string& name)
368 WorldmapState result;
370 HSQUIRRELVM vm = scripting::global_vm;
371 int oldtop = sq_gettop(vm);
375 sq_pushroottable(vm);
376 get_table_entry(vm, "state");
377 get_table_entry(vm, "worlds");
378 get_table_entry(vm, name);
379 get_table_entry(vm, "levels");
381 result.level_states = get_level_states(vm);
383 catch(const std::exception& err)
385 log_warning << err.what() << std::endl;
388 sq_settop(vm, oldtop);
393 std::vector<std::string>
394 Savegame::get_levelsets()
396 std::vector<std::string> results;
398 HSQUIRRELVM vm = scripting::global_vm;
399 int oldtop = sq_gettop(vm);
403 sq_pushroottable(vm);
404 get_table_entry(vm, "state");
405 get_table_entry(vm, "levelsets");
406 results = get_table_keys(vm);
408 catch(const std::exception& err)
410 log_warning << err.what() << std::endl;
413 sq_settop(vm, oldtop);
419 Savegame::get_levelset_state(const std::string& basedir)
421 LevelsetState result;
423 HSQUIRRELVM vm = scripting::global_vm;
424 int oldtop = sq_gettop(vm);
428 sq_pushroottable(vm);
429 get_table_entry(vm, "state");
430 get_table_entry(vm, "levelsets");
431 get_table_entry(vm, basedir);
432 get_table_entry(vm, "levels");
434 result.level_states = get_level_states(vm);
436 catch(const std::exception& err)
438 log_warning << err.what() << std::endl;
441 sq_settop(vm, oldtop);
447 Savegame::set_levelset_state(const std::string& basedir,
448 const std::string& level_filename,
451 LevelsetState state = get_levelset_state(basedir);
453 HSQUIRRELVM vm = scripting::global_vm;
454 int oldtop = sq_gettop(vm);
458 sq_pushroottable(vm);
459 get_table_entry(vm, "state");
460 get_or_create_table_entry(vm, "levelsets");
461 get_or_create_table_entry(vm, basedir);
462 get_or_create_table_entry(vm, "levels");
463 get_or_create_table_entry(vm, level_filename);
465 bool old_solved = false;
466 scripting::get_bool(vm, "solved", old_solved);
467 scripting::store_bool(vm, "solved", solved || old_solved);
469 catch(const std::exception& err)
471 log_warning << err.what() << std::endl;
474 sq_settop(vm, oldtop);