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