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