Fixed state table creation issue on loading a savegame
[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             scripting::load_squirrel_table(vm, -1, *state);
199             sq_pop(vm, 2);
200           }
201         }
202       }
203     }
204     catch(const std::exception& e)
205     {
206       log_fatal << "Couldn't load savegame: " << e.what() << std::endl;
207     }
208   }
209 }
210
211 void
212 Savegame::clear_state_table()
213 {
214   HSQUIRRELVM vm = scripting::global_vm;
215
216   // delete existing state table, if it exists
217   sq_pushroottable(vm);
218   {
219     /*sq_pushstring(vm, "state", -1);
220     if(SQ_FAILED(sq_deleteslot(vm, -2, SQFalse)))
221     {
222       sq_pop(vm, 1);
223       }*/
224
225     // create a new empty state table
226     sq_pushstring(vm, "state", -1);
227     sq_newtable(vm);
228     if(SQ_FAILED(sq_newslot(vm, -3, SQFalse)))
229     {
230       throw std::runtime_error("Couldn't create state table");
231     }
232   }
233   sq_pop(vm, 1);
234 }
235
236 void
237 Savegame::save()
238 {
239   if (m_filename.empty())
240   {
241     log_debug << "no filename set for savegame, skipping save" << std::endl;
242     return;
243   }
244
245   log_debug << "saving savegame to " << m_filename << std::endl;
246
247   { // make sure the savegame directory exists
248     std::string dirname = FileSystem::dirname(m_filename);
249     if(!PHYSFS_exists(dirname.c_str()))
250     {
251       if(!PHYSFS_mkdir(dirname.c_str()))
252       {
253         std::ostringstream msg;
254         msg << "Couldn't create directory for savegames '"
255             << dirname << "': " <<PHYSFS_getLastError();
256         throw std::runtime_error(msg.str());
257       }
258     }
259
260     if(!PHYSFS_isDirectory(dirname.c_str()))
261     {
262       std::ostringstream msg;
263       msg << "Savegame path '" << dirname << "' is not a directory";
264       throw std::runtime_error(msg.str());
265     }
266   }
267
268   HSQUIRRELVM vm = scripting::global_vm;
269
270   lisp::Writer writer(m_filename);
271
272   writer.start_list("supertux-savegame");
273   writer.write("version", 1);
274
275   using namespace worldmap;
276   if(WorldMap::current() != NULL)
277   {
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());
283   }
284
285   writer.start_list("tux");
286   m_player_status->write(writer);
287   writer.end_list("tux");
288
289   writer.start_list("state");
290
291   sq_pushroottable(vm);
292   sq_pushstring(vm, "state", -1);
293   if(SQ_SUCCEEDED(sq_get(vm, -2)))
294   {
295     scripting::save_squirrel_table(vm, -1, writer);
296     sq_pop(vm, 1);
297   }
298   sq_pop(vm, 1);
299   writer.end_list("state");
300
301   writer.end_list("supertux-savegame");
302 }
303
304 std::vector<std::string>
305 Savegame::get_worldmaps()
306 {
307   std::vector<std::string> worlds;
308
309   HSQUIRRELVM vm = scripting::global_vm;
310   int oldtop = sq_gettop(vm);
311
312   try
313   {
314     sq_pushroottable(vm);
315     get_table_entry(vm, "state");
316     get_table_entry(vm, "worlds");
317     worlds = get_table_keys(vm);
318   }
319   catch(const std::exception& err)
320   {
321     log_warning << err.what() << std::endl;
322   }
323
324   sq_settop(vm, oldtop);
325
326   return worlds;
327 }
328
329 WorldmapState
330 Savegame::get_worldmap_state(const std::string& name)
331 {
332   WorldmapState result;
333
334   HSQUIRRELVM vm = scripting::global_vm;
335   int oldtop = sq_gettop(vm);
336
337   try
338   {
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");
344
345     result.level_states = get_level_states(vm);
346   }
347   catch(const std::exception& err)
348   {
349     log_warning << err.what() << std::endl;
350   }
351
352   sq_settop(vm, oldtop);
353
354   return result;
355 }
356
357 std::vector<std::string>
358 Savegame::get_levelsets()
359 {
360   std::vector<std::string> results;
361
362   HSQUIRRELVM vm = scripting::global_vm;
363   int oldtop = sq_gettop(vm);
364
365   try
366   {
367     sq_pushroottable(vm);
368     get_table_entry(vm, "state");
369     get_table_entry(vm, "levelsets");
370     results = get_table_keys(vm);
371   }
372   catch(const std::exception& err)
373   {
374     log_warning << err.what() << std::endl;
375   }
376
377   sq_settop(vm, oldtop);
378
379   return results;
380 }
381
382 LevelsetState
383 Savegame::get_levelset_state(const std::string& name)
384 {
385   LevelsetState result;
386
387   HSQUIRRELVM vm = scripting::global_vm;
388   int oldtop = sq_gettop(vm);
389
390   try
391   {
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");
397
398     result.level_states = get_level_states(vm);
399   }
400   catch(const std::exception& err)
401   {
402     log_warning << err.what() << std::endl;
403   }
404
405   sq_settop(vm, oldtop);
406
407   return result;
408 }
409
410 /* EOF */