Further filename untangling in World class
[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   char** files = PHYSFS_enumerateFiles(m_basedir.c_str());
100   if(!files)
101   {
102     log_warning << "Couldn't read subset dir '" << m_basedir << "'" << std::endl;
103     return;
104   }
105
106   for(const char* const* filename = files; *filename != 0; ++filename)
107   {
108     if(StringUtil::has_suffix(*filename, ".stl"))
109     {
110       m_levels.push_back(*filename);
111     }
112   }
113   PHYSFS_freeList(files);
114
115   std::sort(m_levels.begin(), m_levels.end(), StringUtil::numeric_less);
116 }
117
118 void
119 World::run()
120 {
121   // create new squirrel table for persistent game state
122   HSQUIRRELVM vm = scripting::global_vm;
123
124   sq_pushroottable(vm);
125   sq_pushstring(vm, "state", -1);
126   sq_newtable(vm);
127   if(SQ_FAILED(sq_createslot(vm, -3)))
128   {
129     throw scripting::SquirrelError(vm, "Couldn't create state table");
130   }
131   else
132   {
133     sq_pop(vm, 1);
134
135     load_state();
136
137     std::string filename = m_basedir + "/world.nut";
138     try
139     {
140       IFileStreambuf ins(filename);
141       std::istream in(&ins);
142
143       sq_release(scripting::global_vm, &m_world_thread);
144       m_world_thread = scripting::create_thread(scripting::global_vm);
145       scripting::compile_and_run(scripting::object_to_vm(m_world_thread), in, filename);
146     }
147     catch(const std::exception& )
148     {
149       // fallback: try to load worldmap worldmap.stwm
150       g_screen_manager->push_screen(std::unique_ptr<Screen>(
151                                       new worldmap::WorldMap(m_worldmap_filename,
152                                                              get_player_status())));
153     }
154   }
155 }
156
157 void
158 World::save_state()
159 {
160   { // make sure the savegame directory exists
161     std::string dirname = FileSystem::dirname(m_savegame_filename);
162     if(!PHYSFS_exists(dirname.c_str()))
163     {
164       if(!PHYSFS_mkdir(dirname.c_str()))
165       {
166         std::ostringstream msg;
167         msg << "Couldn't create directory for savegames '"
168             << dirname << "': " <<PHYSFS_getLastError();
169         throw std::runtime_error(msg.str());
170       }
171     }
172
173     if(!PHYSFS_isDirectory(dirname.c_str()))
174     {
175       std::ostringstream msg;
176       msg << "Savegame path '" << dirname << "' is not a directory";
177       throw std::runtime_error(msg.str());
178     }
179   }
180
181   HSQUIRRELVM vm = scripting::global_vm;
182
183   lisp::Writer writer(m_savegame_filename);
184
185   writer.start_list("supertux-savegame");
186   writer.write("version", 1);
187
188   using namespace worldmap;
189   if(WorldMap::current() != NULL)
190   {
191     std::ostringstream title;
192     title << WorldMap::current()->get_title();
193     title << " (" << WorldMap::current()->solved_level_count()
194           << "/" << WorldMap::current()->level_count() << ")";
195     writer.write("title", title.str());
196   }
197
198   writer.start_list("tux");
199   m_player_status->write(writer);
200   writer.end_list("tux");
201
202   writer.start_list("state");
203
204   sq_pushroottable(vm);
205   sq_pushstring(vm, "state", -1);
206   if(SQ_SUCCEEDED(sq_get(vm, -2))) {
207     scripting::save_squirrel_table(vm, -1, writer);
208     sq_pop(vm, 1);
209   }
210   sq_pop(vm, 1);
211   writer.end_list("state");
212
213   writer.end_list("supertux-savegame");
214 }
215
216 void
217 World::load_state()
218 {
219   if(!PHYSFS_exists(m_savegame_filename.c_str()))
220   {
221     log_info << m_savegame_filename << ": doesn't exist, not loading state" << std::endl;
222   }
223   else
224   {
225     try
226     {
227       HSQUIRRELVM vm = scripting::global_vm;
228
229       lisp::Parser parser;
230       const lisp::Lisp* root = parser.parse(m_savegame_filename);
231
232       const lisp::Lisp* lisp = root->get_lisp("supertux-savegame");
233       if(lisp == NULL)
234         throw std::runtime_error("file is not a supertux-savegame file");
235
236       int version = 1;
237       lisp->get("version", version);
238       if(version != 1)
239         throw std::runtime_error("incompatible savegame version");
240
241       const lisp::Lisp* tux = lisp->get_lisp("tux");
242       if(tux == NULL)
243         throw std::runtime_error("No tux section in savegame");
244       m_player_status->read(*tux);
245
246       const lisp::Lisp* state = lisp->get_lisp("state");
247       if(state == NULL)
248         throw std::runtime_error("No state section in savegame");
249
250       sq_pushroottable(vm);
251       sq_pushstring(vm, "state", -1);
252       if(SQ_FAILED(sq_deleteslot(vm, -2, SQFalse)))
253         sq_pop(vm, 1);
254
255       sq_pushstring(vm, "state", -1);
256       sq_newtable(vm);
257       scripting::load_squirrel_table(vm, -1, *state);
258       if(SQ_FAILED(sq_createslot(vm, -3)))
259         throw std::runtime_error("Couldn't create state table");
260       sq_pop(vm, 1);
261     }
262     catch(const std::exception& e)
263     {
264       log_fatal << "Couldn't load savegame: " << e.what() << std::endl;
265     }
266   }
267 }
268
269 std::string
270 World::get_level_filename(unsigned int i) const
271 {
272   return FileSystem::join(m_basedir, m_levels[i]);
273 }
274
275 unsigned int
276 World::get_num_levels() const
277 {
278   return m_levels.size();
279 }
280
281 int
282 World::get_num_solved_levels() const
283 {
284   int num_solved_levels = 0;
285
286   HSQUIRRELVM vm = scripting::global_vm;
287   int oldtop = sq_gettop(vm);
288
289   sq_pushroottable(vm);
290   sq_pushstring(vm, "state", -1);
291   if(SQ_FAILED(sq_get(vm, -2)))
292   {
293     log_warning << "failed to get 'state' table" << std::endl;
294   }
295   else
296   {
297     sq_pushstring(vm, "worlds", -1);
298     if(SQ_FAILED(sq_get(vm, -2)))
299     {
300       log_warning << "failed to get 'state.worlds' table" << std::endl;
301     }
302     else
303     {
304       sq_pushstring(vm, m_worldmap_filename.c_str(), -1);
305       if(SQ_FAILED(sq_get(vm, -2)))
306       {
307         log_warning << "failed to get state.worlds['" << m_worldmap_filename << "']" << std::endl;
308       }
309       else
310       {
311         sq_pushstring(vm, "levels", -1);
312         if(SQ_FAILED(sq_get(vm, -2)))
313         {
314           log_warning << "failed to get state.worlds['" << m_worldmap_filename << "'].levels" << std::endl;
315         }
316         else
317         {
318           for(auto level : m_levels)
319           {
320             sq_pushstring(vm, level.c_str(), -1);
321             if(SQ_FAILED(sq_get(vm, -2)))
322             {
323               log_warning << "failed to get state.worlds['" << m_worldmap_filename << "'].levels['"
324                           << level << "']" << std::endl;
325             }
326             else
327             {
328               bool solved = scripting::read_bool(vm, "solved");
329               if (solved)
330               {
331                 num_solved_levels += 1;
332               }
333               sq_pop(vm, 1);
334             }
335           }
336         }
337       }
338     }
339   }
340
341   sq_settop(vm, oldtop);
342
343   return num_solved_levels;
344 }
345
346 std::string
347 World::get_basedir() const
348 {
349   return m_basedir;
350 }
351
352 std::string
353 World::get_title() const
354 {
355   return m_title;
356 }
357
358 /* EOF */