Cleaned up the file handling in World, Worlds are now loaded by directory name instea...
[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/gameconfig.hpp"
25 #include "supertux/globals.hpp"
26 #include "supertux/player_status.hpp"
27 #include "supertux/screen_fade.hpp"
28 #include "supertux/screen_manager.hpp"
29 #include "supertux/world.hpp"
30 #include "util/file_system.hpp"
31 #include "util/reader.hpp"
32 #include "util/string_util.hpp"
33 #include "worldmap/worldmap.hpp"
34
35 std::unique_ptr<World>
36 World::load(const std::string& directory)
37 {
38   std::unique_ptr<World> world(new World);
39
40   world->load_(directory);
41
42   { // generate savegame filename
43     std::string worlddirname = FileSystem::basename(directory);
44     std::ostringstream stream;
45     stream << "profile" << g_config->profile << "/" << worlddirname << ".stsg";
46     std::string slotfile = stream.str();
47     world->m_savegame_filename = stream.str();
48   }
49
50   return std::move(world);
51 }
52
53 World::World() :
54   m_levels(),
55   m_basedir(),
56   m_worldmap_filename(),
57   m_savegame_filename(),
58   m_world_thread(),
59   m_title(),
60   m_description(),
61   m_player_status(new PlayerStatus),
62   m_hide_from_contribs(false),
63   m_is_levelset(true)
64 {
65   sq_resetobject(&m_world_thread);
66 }
67
68 World::~World()
69 {
70   sq_release(scripting::global_vm, &m_world_thread);
71 }
72
73 void
74 World::load_(const std::string& directory)
75 {
76   m_basedir = directory;
77   m_worldmap_filename = m_basedir + "/worldmap.stwm";
78
79   lisp::Parser parser;
80   const lisp::Lisp* root = parser.parse(m_basedir + "/info");
81
82   const lisp::Lisp* info = root->get_lisp("supertux-world");
83   if(info == NULL)
84     info = root->get_lisp("supertux-level-subset");
85   if(info == NULL)
86     throw std::runtime_error("File is not a world or levelsubset file");
87
88   m_hide_from_contribs = false;
89   m_is_levelset = true;
90
91   info->get("title", m_title);
92   info->get("description", m_description);
93   info->get("levelset", m_is_levelset);
94   info->get("hide-from-contribs", m_hide_from_contribs);
95
96   // Level info file doesn't define any levels, so read the
97   // directory to see what we can find
98
99   std::string path = m_basedir;
100   char** files = PHYSFS_enumerateFiles(path.c_str());
101   if(!files)
102   {
103     log_warning << "Couldn't read subset dir '" << path << "'" << std::endl;
104     return;
105   }
106
107   for(const char* const* filename = files; *filename != 0; ++filename)
108   {
109     if(StringUtil::has_suffix(*filename, ".stl"))
110     {
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   {
137     throw scripting::SquirrelError(vm, "Couldn't create state table");
138   }
139   else
140   {
141     sq_pop(vm, 1);
142
143     load_state();
144
145     std::string filename = m_basedir + "/world.nut";
146     try
147     {
148       IFileStreambuf ins(filename);
149       std::istream in(&ins);
150
151       sq_release(scripting::global_vm, &m_world_thread);
152       m_world_thread = scripting::create_thread(scripting::global_vm);
153       scripting::compile_and_run(scripting::object_to_vm(m_world_thread), in, filename);
154     }
155     catch(const std::exception& )
156     {
157       // fallback: try to load worldmap worldmap.stwm
158       g_screen_manager->push_screen(std::unique_ptr<Screen>(
159                                       new worldmap::WorldMap(m_worldmap_filename,
160                                                              get_player_status())));
161     }
162   }
163 }
164
165 void
166 World::save_state()
167 {
168   { // make sure the savegame directory exists
169     std::string dirname = FileSystem::dirname(m_savegame_filename);
170     if(!PHYSFS_exists(dirname.c_str()))
171     {
172       if(!PHYSFS_mkdir(dirname.c_str()))
173       {
174         std::ostringstream msg;
175         msg << "Couldn't create directory for savegames '"
176             << dirname << "': " <<PHYSFS_getLastError();
177         throw std::runtime_error(msg.str());
178       }
179     }
180
181     if(!PHYSFS_isDirectory(dirname.c_str()))
182     {
183       std::ostringstream msg;
184       msg << "Savegame path '" << dirname << "' is not a directory";
185       throw std::runtime_error(msg.str());
186     }
187   }
188
189   HSQUIRRELVM vm = scripting::global_vm;
190
191   lisp::Writer writer(m_savegame_filename);
192
193   writer.start_list("supertux-savegame");
194   writer.write("version", 1);
195
196   using namespace worldmap;
197   if(WorldMap::current() != NULL)
198   {
199     std::ostringstream title;
200     title << WorldMap::current()->get_title();
201     title << " (" << WorldMap::current()->solved_level_count()
202           << "/" << WorldMap::current()->level_count() << ")";
203     writer.write("title", title.str());
204   }
205
206   writer.start_list("tux");
207   m_player_status->write(writer);
208   writer.end_list("tux");
209
210   writer.start_list("state");
211
212   sq_pushroottable(vm);
213   sq_pushstring(vm, "state", -1);
214   if(SQ_SUCCEEDED(sq_get(vm, -2))) {
215     scripting::save_squirrel_table(vm, -1, writer);
216     sq_pop(vm, 1);
217   }
218   sq_pop(vm, 1);
219   writer.end_list("state");
220
221   writer.end_list("supertux-savegame");
222 }
223
224 void
225 World::load_state()
226 {
227   if(!PHYSFS_exists(m_savegame_filename.c_str()))
228   {
229     log_info << m_savegame_filename << ": doesn't exist, not loading state" << std::endl;
230   }
231   else
232   {
233     try
234     {
235       HSQUIRRELVM vm = scripting::global_vm;
236
237       lisp::Parser parser;
238       const lisp::Lisp* root = parser.parse(m_savegame_filename);
239
240       const lisp::Lisp* lisp = root->get_lisp("supertux-savegame");
241       if(lisp == NULL)
242         throw std::runtime_error("file is not a supertux-savegame file");
243
244       int version = 1;
245       lisp->get("version", version);
246       if(version != 1)
247         throw std::runtime_error("incompatible savegame version");
248
249       const lisp::Lisp* tux = lisp->get_lisp("tux");
250       if(tux == NULL)
251         throw std::runtime_error("No tux section in savegame");
252       m_player_status->read(*tux);
253
254       const lisp::Lisp* state = lisp->get_lisp("state");
255       if(state == NULL)
256         throw std::runtime_error("No state section in savegame");
257
258       sq_pushroottable(vm);
259       sq_pushstring(vm, "state", -1);
260       if(SQ_FAILED(sq_deleteslot(vm, -2, SQFalse)))
261         sq_pop(vm, 1);
262
263       sq_pushstring(vm, "state", -1);
264       sq_newtable(vm);
265       scripting::load_squirrel_table(vm, -1, *state);
266       if(SQ_FAILED(sq_createslot(vm, -3)))
267         throw std::runtime_error("Couldn't create state table");
268       sq_pop(vm, 1);
269     }
270     catch(const std::exception& e)
271     {
272       log_fatal << "Couldn't load savegame: " << e.what() << std::endl;
273     }
274   }
275 }
276
277 const std::string&
278 World::get_level_filename(unsigned int i) const
279 {
280   return m_levels[i].fullpath;
281 }
282
283 unsigned int
284 World::get_num_levels() const
285 {
286   return m_levels.size();
287 }
288
289 int
290 World::get_num_solved_levels() const
291 {
292   int num_solved_levels = 0;
293
294   HSQUIRRELVM vm = scripting::global_vm;
295   int oldtop = sq_gettop(vm);
296
297   sq_pushroottable(vm);
298   sq_pushstring(vm, "state", -1);
299   if(SQ_FAILED(sq_get(vm, -2)))
300   {
301     log_warning << "failed to get 'state' table" << std::endl;
302   }
303   else
304   {
305     sq_pushstring(vm, "worlds", -1);
306     if(SQ_FAILED(sq_get(vm, -2)))
307     {
308       log_warning << "failed to get 'state.worlds' table" << std::endl;
309     }
310     else
311     {
312       sq_pushstring(vm, m_worldmap_filename.c_str(), -1);
313       if(SQ_FAILED(sq_get(vm, -2)))
314       {
315         log_warning << "failed to get state.worlds['" << m_worldmap_filename << "']" << std::endl;
316       }
317       else
318       {
319         sq_pushstring(vm, "levels", -1);
320         if(SQ_FAILED(sq_get(vm, -2)))
321         {
322           log_warning << "failed to get state.worlds['" << m_worldmap_filename << "'].levels" << std::endl;
323         }
324         else
325         {
326           for(auto level : m_levels)
327           {
328             sq_pushstring(vm, level.name.c_str(), -1);
329             if(SQ_FAILED(sq_get(vm, -2)))
330             {
331               log_warning << "failed to get state.worlds['" << m_worldmap_filename << "'].levels['"
332                           << level.name << "']" << std::endl;
333             }
334             else
335             {
336               bool solved = scripting::read_bool(vm, "solved");
337               if (solved)
338               {
339                 num_solved_levels += 1;
340               }
341               sq_pop(vm, 1);
342             }
343           }
344         }
345       }
346     }
347   }
348
349   sq_settop(vm, oldtop);
350
351   return num_solved_levels;
352 }
353
354 const std::string&
355 World::get_basedir() const
356 {
357   return m_basedir;
358 }
359
360 const std::string&
361 World::get_title() const
362 {
363   return m_title;
364 }
365
366 /* EOF */