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