Replace sq_newslot with sq_createslot where appropriate (shorter and does the same...
[supertux.git] / src / supertux / savegame.cpp
1 //  SuperTux
2 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
3 //                2014 Ingo Ruhnke <grumbel@gmx.de>
4 //
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.
9 //
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.
14 //
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/>.
17
18 #include "supertux/savegame.hpp"
19
20 #include <algorithm>
21
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"
33
34 namespace {
35
36 void get_table_entry(HSQUIRRELVM vm, const std::string& name)
37 {
38   sq_pushstring(vm, name.c_str(), -1);
39   if(SQ_FAILED(sq_get(vm, -2)))
40   {
41     throw std::runtime_error("failed to get '" + name + "' table entry");
42   }
43   else
44   {
45     // successfully placed result on stack
46   }
47 }
48
49 void get_or_create_table_entry(HSQUIRRELVM vm, const std::string& name)
50 {
51   sq_pushstring(vm, name.c_str(), -1);
52   if(SQ_FAILED(sq_get(vm, -2)))
53   {
54     sq_pushstring(vm, name.c_str(), -1);
55     sq_newtable(vm);
56     if(SQ_FAILED(sq_createslot(vm, -3)))
57     {
58       throw std::runtime_error("failed to create '" + name + "' table entry");
59     }
60     else
61     {
62       get_table_entry(vm, name);
63     }
64   }
65   else
66   {
67     // successfully placed result on stack
68   }
69 }
70
71 std::vector<std::string> get_table_keys(HSQUIRRELVM vm)
72 {
73   std::vector<std::string> worlds;
74
75   sq_pushnull(vm);
76   while(SQ_SUCCEEDED(sq_next(vm, -2)))
77   {
78     //here -1 is the value and -2 is the key
79     const char* result;
80     if(SQ_FAILED(sq_getstring(vm, -2, &result)))
81     {
82       std::ostringstream msg;
83       msg << "Couldn't get string value for key";
84       throw scripting::SquirrelError(vm, msg.str());
85     }
86     else
87     {
88       worlds.push_back(result);
89     }
90
91     // pops key and val before the next iteration
92     sq_pop(vm, 2);
93   }
94
95   return worlds;
96 }
97
98 std::vector<LevelState> get_level_states(HSQUIRRELVM vm)
99 {
100   std::vector<LevelState> results;
101
102   sq_pushnull(vm);
103   while(SQ_SUCCEEDED(sq_next(vm, -2)))
104   {
105     //here -1 is the value and -2 is the key
106     const char* result;
107     if(SQ_FAILED(sq_getstring(vm, -2, &result)))
108     {
109       std::ostringstream msg;
110       msg << "Couldn't get string value";
111       throw scripting::SquirrelError(vm, msg.str());
112     }
113     else
114     {
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);
119
120       results.push_back(level_state);
121     }
122
123     // pops key and val before the next iteration
124     sq_pop(vm, 2);
125   }
126
127   return results;
128 }
129
130 } // namespace
131
132 void
133 LevelsetState::store_level_state(const LevelState& in_state)
134 {
135   auto it = std::find_if(level_states.begin(), level_states.end(),
136                          [&in_state](const LevelState& state)
137                          {
138                            return state.filename == in_state.filename;
139                          });
140   if (it != level_states.end())
141   {
142     *it = in_state;
143   }
144   else
145   {
146     level_states.push_back(in_state);
147   }
148 }
149
150 LevelState
151 LevelsetState::get_level_state(const std::string& filename)
152 {
153   auto it = std::find_if(level_states.begin(), level_states.end(),
154                          [filename](const LevelState& state)
155                          {
156                            return state.filename == filename;
157                          });
158   if (it != level_states.end())
159   {
160     return *it;
161   }
162   else
163   {
164     log_warning << "failed to retrieve level state for " << filename << std::endl;
165     LevelState state;
166     state.filename = filename;
167     return state;
168   }
169 }
170
171 Savegame::Savegame(const std::string& filename) :
172   m_filename(filename),
173   m_player_status(new PlayerStatus)
174 {
175 }
176
177 Savegame::~Savegame()
178 {
179 }
180
181 void
182 Savegame::load()
183 {
184   if (m_filename.empty())
185   {
186     log_debug << "no filename set for savegame, skipping load" << std::endl;
187     return;
188   }
189
190   clear_state_table();
191
192   if(!PHYSFS_exists(m_filename.c_str()))
193   {
194     log_info << m_filename << ": doesn't exist, not loading state" << std::endl;
195   }
196   else
197   {
198     log_debug << "loading savegame from " << m_filename << std::endl;
199
200     try
201     {
202       HSQUIRRELVM vm = scripting::global_vm;
203
204       lisp::Parser parser;
205       const lisp::Lisp* root = parser.parse(m_filename);
206
207       const lisp::Lisp* lisp = root->get_lisp("supertux-savegame");
208       if(lisp == NULL)
209       {
210         throw std::runtime_error("file is not a supertux-savegame file");
211       }
212       else
213       {
214         int version = 1;
215         lisp->get("version", version);
216         if(version != 1)
217         {
218           throw std::runtime_error("incompatible savegame version");
219         }
220         else
221         {
222           const lisp::Lisp* tux = lisp->get_lisp("tux");
223           if(tux == NULL)
224           {
225             throw std::runtime_error("No tux section in savegame");
226           }
227           {
228             m_player_status->read(*tux);
229           }
230
231           const lisp::Lisp* state = lisp->get_lisp("state");
232           if(state == NULL)
233           {
234             throw std::runtime_error("No state section in savegame");
235           }
236           else
237           {
238             sq_pushroottable(vm);
239             get_table_entry(vm, "state");
240             scripting::load_squirrel_table(vm, -1, *state);
241             sq_pop(vm, 2);
242           }
243         }
244       }
245     }
246     catch(const std::exception& e)
247     {
248       log_fatal << "Couldn't load savegame: " << e.what() << std::endl;
249     }
250   }
251 }
252
253 void
254 Savegame::clear_state_table()
255 {
256   HSQUIRRELVM vm = scripting::global_vm;
257
258   // delete existing state table, if it exists
259   sq_pushroottable(vm);
260   {
261     // create a new empty state table
262     sq_pushstring(vm, "state", -1);
263     sq_newtable(vm);
264     if(SQ_FAILED(sq_createslot(vm, -3)))
265     {
266       throw std::runtime_error("Couldn't create state table");
267     }
268   }
269   sq_pop(vm, 1);
270 }
271
272 void
273 Savegame::save()
274 {
275   if (m_filename.empty())
276   {
277     log_debug << "no filename set for savegame, skipping save" << std::endl;
278     return;
279   }
280
281   log_debug << "saving savegame to " << m_filename << std::endl;
282
283   { // make sure the savegame directory exists
284     std::string dirname = FileSystem::dirname(m_filename);
285     if(!PHYSFS_exists(dirname.c_str()))
286     {
287       if(!PHYSFS_mkdir(dirname.c_str()))
288       {
289         std::ostringstream msg;
290         msg << "Couldn't create directory for savegames '"
291             << dirname << "': " <<PHYSFS_getLastError();
292         throw std::runtime_error(msg.str());
293       }
294     }
295
296     if(!PHYSFS_isDirectory(dirname.c_str()))
297     {
298       std::ostringstream msg;
299       msg << "Savegame path '" << dirname << "' is not a directory";
300       throw std::runtime_error(msg.str());
301     }
302   }
303
304   HSQUIRRELVM vm = scripting::global_vm;
305
306   lisp::Writer writer(m_filename);
307
308   writer.start_list("supertux-savegame");
309   writer.write("version", 1);
310
311   using namespace worldmap;
312   if(WorldMap::current() != NULL)
313   {
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());
319   }
320
321   writer.start_list("tux");
322   m_player_status->write(writer);
323   writer.end_list("tux");
324
325   writer.start_list("state");
326
327   sq_pushroottable(vm);
328   sq_pushstring(vm, "state", -1);
329   if(SQ_SUCCEEDED(sq_get(vm, -2)))
330   {
331     scripting::save_squirrel_table(vm, -1, writer);
332     sq_pop(vm, 1);
333   }
334   sq_pop(vm, 1);
335   writer.end_list("state");
336
337   writer.end_list("supertux-savegame");
338 }
339
340 std::vector<std::string>
341 Savegame::get_worldmaps()
342 {
343   std::vector<std::string> worlds;
344
345   HSQUIRRELVM vm = scripting::global_vm;
346   int oldtop = sq_gettop(vm);
347
348   try
349   {
350     sq_pushroottable(vm);
351     get_table_entry(vm, "state");
352     get_table_entry(vm, "worlds");
353     worlds = get_table_keys(vm);
354   }
355   catch(const std::exception& err)
356   {
357     log_warning << err.what() << std::endl;
358   }
359
360   sq_settop(vm, oldtop);
361
362   return worlds;
363 }
364
365 WorldmapState
366 Savegame::get_worldmap_state(const std::string& name)
367 {
368   WorldmapState result;
369
370   HSQUIRRELVM vm = scripting::global_vm;
371   int oldtop = sq_gettop(vm);
372
373   try
374   {
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");
380
381     result.level_states = get_level_states(vm);
382   }
383   catch(const std::exception& err)
384   {
385     log_warning << err.what() << std::endl;
386   }
387
388   sq_settop(vm, oldtop);
389
390   return result;
391 }
392
393 std::vector<std::string>
394 Savegame::get_levelsets()
395 {
396   std::vector<std::string> results;
397
398   HSQUIRRELVM vm = scripting::global_vm;
399   int oldtop = sq_gettop(vm);
400
401   try
402   {
403     sq_pushroottable(vm);
404     get_table_entry(vm, "state");
405     get_table_entry(vm, "levelsets");
406     results = get_table_keys(vm);
407   }
408   catch(const std::exception& err)
409   {
410     log_warning << err.what() << std::endl;
411   }
412
413   sq_settop(vm, oldtop);
414
415   return results;
416 }
417
418 LevelsetState
419 Savegame::get_levelset_state(const std::string& basedir)
420 {
421   LevelsetState result;
422
423   HSQUIRRELVM vm = scripting::global_vm;
424   int oldtop = sq_gettop(vm);
425
426   try
427   {
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");
433
434     result.level_states = get_level_states(vm);
435   }
436   catch(const std::exception& err)
437   {
438     log_warning << err.what() << std::endl;
439   }
440
441   sq_settop(vm, oldtop);
442
443   return result;
444 }
445
446 void
447 Savegame::set_levelset_state(const std::string& basedir,
448                              const std::string& level_filename,
449                              bool solved)
450 {
451   LevelsetState state = get_levelset_state(basedir);
452
453   HSQUIRRELVM vm = scripting::global_vm;
454   int oldtop = sq_gettop(vm);
455
456   try
457   {
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);
464
465     bool old_solved = false;
466     scripting::get_bool(vm, "solved", old_solved);
467     scripting::store_bool(vm, "solved", solved || old_solved);
468   }
469   catch(const std::exception& err)
470   {
471     log_warning << err.what() << std::endl;
472   }
473
474   sq_settop(vm, oldtop);
475 }
476
477 /* EOF */