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"
20 #include "lisp/lisp.hpp"
21 #include "lisp/parser.hpp"
22 #include "lisp/writer.hpp"
23 #include "physfs/ifile_streambuf.hpp"
24 #include "scripting/serialize.hpp"
25 #include "scripting/squirrel_util.hpp"
26 #include "scripting/squirrel_util.hpp"
27 #include "supertux/player_status.hpp"
28 #include "util/file_system.hpp"
29 #include "util/log.hpp"
30 #include "worldmap/worldmap.hpp"
34 void get_table_entry(HSQUIRRELVM vm, const std::string& name)
36 sq_pushstring(vm, name.c_str(), -1);
37 if(SQ_FAILED(sq_get(vm, -2)))
39 throw std::runtime_error("failed to get '" + name + "' table entry");
43 // successfully placed result on stack
47 void get_or_create_table_entry(HSQUIRRELVM vm, const std::string& name)
49 sq_pushstring(vm, name.c_str(), -1);
50 if(SQ_FAILED(sq_get(vm, -2)))
52 sq_pushstring(vm, name.c_str(), -1);
54 if(SQ_FAILED(sq_newslot(vm, -3, SQFalse)))
56 throw std::runtime_error("failed to create '" + name + "' table entry");
60 get_table_entry(vm, name);
65 // successfully placed result on stack
69 std::vector<std::string> get_table_keys(HSQUIRRELVM vm)
71 std::vector<std::string> worlds;
74 while(SQ_SUCCEEDED(sq_next(vm, -2)))
76 //here -1 is the value and -2 is the key
78 if(SQ_FAILED(sq_getstring(vm, -2, &result)))
80 std::ostringstream msg;
81 msg << "Couldn't get string value for key";
82 throw scripting::SquirrelError(vm, msg.str());
86 worlds.push_back(result);
89 // pops key and val before the next iteration
96 std::vector<LevelState> get_level_states(HSQUIRRELVM vm)
98 std::vector<LevelState> results;
101 while(SQ_SUCCEEDED(sq_next(vm, -2)))
103 //here -1 is the value and -2 is the key
105 if(SQ_FAILED(sq_getstring(vm, -2, &result)))
107 std::ostringstream msg;
108 msg << "Couldn't get string value";
109 throw scripting::SquirrelError(vm, msg.str());
113 LevelState level_state;
114 level_state.filename = result;
115 scripting::get_bool(vm, "solved", level_state.solved);
116 scripting::get_bool(vm, "perfect", level_state.perfect);
118 results.push_back(level_state);
121 // pops key and val before the next iteration
131 LevelsetState::store_level_state(const LevelState& in_state)
133 auto it = std::find_if(level_states.begin(), level_states.end(),
134 [&in_state](const LevelState& state)
136 return state.filename == in_state.filename;
138 if (it != level_states.end())
144 level_states.push_back(in_state);
149 LevelsetState::get_level_state(const std::string& filename)
151 auto it = std::find_if(level_states.begin(), level_states.end(),
152 [filename](const LevelState& state)
154 return state.filename == filename;
156 if (it != level_states.end())
162 log_warning << "failed to retrieve level state for " << filename << std::endl;
164 state.filename = filename;
169 Savegame::Savegame(const std::string& filename) :
170 m_filename(filename),
171 m_player_status(new PlayerStatus)
175 Savegame::~Savegame()
182 if (m_filename.empty())
184 log_debug << "no filename set for savegame, skipping load" << std::endl;
190 if(!PHYSFS_exists(m_filename.c_str()))
192 log_info << m_filename << ": doesn't exist, not loading state" << std::endl;
196 log_debug << "loading savegame from " << m_filename << std::endl;
200 HSQUIRRELVM vm = scripting::global_vm;
203 const lisp::Lisp* root = parser.parse(m_filename);
205 const lisp::Lisp* lisp = root->get_lisp("supertux-savegame");
208 throw std::runtime_error("file is not a supertux-savegame file");
213 lisp->get("version", version);
216 throw std::runtime_error("incompatible savegame version");
220 const lisp::Lisp* tux = lisp->get_lisp("tux");
223 throw std::runtime_error("No tux section in savegame");
226 m_player_status->read(*tux);
229 const lisp::Lisp* state = lisp->get_lisp("state");
232 throw std::runtime_error("No state section in savegame");
236 sq_pushroottable(vm);
237 get_table_entry(vm, "state");
238 scripting::load_squirrel_table(vm, -1, *state);
244 catch(const std::exception& e)
246 log_fatal << "Couldn't load savegame: " << e.what() << std::endl;
252 Savegame::clear_state_table()
254 HSQUIRRELVM vm = scripting::global_vm;
256 // delete existing state table, if it exists
257 sq_pushroottable(vm);
259 /*sq_pushstring(vm, "state", -1);
260 if(SQ_FAILED(sq_deleteslot(vm, -2, SQFalse)))
265 // create a new empty state table
266 sq_pushstring(vm, "state", -1);
268 if(SQ_FAILED(sq_newslot(vm, -3, SQFalse)))
270 throw std::runtime_error("Couldn't create state table");
279 if (m_filename.empty())
281 log_debug << "no filename set for savegame, skipping save" << std::endl;
285 log_debug << "saving savegame to " << m_filename << std::endl;
287 { // make sure the savegame directory exists
288 std::string dirname = FileSystem::dirname(m_filename);
289 if(!PHYSFS_exists(dirname.c_str()))
291 if(!PHYSFS_mkdir(dirname.c_str()))
293 std::ostringstream msg;
294 msg << "Couldn't create directory for savegames '"
295 << dirname << "': " <<PHYSFS_getLastError();
296 throw std::runtime_error(msg.str());
300 if(!PHYSFS_isDirectory(dirname.c_str()))
302 std::ostringstream msg;
303 msg << "Savegame path '" << dirname << "' is not a directory";
304 throw std::runtime_error(msg.str());
308 HSQUIRRELVM vm = scripting::global_vm;
310 lisp::Writer writer(m_filename);
312 writer.start_list("supertux-savegame");
313 writer.write("version", 1);
315 using namespace worldmap;
316 if(WorldMap::current() != NULL)
318 std::ostringstream title;
319 title << WorldMap::current()->get_title();
320 title << " (" << WorldMap::current()->solved_level_count()
321 << "/" << WorldMap::current()->level_count() << ")";
322 writer.write("title", title.str());
325 writer.start_list("tux");
326 m_player_status->write(writer);
327 writer.end_list("tux");
329 writer.start_list("state");
331 sq_pushroottable(vm);
332 sq_pushstring(vm, "state", -1);
333 if(SQ_SUCCEEDED(sq_get(vm, -2)))
335 scripting::save_squirrel_table(vm, -1, writer);
339 writer.end_list("state");
341 writer.end_list("supertux-savegame");
344 std::vector<std::string>
345 Savegame::get_worldmaps()
347 std::vector<std::string> worlds;
349 HSQUIRRELVM vm = scripting::global_vm;
350 int oldtop = sq_gettop(vm);
354 sq_pushroottable(vm);
355 get_table_entry(vm, "state");
356 get_table_entry(vm, "worlds");
357 worlds = get_table_keys(vm);
359 catch(const std::exception& err)
361 log_warning << err.what() << std::endl;
364 sq_settop(vm, oldtop);
370 Savegame::get_worldmap_state(const std::string& name)
372 WorldmapState result;
374 HSQUIRRELVM vm = scripting::global_vm;
375 int oldtop = sq_gettop(vm);
379 sq_pushroottable(vm);
380 get_table_entry(vm, "state");
381 get_table_entry(vm, "worlds");
382 get_table_entry(vm, name);
383 get_table_entry(vm, "levels");
385 result.level_states = get_level_states(vm);
387 catch(const std::exception& err)
389 log_warning << err.what() << std::endl;
392 sq_settop(vm, oldtop);
397 std::vector<std::string>
398 Savegame::get_levelsets()
400 std::vector<std::string> results;
402 HSQUIRRELVM vm = scripting::global_vm;
403 int oldtop = sq_gettop(vm);
407 sq_pushroottable(vm);
408 get_table_entry(vm, "state");
409 get_table_entry(vm, "levelsets");
410 results = get_table_keys(vm);
412 catch(const std::exception& err)
414 log_warning << err.what() << std::endl;
417 sq_settop(vm, oldtop);
423 Savegame::get_levelset_state(const std::string& basedir)
425 LevelsetState result;
427 HSQUIRRELVM vm = scripting::global_vm;
428 int oldtop = sq_gettop(vm);
432 sq_pushroottable(vm);
433 get_table_entry(vm, "state");
434 get_table_entry(vm, "levelsets");
435 get_table_entry(vm, basedir);
436 get_table_entry(vm, "levels");
438 result.level_states = get_level_states(vm);
440 catch(const std::exception& err)
442 log_warning << err.what() << std::endl;
445 sq_settop(vm, oldtop);
451 Savegame::set_levelset_state(const std::string& basedir,
452 const std::string& level_filename,
455 LevelsetState state = get_levelset_state(basedir);
457 HSQUIRRELVM vm = scripting::global_vm;
458 int oldtop = sq_gettop(vm);
462 sq_pushroottable(vm);
463 get_table_entry(vm, "state");
464 get_or_create_table_entry(vm, "levelsets");
465 get_or_create_table_entry(vm, basedir);
466 get_or_create_table_entry(vm, "levels");
467 get_or_create_table_entry(vm, level_filename);
469 scripting::store_bool(vm, "solved", solved);
471 catch(const std::exception& err)
473 log_warning << err.what() << std::endl;
476 sq_settop(vm, oldtop);