fix cr/lfs and remove trailing whitespaces...
[supertux.git] / src / world.cpp
1 //  $Id$
2 //
3 //  SuperTux
4 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19 //  02111-1307, USA.
20 #include <config.h>
21
22 #include <stddef.h>
23 #include <physfs.h>
24 #include <stdexcept>
25
26 #include "world.hpp"
27 #include "file_system.hpp"
28 #include "lisp/parser.hpp"
29 #include "lisp/lisp.hpp"
30 #include "physfs/physfs_stream.hpp"
31 #include "scripting/squirrel_util.hpp"
32 #include "scripting/serialize.hpp"
33 #include "log.hpp"
34 #include "worldmap/worldmap.hpp"
35 #include "mainloop.hpp"
36
37 static bool has_suffix(const std::string& data, const std::string& suffix)
38 {
39   if (data.length() >= suffix.length())
40     return data.compare(data.length() - suffix.length(), suffix.length(), suffix) == 0;
41   else
42     return false;
43 }
44
45 World* World::current_ = NULL;
46
47 World::World()
48 {
49   is_levelset = true;
50   hide_from_contribs = false;
51   sq_resetobject(&world_thread);
52 }
53
54 World::~World()
55 {
56   sq_release(Scripting::global_vm, &world_thread);
57   if(current_ == this)
58     current_ = NULL;
59 }
60
61 void
62 World::set_savegame_filename(const std::string& filename)
63 {
64   this->savegame_filename = filename;
65   // make sure the savegame directory exists
66   std::string dirname = FileSystem::dirname(filename);
67   if(!PHYSFS_exists(dirname.c_str())) {
68       if(PHYSFS_mkdir(dirname.c_str())) {
69           std::ostringstream msg;
70           msg << "Couldn't create directory for savegames '"
71               << dirname << "': " <<PHYSFS_getLastError();
72           throw std::runtime_error(msg.str());
73       }
74   }
75
76   if(!PHYSFS_isDirectory(dirname.c_str())) {
77       std::ostringstream msg;
78       msg << "Savegame path '" << dirname << "' is not a directory";
79       throw std::runtime_error(msg.str());
80   }
81 }
82
83 void
84 World::load(const std::string& filename)
85 {
86   basedir = FileSystem::dirname(filename);
87
88   lisp::Parser parser;
89   std::auto_ptr<lisp::Lisp> root (parser.parse(filename));
90
91   const lisp::Lisp* info = root->get_lisp("supertux-world");
92   if(info == NULL)
93     info = root->get_lisp("supertux-level-subset");
94   if(info == NULL)
95     throw std::runtime_error("File is not a world or levelsubset file");
96
97   hide_from_contribs = false;
98   is_levelset = true;
99
100   info->get("title", title);
101   info->get("description", description);
102   info->get("levelset", is_levelset);
103   info->get_vector("levels", levels);
104   info->get("hide-from-contribs", hide_from_contribs);
105
106   // Level info file doesn't define any levels, so read the
107   // directory to see what we can find
108
109   std::string path = basedir + "/";
110   char** files = PHYSFS_enumerateFiles(path.c_str());
111   if(!files) {
112     log_warning << "Couldn't read subset dir '" << path << "'" << std::endl;
113     return;
114   }
115
116   for(const char* const* filename = files; *filename != 0; ++filename) {
117     if(has_suffix(*filename, ".stl")) {
118       levels.push_back(path + *filename);
119     }
120   }
121   PHYSFS_freeList(files);
122 }
123
124 void
125 World::run()
126 {
127   using namespace Scripting;
128
129   current_ = this;
130
131   // create new squirrel table for persisten game state
132   HSQUIRRELVM vm = Scripting::global_vm;
133
134   sq_pushroottable(vm);
135   sq_pushstring(vm, "state", -1);
136   sq_newtable(vm);
137   if(SQ_FAILED(sq_createslot(vm, -3)))
138     throw Scripting::SquirrelError(vm, "Couldn't create state table");
139   sq_pop(vm, 1);
140
141   load_state();
142
143   std::string filename = basedir + "/world.nut";
144   try {
145     IFileStream in(filename);
146
147     sq_release(global_vm, &world_thread);
148     world_thread = create_thread(global_vm);
149     compile_and_run(object_to_vm(world_thread), in, filename);
150   } catch(std::exception& e) {
151     // fallback: try to load worldmap worldmap.stwm
152     using namespace WorldMapNS;
153     main_loop->push_screen(new WorldMap(basedir + "worldmap.stwm"));
154   }
155 }
156
157 void
158 World::save_state()
159 {
160   using namespace Scripting;
161
162   lisp::Writer writer(savegame_filename);
163
164   writer.start_list("supertux-savegame");
165   writer.write_int("version", 1);
166
167   using namespace WorldMapNS;
168   if(WorldMap::current() != NULL) {
169     std::ostringstream title;
170     title << WorldMap::current()->get_title();
171     title << " (" << WorldMap::current()->solved_level_count()
172           << "/" << WorldMap::current()->level_count() << ")";
173     writer.write_string("title", title.str());
174   }
175
176   writer.start_list("tux");
177   player_status->write(writer);
178   writer.end_list("tux");
179
180   writer.start_list("state");
181
182   sq_pushroottable(global_vm);
183   sq_pushstring(global_vm, "state", -1);
184   if(SQ_SUCCEEDED(sq_get(global_vm, -2))) {
185     Scripting::save_squirrel_table(global_vm, -1, writer);
186     sq_pop(global_vm, 1);
187   }
188   sq_pop(global_vm, 1);
189   writer.end_list("state");
190
191   writer.end_list("supertux-savegame");
192 }
193
194 void
195 World::load_state()
196 {
197   using namespace Scripting;
198
199   try {
200     lisp::Parser parser;
201     std::auto_ptr<lisp::Lisp> root (parser.parse(savegame_filename));
202
203     const lisp::Lisp* lisp = root->get_lisp("supertux-savegame");
204     if(lisp == NULL)
205       throw std::runtime_error("file is not a supertux-savegame file");
206
207     int version = 1;
208     lisp->get("version", version);
209     if(version != 1)
210       throw std::runtime_error("incompatible savegame version");
211
212     const lisp::Lisp* tux = lisp->get_lisp("tux");
213     if(tux == NULL)
214       throw std::runtime_error("No tux section in savegame");
215     player_status->read(*tux);
216
217     const lisp::Lisp* state = lisp->get_lisp("state");
218     if(state == NULL)
219       throw std::runtime_error("No state section in savegame");
220
221     sq_pushroottable(global_vm);
222     sq_pushstring(global_vm, "state", -1);
223     if(SQ_FAILED(sq_deleteslot(global_vm, -2, SQFalse)))
224       sq_pop(global_vm, 1);
225
226     sq_pushstring(global_vm, "state", -1);
227     sq_newtable(global_vm);
228     load_squirrel_table(global_vm, -1, state);
229     if(SQ_FAILED(sq_createslot(global_vm, -3)))
230       throw std::runtime_error("Couldn't create state table");
231     sq_pop(global_vm, 1);
232   } catch(std::exception& e) {
233     log_debug << "Couldn't load savegame: " << e.what() << std::endl;
234   }
235 }
236
237 const std::string&
238 World::get_level_filename(unsigned int i) const
239 {
240   return levels[i];
241 }
242
243 unsigned int
244 World::get_num_levels() const
245 {
246   return levels.size();
247 }
248
249 const std::string&
250 World::get_basedir() const
251 {
252   return basedir;
253 }