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 std::vector<std::string> get_table_keys(HSQUIRRELVM vm)
49 std::vector<std::string> worlds;
52 while(SQ_SUCCEEDED(sq_next(vm, -2)))
54 //here -1 is the value and -2 is the key
56 if(SQ_FAILED(sq_getstring(vm, -2, &result)))
58 std::ostringstream msg;
59 msg << "Couldn't get string value for key";
60 throw scripting::SquirrelError(vm, msg.str());
64 worlds.push_back(result);
67 // pops key and val before the next iteration
74 std::vector<LevelState> get_level_states(HSQUIRRELVM vm)
76 std::vector<LevelState> results;
79 while(SQ_SUCCEEDED(sq_next(vm, -2)))
81 //here -1 is the value and -2 is the key
83 if(SQ_FAILED(sq_getstring(vm, -2, &result)))
85 std::ostringstream msg;
86 msg << "Couldn't get string value";
87 throw scripting::SquirrelError(vm, msg.str());
91 LevelState level_state;
92 level_state.filename = result;
93 scripting::get_bool(vm, "solved", level_state.solved);
94 scripting::get_bool(vm, "perfect", level_state.perfect);
96 results.push_back(level_state);
99 // pops key and val before the next iteration
109 LevelsetState::get_level_state(const std::string& filename)
111 auto it = std::find_if(level_states.begin(), level_states.end(),
112 [filename](const LevelState& state)
114 return state.filename == filename;
116 if (it != level_states.end())
122 log_warning << "failed to retrieve level state for " << filename << std::endl;
124 state.filename = filename;
129 Savegame::Savegame(const std::string& filename) :
130 m_filename(filename),
131 m_player_status(new PlayerStatus)
135 Savegame::~Savegame()
142 if (m_filename.empty())
144 log_debug << "no filename set for savegame, skipping load" << std::endl;
150 if(!PHYSFS_exists(m_filename.c_str()))
152 log_info << m_filename << ": doesn't exist, not loading state" << std::endl;
156 log_debug << "loading savegame from " << m_filename << std::endl;
160 HSQUIRRELVM vm = scripting::global_vm;
163 const lisp::Lisp* root = parser.parse(m_filename);
165 const lisp::Lisp* lisp = root->get_lisp("supertux-savegame");
168 throw std::runtime_error("file is not a supertux-savegame file");
173 lisp->get("version", version);
176 throw std::runtime_error("incompatible savegame version");
180 const lisp::Lisp* tux = lisp->get_lisp("tux");
183 throw std::runtime_error("No tux section in savegame");
186 m_player_status->read(*tux);
189 const lisp::Lisp* state = lisp->get_lisp("state");
192 throw std::runtime_error("No state section in savegame");
196 sq_pushroottable(vm);
197 get_table_entry(vm, "state");
198 scripting::load_squirrel_table(vm, -1, *state);
204 catch(const std::exception& e)
206 log_fatal << "Couldn't load savegame: " << e.what() << std::endl;
212 Savegame::clear_state_table()
214 HSQUIRRELVM vm = scripting::global_vm;
216 // delete existing state table, if it exists
217 sq_pushroottable(vm);
219 /*sq_pushstring(vm, "state", -1);
220 if(SQ_FAILED(sq_deleteslot(vm, -2, SQFalse)))
225 // create a new empty state table
226 sq_pushstring(vm, "state", -1);
228 if(SQ_FAILED(sq_newslot(vm, -3, SQFalse)))
230 throw std::runtime_error("Couldn't create state table");
239 if (m_filename.empty())
241 log_debug << "no filename set for savegame, skipping save" << std::endl;
245 log_debug << "saving savegame to " << m_filename << std::endl;
247 { // make sure the savegame directory exists
248 std::string dirname = FileSystem::dirname(m_filename);
249 if(!PHYSFS_exists(dirname.c_str()))
251 if(!PHYSFS_mkdir(dirname.c_str()))
253 std::ostringstream msg;
254 msg << "Couldn't create directory for savegames '"
255 << dirname << "': " <<PHYSFS_getLastError();
256 throw std::runtime_error(msg.str());
260 if(!PHYSFS_isDirectory(dirname.c_str()))
262 std::ostringstream msg;
263 msg << "Savegame path '" << dirname << "' is not a directory";
264 throw std::runtime_error(msg.str());
268 HSQUIRRELVM vm = scripting::global_vm;
270 lisp::Writer writer(m_filename);
272 writer.start_list("supertux-savegame");
273 writer.write("version", 1);
275 using namespace worldmap;
276 if(WorldMap::current() != NULL)
278 std::ostringstream title;
279 title << WorldMap::current()->get_title();
280 title << " (" << WorldMap::current()->solved_level_count()
281 << "/" << WorldMap::current()->level_count() << ")";
282 writer.write("title", title.str());
285 writer.start_list("tux");
286 m_player_status->write(writer);
287 writer.end_list("tux");
289 writer.start_list("state");
291 sq_pushroottable(vm);
292 sq_pushstring(vm, "state", -1);
293 if(SQ_SUCCEEDED(sq_get(vm, -2)))
295 scripting::save_squirrel_table(vm, -1, writer);
299 writer.end_list("state");
301 writer.end_list("supertux-savegame");
304 std::vector<std::string>
305 Savegame::get_worldmaps()
307 std::vector<std::string> worlds;
309 HSQUIRRELVM vm = scripting::global_vm;
310 int oldtop = sq_gettop(vm);
314 sq_pushroottable(vm);
315 get_table_entry(vm, "state");
316 get_table_entry(vm, "worlds");
317 worlds = get_table_keys(vm);
319 catch(const std::exception& err)
321 log_warning << err.what() << std::endl;
324 sq_settop(vm, oldtop);
330 Savegame::get_worldmap_state(const std::string& name)
332 WorldmapState result;
334 HSQUIRRELVM vm = scripting::global_vm;
335 int oldtop = sq_gettop(vm);
339 sq_pushroottable(vm);
340 get_table_entry(vm, "state");
341 get_table_entry(vm, "worlds");
342 get_table_entry(vm, name);
343 get_table_entry(vm, "levels");
345 result.level_states = get_level_states(vm);
347 catch(const std::exception& err)
349 log_warning << err.what() << std::endl;
352 sq_settop(vm, oldtop);
357 std::vector<std::string>
358 Savegame::get_levelsets()
360 std::vector<std::string> results;
362 HSQUIRRELVM vm = scripting::global_vm;
363 int oldtop = sq_gettop(vm);
367 sq_pushroottable(vm);
368 get_table_entry(vm, "state");
369 get_table_entry(vm, "levelsets");
370 results = get_table_keys(vm);
372 catch(const std::exception& err)
374 log_warning << err.what() << std::endl;
377 sq_settop(vm, oldtop);
383 Savegame::get_levelset_state(const std::string& name)
385 LevelsetState result;
387 HSQUIRRELVM vm = scripting::global_vm;
388 int oldtop = sq_gettop(vm);
392 sq_pushroottable(vm);
393 get_table_entry(vm, "state");
394 get_table_entry(vm, "levelsets");
395 get_table_entry(vm, name);
396 get_table_entry(vm, "levels");
398 result.level_states = get_level_states(vm);
400 catch(const std::exception& err)
402 log_warning << err.what() << std::endl;
405 sq_settop(vm, oldtop);