Renamed WorldState to Savegame and implemented basic load/save for Worldmaps and...
[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 Savegame::Savegame(const std::string& filename) :
109   m_filename(filename),
110   m_player_status(new PlayerStatus)
111 {
112 }
113
114 Savegame::~Savegame()
115 {
116 }
117
118 void
119 Savegame::load()
120 {
121   if (m_filename.empty())
122   {
123     log_debug << "no filename set for savegame, skipping load" << std::endl;
124     return;
125   }
126
127   if(!PHYSFS_exists(m_filename.c_str()))
128   {
129     log_info << m_filename << ": doesn't exist, not loading state" << std::endl;
130   }
131   else
132   {
133     log_debug << "loading savegame from " << m_filename << std::endl;
134
135     try
136     {
137       HSQUIRRELVM vm = scripting::global_vm;
138
139       lisp::Parser parser;
140       const lisp::Lisp* root = parser.parse(m_filename);
141
142       const lisp::Lisp* lisp = root->get_lisp("supertux-savegame");
143       if(lisp == NULL)
144       {
145         throw std::runtime_error("file is not a supertux-savegame file");
146       }
147       else
148       {
149         int version = 1;
150         lisp->get("version", version);
151         if(version != 1)
152         {
153           throw std::runtime_error("incompatible savegame version");
154         }
155         else
156         {
157           const lisp::Lisp* tux = lisp->get_lisp("tux");
158           if(tux == NULL)
159           {
160             throw std::runtime_error("No tux section in savegame");
161           }
162           {
163             m_player_status->read(*tux);
164           }
165
166           const lisp::Lisp* state = lisp->get_lisp("state");
167           if(state == NULL)
168           {
169             throw std::runtime_error("No state section in savegame");
170           }
171           else
172           {
173             // delete existing state table, if it exists
174             sq_pushroottable(vm);
175             sq_pushstring(vm, "state", -1);
176             if(SQ_FAILED(sq_deleteslot(vm, -2, SQFalse)))
177               sq_pop(vm, 1);
178
179             // create a new empty state table
180             sq_pushstring(vm, "state", -1);
181             sq_newtable(vm);
182             scripting::load_squirrel_table(vm, -1, *state);
183             if(SQ_FAILED(sq_createslot(vm, -3)))
184               throw std::runtime_error("Couldn't create state table");
185             sq_pop(vm, 1);
186           }
187         }
188       }
189     }
190     catch(const std::exception& e)
191     {
192       log_fatal << "Couldn't load savegame: " << e.what() << std::endl;
193     }
194   }
195 }
196
197 void
198 Savegame::save()
199 {
200   if (m_filename.empty())
201   {
202     log_debug << "no filename set for savegame, skipping save" << std::endl;
203     return;
204   }
205
206   log_debug << "saving savegame to " << m_filename << std::endl;
207
208   { // make sure the savegame directory exists
209     std::string dirname = FileSystem::dirname(m_filename);
210     if(!PHYSFS_exists(dirname.c_str()))
211     {
212       if(!PHYSFS_mkdir(dirname.c_str()))
213       {
214         std::ostringstream msg;
215         msg << "Couldn't create directory for savegames '"
216             << dirname << "': " <<PHYSFS_getLastError();
217         throw std::runtime_error(msg.str());
218       }
219     }
220
221     if(!PHYSFS_isDirectory(dirname.c_str()))
222     {
223       std::ostringstream msg;
224       msg << "Savegame path '" << dirname << "' is not a directory";
225       throw std::runtime_error(msg.str());
226     }
227   }
228
229   HSQUIRRELVM vm = scripting::global_vm;
230
231   lisp::Writer writer(m_filename);
232
233   writer.start_list("supertux-savegame");
234   writer.write("version", 1);
235
236   using namespace worldmap;
237   if(WorldMap::current() != NULL)
238   {
239     std::ostringstream title;
240     title << WorldMap::current()->get_title();
241     title << " (" << WorldMap::current()->solved_level_count()
242           << "/" << WorldMap::current()->level_count() << ")";
243     writer.write("title", title.str());
244   }
245
246   writer.start_list("tux");
247   m_player_status->write(writer);
248   writer.end_list("tux");
249
250   writer.start_list("state");
251
252   sq_pushroottable(vm);
253   sq_pushstring(vm, "state", -1);
254   if(SQ_SUCCEEDED(sq_get(vm, -2)))
255   {
256     scripting::save_squirrel_table(vm, -1, writer);
257     sq_pop(vm, 1);
258   }
259   sq_pop(vm, 1);
260   writer.end_list("state");
261
262   writer.end_list("supertux-savegame");
263 }
264
265 std::vector<std::string>
266 Savegame::get_worldmaps()
267 {
268   std::vector<std::string> worlds;
269
270   HSQUIRRELVM vm = scripting::global_vm;
271   int oldtop = sq_gettop(vm);
272
273   try
274   {
275     sq_pushroottable(vm);
276     get_table_entry(vm, "state");
277     get_table_entry(vm, "worlds");
278     worlds = get_table_keys(vm);
279   }
280   catch(const std::exception& err)
281   {
282     log_warning << err.what() << std::endl;
283   }
284
285   sq_settop(vm, oldtop);
286
287   return worlds;
288 }
289
290 WorldmapState
291 Savegame::get_worldmap_state(const std::string& name)
292 {
293   WorldmapState result;
294
295   HSQUIRRELVM vm = scripting::global_vm;
296   int oldtop = sq_gettop(vm);
297
298   try
299   {
300     sq_pushroottable(vm);
301     get_table_entry(vm, "state");
302     get_table_entry(vm, "worlds");
303     get_table_entry(vm, name);
304     get_table_entry(vm, "levels");
305
306     result.level_states = get_level_states(vm);
307   }
308   catch(const std::exception& err)
309   {
310     log_warning << err.what() << std::endl;
311   }
312
313   sq_settop(vm, oldtop);
314
315   return result;
316 }
317
318 std::vector<std::string>
319 Savegame::get_levelsets()
320 {
321   std::vector<std::string> results;
322
323   HSQUIRRELVM vm = scripting::global_vm;
324   int oldtop = sq_gettop(vm);
325
326   try
327   {
328     sq_pushroottable(vm);
329     get_table_entry(vm, "state");
330     get_table_entry(vm, "levelsets");
331     results = get_table_keys(vm);
332   }
333   catch(const std::exception& err)
334   {
335     log_warning << err.what() << std::endl;
336   }
337
338   sq_settop(vm, oldtop);
339
340   return results;
341 }
342
343 LevelsetState
344 Savegame::get_levelset_state(const std::string& name)
345 {
346   LevelsetState result;
347
348   HSQUIRRELVM vm = scripting::global_vm;
349   int oldtop = sq_gettop(vm);
350
351   try
352   {
353     sq_pushroottable(vm);
354     get_table_entry(vm, "state");
355     get_table_entry(vm, "levelsets");
356     get_table_entry(vm, name);
357     get_table_entry(vm, "levels");
358
359     result.level_states = get_level_states(vm);
360   }
361   catch(const std::exception& err)
362   {
363     log_warning << err.what() << std::endl;
364   }
365
366   sq_settop(vm, oldtop);
367
368   return result;
369 }
370
371 /* EOF */