fbfe809b5eb703ba2ac2e3f24e82afe670fa12e4
[supertux.git] / src / supertux / world.cpp
1 //  SuperTux
2 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
3 //
4 //  This program is free software: you can redistribute it and/or modify
5 //  it under the terms of the GNU General Public License as published by
6 //  the Free Software Foundation, either version 3 of the License, or
7 //  (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 #include <algorithm>
18
19 #include "lisp/parser.hpp"
20 #include "lisp/writer.hpp"
21 #include "physfs/ifile_streambuf.hpp"
22 #include "scripting/serialize.hpp"
23 #include "scripting/squirrel_util.hpp"
24 #include "supertux/globals.hpp"
25 #include "supertux/screen_fade.hpp"
26 #include "supertux/screen_manager.hpp"
27 #include "supertux/player_status.hpp"
28 #include "supertux/world.hpp"
29 #include "util/file_system.hpp"
30 #include "util/reader.hpp"
31 #include "util/string_util.hpp"
32 #include "worldmap/worldmap.hpp"
33
34 World::World() :
35   m_worldmap_filename(),
36   m_levels(),
37   m_basedir(),
38   m_savegame_filename(),
39   m_world_thread(),
40   m_title(),
41   m_description(),
42   m_player_status(new PlayerStatus),
43   m_hide_from_contribs(false),
44   m_is_levelset(true)
45 {
46   sq_resetobject(&m_world_thread);
47 }
48
49 World::~World()
50 {
51   sq_release(scripting::global_vm, &m_world_thread);
52 }
53
54 void
55 World::set_savegame_filename(const std::string& filename)
56 {
57   m_savegame_filename = filename;
58   // make sure the savegame directory exists
59   std::string dirname = FileSystem::dirname(filename);
60   if(!PHYSFS_exists(dirname.c_str())) {
61     if(!PHYSFS_mkdir(dirname.c_str())) {
62       std::ostringstream msg;
63       msg << "Couldn't create directory for savegames '"
64           << dirname << "': " <<PHYSFS_getLastError();
65       throw std::runtime_error(msg.str());
66     }
67   }
68
69   if(!PHYSFS_isDirectory(dirname.c_str())) {
70     std::ostringstream msg;
71     msg << "Savegame path '" << dirname << "' is not a directory";
72     throw std::runtime_error(msg.str());
73   }
74 }
75
76 void
77 World::load(const std::string& filename)
78 {
79   m_basedir = FileSystem::dirname(filename);
80   m_worldmap_filename = m_basedir + "worldmap.stwm";
81
82   lisp::Parser parser;
83   const lisp::Lisp* root = parser.parse(filename);
84
85   const lisp::Lisp* info = root->get_lisp("supertux-world");
86   if(info == NULL)
87     info = root->get_lisp("supertux-level-subset");
88   if(info == NULL)
89     throw std::runtime_error("File is not a world or levelsubset file");
90
91   m_hide_from_contribs = false;
92   m_is_levelset = true;
93
94   info->get("title", m_title);
95   info->get("description", m_description);
96   info->get("levelset", m_is_levelset);
97   info->get("hide-from-contribs", m_hide_from_contribs);
98
99   // Level info file doesn't define any levels, so read the
100   // directory to see what we can find
101
102   std::string path = m_basedir;
103   char** files = PHYSFS_enumerateFiles(path.c_str());
104   if(!files) {
105     log_warning << "Couldn't read subset dir '" << path << "'" << std::endl;
106     return;
107   }
108
109   for(const char* const* filename = files; *filename != 0; ++filename) {
110     if(StringUtil::has_suffix(*filename, ".stl")) {
111       Level level;
112       level.fullpath = path + *filename;
113       level.name = *filename;
114       m_levels.push_back(level);
115     }
116   }
117   PHYSFS_freeList(files);
118
119   std::sort(m_levels.begin(), m_levels.end(),
120             [](const Level& lhs, const Level& rhs)
121             {
122               return StringUtil::numeric_less(lhs.fullpath, rhs.fullpath);
123             });
124 }
125
126 void
127 World::run()
128 {
129   // create new squirrel table for persistent game state
130   HSQUIRRELVM vm = scripting::global_vm;
131
132   sq_pushroottable(vm);
133   sq_pushstring(vm, "state", -1);
134   sq_newtable(vm);
135   if(SQ_FAILED(sq_createslot(vm, -3)))
136     throw scripting::SquirrelError(vm, "Couldn't create state table");
137   sq_pop(vm, 1);
138
139   load_state();
140
141   std::string filename = m_basedir + "/world.nut";
142   try {
143     IFileStreambuf ins(filename);
144     std::istream in(&ins);
145
146     sq_release(scripting::global_vm, &m_world_thread);
147     m_world_thread = scripting::create_thread(scripting::global_vm);
148     scripting::compile_and_run(scripting::object_to_vm(m_world_thread), in, filename);
149   } catch(std::exception& ) {
150     // fallback: try to load worldmap worldmap.stwm
151     using namespace worldmap;
152     g_screen_manager->push_screen(std::unique_ptr<Screen>(new WorldMap(m_basedir + "worldmap.stwm", get_player_status())));
153   }
154 }
155
156 void
157 World::save_state()
158 {
159   using namespace scripting;
160
161   lisp::Writer writer(m_savegame_filename);
162
163   writer.start_list("supertux-savegame");
164   writer.write("version", 1);
165
166   using namespace worldmap;
167   if(WorldMap::current() != NULL) {
168     std::ostringstream title;
169     title << WorldMap::current()->get_title();
170     title << " (" << WorldMap::current()->solved_level_count()
171           << "/" << WorldMap::current()->level_count() << ")";
172     writer.write("title", title.str());
173   }
174
175   writer.start_list("tux");
176   m_player_status->write(writer);
177   writer.end_list("tux");
178
179   writer.start_list("state");
180
181   sq_pushroottable(global_vm);
182   sq_pushstring(global_vm, "state", -1);
183   if(SQ_SUCCEEDED(sq_get(global_vm, -2))) {
184     scripting::save_squirrel_table(global_vm, -1, writer);
185     sq_pop(global_vm, 1);
186   }
187   sq_pop(global_vm, 1);
188   writer.end_list("state");
189
190   writer.end_list("supertux-savegame");
191 }
192
193 void
194 World::load_state()
195 {
196   using namespace scripting;
197
198   if(!PHYSFS_exists(m_savegame_filename.c_str()))
199   {
200     log_info << m_savegame_filename << ": doesn't exist, not loading state" << std::endl;
201   }
202   else
203   {
204     try {
205       lisp::Parser parser;
206       const lisp::Lisp* root = parser.parse(m_savegame_filename);
207
208       const lisp::Lisp* lisp = root->get_lisp("supertux-savegame");
209       if(lisp == NULL)
210         throw std::runtime_error("file is not a supertux-savegame file");
211
212       int version = 1;
213       lisp->get("version", version);
214       if(version != 1)
215         throw std::runtime_error("incompatible savegame version");
216
217       const lisp::Lisp* tux = lisp->get_lisp("tux");
218       if(tux == NULL)
219         throw std::runtime_error("No tux section in savegame");
220       m_player_status->read(*tux);
221
222       const lisp::Lisp* state = lisp->get_lisp("state");
223       if(state == NULL)
224         throw std::runtime_error("No state section in savegame");
225
226       sq_pushroottable(global_vm);
227       sq_pushstring(global_vm, "state", -1);
228       if(SQ_FAILED(sq_deleteslot(global_vm, -2, SQFalse)))
229         sq_pop(global_vm, 1);
230
231       sq_pushstring(global_vm, "state", -1);
232       sq_newtable(global_vm);
233       load_squirrel_table(global_vm, -1, *state);
234       if(SQ_FAILED(sq_createslot(global_vm, -3)))
235         throw std::runtime_error("Couldn't create state table");
236       sq_pop(global_vm, 1);
237     } catch(std::exception& e) {
238       log_fatal << "Couldn't load savegame: " << e.what() << std::endl;
239     }
240   }
241 }
242
243 const std::string&
244 World::get_level_filename(unsigned int i) const
245 {
246   return m_levels[i].fullpath;
247 }
248
249 unsigned int
250 World::get_num_levels() const
251 {
252   return m_levels.size();
253 }
254
255 int
256 World::get_num_solved_levels() const
257 {
258   int num_solved_levels = 0;
259
260   HSQUIRRELVM vm = scripting::global_vm;
261   int oldtop = sq_gettop(vm);
262
263   sq_pushroottable(vm);
264   sq_pushstring(vm, "state", -1);
265   if(SQ_FAILED(sq_get(vm, -2)))
266   {
267     log_warning << "failed to get 'state' table" << std::endl;
268   }
269   else
270   {
271     sq_pushstring(vm, "worlds", -1);
272     if(SQ_FAILED(sq_get(vm, -2)))
273     {
274       log_warning << "failed to get 'state.worlds' table" << std::endl;
275     }
276     else
277     {
278       sq_pushstring(vm, m_worldmap_filename.c_str(), -1);
279       if(SQ_FAILED(sq_get(vm, -2)))
280       {
281         log_warning << "failed to get state.worlds['" << m_worldmap_filename << "']" << std::endl;
282       }
283       else
284       {
285         sq_pushstring(vm, "levels", -1);
286         if(SQ_FAILED(sq_get(vm, -2)))
287         {
288           log_warning << "failed to get state.worlds['" << m_worldmap_filename << "'].levels" << std::endl;
289         }
290         else
291         {
292           for(auto level : m_levels)
293           {
294             sq_pushstring(vm, level.name.c_str(), -1);
295             if(SQ_FAILED(sq_get(vm, -2)))
296             {
297               log_warning << "failed to get state.worlds['" << m_worldmap_filename << "'].levels['"
298                           << level.name << "']" << std::endl;
299             }
300             else
301             {
302               bool solved = scripting::read_bool(vm, "solved");
303               if (solved)
304               {
305                 num_solved_levels += 1;
306               }
307               sq_pop(vm, 1);
308             }
309           }
310         }
311       }
312     }
313   }
314
315   sq_settop(vm, oldtop);
316
317   return num_solved_levels;
318 }
319
320 const std::string&
321 World::get_basedir() const
322 {
323   return m_basedir;
324 }
325
326 const std::string&
327 World::get_title() const
328 {
329   return m_title;
330 }
331
332 /* EOF */