Merge branch 'feature/savegame'
authorIngo Ruhnke <grumbel@gmail.com>
Mon, 11 Aug 2014 22:22:12 +0000 (00:22 +0200)
committerIngo Ruhnke <grumbel@gmail.com>
Mon, 11 Aug 2014 22:22:12 +0000 (00:22 +0200)
15 files changed:
CMakeLists.txt
src/supertux/game_manager.cpp
src/supertux/game_manager.hpp
src/supertux/menu/contrib_menu.cpp
src/supertux/menu/contrib_world_menu.cpp
src/supertux/menu/main_menu.cpp
src/supertux/title_screen.cpp
src/supertux/title_screen.hpp
src/supertux/world.cpp
src/supertux/world.hpp
src/supertux/world_state.cpp [new file with mode: 0644]
src/supertux/world_state.hpp [new file with mode: 0644]
src/util/currenton.hpp
src/util/file_system.cpp
src/util/file_system.hpp

index c3f7371..41d4a3f 100644 (file)
@@ -258,10 +258,10 @@ IF(CMAKE_COMPILER_IS_GNUCC)
     # temporarily disabled:
     #   -Wsign-conversion -Wfloat-equal -Wconversion -Wundef -Wshadow -Wswitch-default
     #   -Wswitch-enum -Wsign-promo -Wcast-qual -Woverloaded-virtual -Wmissing-format-attribute
-    #   -Wold-style-cast -Wpadded
+    #   -Wold-style-cast -Wpadded -Wabi
     # fails on MinGW:
     #   -ansi
-    SET(SUPERTUX2_EXTRA_WARNING_FLAGS "-fdiagnostics-show-option -pedantic -Wno-long-long -Wcast-align -Wdisabled-optimization -Winit-self -Winvalid-pch -Wlogical-op -Wmissing-include-dirs -Wmissing-noreturn -Wpacked -Wredundant-decls -Wstack-protector -Winline -Wunsafe-loop-optimizations  -Wstrict-overflow=5 -Wformat=2 -Weffc++ -Wabi -Wctor-dtor-privacy -Wstrict-null-sentinel -Wno-unused-parameter")
+    SET(SUPERTUX2_EXTRA_WARNING_FLAGS "-fdiagnostics-show-option -pedantic -Wno-long-long -Wcast-align -Wdisabled-optimization -Winit-self -Winvalid-pch -Wlogical-op -Wmissing-include-dirs -Wmissing-noreturn -Wpacked -Wredundant-decls -Wstack-protector -Winline -Wunsafe-loop-optimizations  -Wstrict-overflow=5 -Wformat=2 -Weffc++ -Wctor-dtor-privacy -Wstrict-null-sentinel -Wno-unused-parameter")
   ENDIF(WARNINGS)
 ENDIF(CMAKE_COMPILER_IS_GNUCC)
 
index d6f02a0..1d20184 100644 (file)
@@ -19,6 +19,8 @@
 #include <sstream>
 
 #include "gui/menu_manager.hpp"
+#include "lisp/lisp.hpp"
+#include "lisp/parser.hpp"
 #include "supertux/game_session.hpp"
 #include "supertux/gameconfig.hpp"
 #include "supertux/globals.hpp"
@@ -43,7 +45,7 @@ GameManager::start_level(std::unique_ptr<World> world, int index)
 {
   m_world = std::move(world);
 
-  std::unique_ptr<Screen> screen(new GameSession(m_world->get_level_filename(index), 
+  std::unique_ptr<Screen> screen(new GameSession(m_world->get_level_filename(index),
                                                  m_world->get_player_status()));
   g_screen_manager->push_screen(std::move(screen));
 }
@@ -55,16 +57,8 @@ GameManager::start_game(std::unique_ptr<World> world)
 
   MenuManager::instance().clear_menu_stack();
 
-  std::string basename = m_world->get_basedir();
-  basename = basename.substr(0, basename.length()-1);
-  std::string worlddirname = FileSystem::basename(basename);
-  std::ostringstream stream;
-  stream << "profile" << g_config->profile << "/" << worlddirname << ".stsg";
-  std::string slotfile = stream.str();
-
   try
   {
-    m_world->set_savegame_filename(slotfile);
     m_world->run();
   }
   catch(std::exception& e)
@@ -73,4 +67,28 @@ GameManager::start_game(std::unique_ptr<World> world)
   }
 }
 
+std::string
+GameManager::get_level_name(const std::string& filename) const
+{
+  try
+  {
+    lisp::Parser parser;
+    const lisp::Lisp* root = parser.parse(filename);
+
+    const lisp::Lisp* level = root->get_lisp("supertux-level");
+    if(!level)
+      return "";
+
+    std::string name;
+    level->get("name", name);
+    return name;
+  }
+  catch(const std::exception& e)
+  {
+    log_warning << "Problem getting name of '" << filename << "': "
+                << e.what() << std::endl;
+    return "";
+  }
+}
+
 /* EOF */
index 2e87896..f349032 100644 (file)
@@ -35,6 +35,8 @@ public:
   void start_game(std::unique_ptr<World> world);
   void start_level(std::unique_ptr<World> world, int index);
 
+  std::string get_level_name(const std::string& levelfile) const;
+
 private:
   GameManager(const GameManager&) = delete;
   GameManager& operator=(const GameManager&) = delete;
index 49d0b5e..e36b1a0 100644 (file)
@@ -50,20 +50,11 @@ ContribMenu::ContribMenu() :
   {
     try
     {
-      std::unique_ptr<World> world (new World);
+      std::unique_ptr<World> world = World::load(*it);
 
-      world->load(*it + "/info");
-
-      if (!world->hide_from_contribs) 
+      if (!world->hide_from_contribs())
       {
-        { // FIXME: yuck, this should be easier
-          std::ostringstream stream;
-          std::string worlddirname = FileSystem::basename(*it);
-          stream << "profile" << g_config->profile << "/" << worlddirname << ".stsg";
-          std::string slotfile = stream.str();
-          world->set_savegame_filename(stream.str());
-          world->load_state();
-        }
+        world->load_state();
 
         std::ostringstream title;
         title << world->get_title() << " (" << world->get_num_solved_levels() << "/" << world->get_num_levels() << ")";
@@ -92,14 +83,14 @@ ContribMenu::check_menu()
   if (index != -1)
   {
     World* world = m_contrib_worlds[index].get();
-    if (!world->is_levelset
+    if (!world->is_levelset())
     {
       // FIXME: not the most elegant of solutions to std::move() the
       // World, but the ContribMenu should get destructed after this,
       // so it might be ok
       GameManager::current()->start_game(std::move(m_contrib_worlds[index]));
     }
-    else 
+    else
     {
       MenuManager::instance().push_menu(std::unique_ptr<Menu>(new ContribWorldMenu(std::move(m_contrib_worlds[index]))));
     }
index c03c543..799e410 100644 (file)
@@ -32,11 +32,11 @@ ContribWorldMenu::ContribWorldMenu(std::unique_ptr<World> world) :
   add_label(m_world->get_title());
   add_hl();
 
-  for (unsigned int i = 0; i < m_world->get_num_levels(); ++i)
+  for (int i = 0; i < m_world->get_num_levels(); ++i)
   {
     /** get level's title */
     std::string filename = m_world->get_level_filename(i);
-    std::string title = TitleScreen::get_level_name(filename);
+    std::string title = GameManager::current()->get_level_name(filename);
     add_entry(i, title);
   }
 
index 61c54fe..dd4195a 100644 (file)
@@ -57,8 +57,7 @@ MainMenu::check_menu()
   {
     case MNID_STARTGAME:
       {
-        std::unique_ptr<World> world(new World);
-        world->load("levels/world1/info");
+        std::unique_ptr<World> world = World::load("levels/world1");
         GameManager::current()->start_game(std::move(world));
       }
       break;
index 62d810e..7b687b1 100644 (file)
@@ -61,27 +61,6 @@ TitleScreen::TitleScreen(PlayerStatus* player_status) :
    );
 }
 
-std::string
-TitleScreen::get_level_name(const std::string& filename)
-{
-  try {
-    lisp::Parser parser;
-    const lisp::Lisp* root = parser.parse(filename);
-
-    const lisp::Lisp* level = root->get_lisp("supertux-level");
-    if(!level)
-      return "";
-
-    std::string name;
-    level->get("name", name);
-    return name;
-  } catch(std::exception& e) {
-    log_warning << "Problem getting name of '" << filename << "': "
-                << e.what() << std::endl;
-    return "";
-  }
-}
-
 void
 TitleScreen::make_tux_jump()
 {
index 69354c1..a749791 100644 (file)
@@ -34,9 +34,6 @@ class World;
 class TitleScreen : public Screen
 {
 public:
-  static std::string get_level_name(const std::string& levelfile);
-
-public:
   TitleScreen(PlayerStatus* player_status);
   virtual ~TitleScreen();
 
index 05470f8..c2063a7 100644 (file)
 #include "physfs/ifile_streambuf.hpp"
 #include "scripting/serialize.hpp"
 #include "scripting/squirrel_util.hpp"
+#include "supertux/gameconfig.hpp"
 #include "supertux/globals.hpp"
+#include "supertux/player_status.hpp"
 #include "supertux/screen_fade.hpp"
 #include "supertux/screen_manager.hpp"
-#include "supertux/player_status.hpp"
 #include "supertux/world.hpp"
+#include "supertux/world_state.hpp"
 #include "util/file_system.hpp"
 #include "util/reader.hpp"
 #include "util/string_util.hpp"
 #include "worldmap/worldmap.hpp"
 
-World* World::current_ = NULL;
-
-World::World() :
-  worldname(),
-  levels(),
-  basedir(),
-  savegame_filename(),
-  state_table(),
-  world_thread(),
-  title(),
-  description(),
-  player_status(),
-  hide_from_contribs(),
-  is_levelset()
+std::unique_ptr<World>
+World::load(const std::string& directory)
 {
-  player_status.reset(new PlayerStatus());
+  std::unique_ptr<World> world(new World);
+
+  world->load_(directory);
+
+  { // generate savegame filename
+    std::string worlddirname = FileSystem::basename(directory);
+    std::ostringstream stream;
+    stream << "profile" << g_config->profile << "/" << worlddirname << ".stsg";
+    std::string slotfile = stream.str();
+    world->m_savegame_filename = stream.str();
+  }
 
-  is_levelset = true;
-  hide_from_contribs = false;
-  sq_resetobject(&world_thread);
+  return std::move(world);
 }
 
-World::~World()
+World::World() :
+  m_levels(),
+  m_basedir(),
+  m_worldmap_filename(),
+  m_savegame_filename(),
+  m_world_thread(),
+  m_title(),
+  m_description(),
+  m_world_state(new WorldState),
+  m_hide_from_contribs(false),
+  m_is_levelset(true)
 {
-  sq_release(scripting::global_vm, &world_thread);
-  if(current_ == this)
-    current_ = NULL;
+  sq_resetobject(&m_world_thread);
 }
 
-void
-World::set_savegame_filename(const std::string& filename)
+World::~World()
 {
-  this->savegame_filename = filename;
-  // make sure the savegame directory exists
-  std::string dirname = FileSystem::dirname(filename);
-  if(!PHYSFS_exists(dirname.c_str())) {
-    if(!PHYSFS_mkdir(dirname.c_str())) {
-      std::ostringstream msg;
-      msg << "Couldn't create directory for savegames '"
-          << dirname << "': " <<PHYSFS_getLastError();
-      throw std::runtime_error(msg.str());
-    }
-  }
-
-  if(!PHYSFS_isDirectory(dirname.c_str())) {
-    std::ostringstream msg;
-    msg << "Savegame path '" << dirname << "' is not a directory";
-    throw std::runtime_error(msg.str());
-  }
+  sq_release(scripting::global_vm, &m_world_thread);
 }
 
 void
-World::load(const std::string& filename)
+World::load_(const std::string& directory)
 {
-  basedir = FileSystem::dirname(filename);
-  worldname = basedir + "worldmap.stwm";
+  m_basedir = directory;
+  m_worldmap_filename = m_basedir + "/worldmap.stwm";
 
   lisp::Parser parser;
-  const lisp::Lisp* root = parser.parse(filename);
+  const lisp::Lisp* root = parser.parse(m_basedir + "/info");
 
   const lisp::Lisp* info = root->get_lisp("supertux-world");
   if(info == NULL)
@@ -97,46 +86,39 @@ World::load(const std::string& filename)
   if(info == NULL)
     throw std::runtime_error("File is not a world or levelsubset file");
 
-  hide_from_contribs = false;
-  is_levelset = true;
+  m_hide_from_contribs = false;
+  m_is_levelset = true;
 
-  info->get("title", title);
-  info->get("description", description);
-  info->get("levelset", is_levelset);
-  info->get("hide-from-contribs", hide_from_contribs);
+  info->get("title", m_title);
+  info->get("description", m_description);
+  info->get("levelset", m_is_levelset);
+  info->get("hide-from-contribs", m_hide_from_contribs);
 
   // Level info file doesn't define any levels, so read the
   // directory to see what we can find
 
-  std::string path = basedir;
-  char** files = PHYSFS_enumerateFiles(path.c_str());
-  if(!files) {
-    log_warning << "Couldn't read subset dir '" << path << "'" << std::endl;
+  char** files = PHYSFS_enumerateFiles(m_basedir.c_str());
+  if(!files)
+  {
+    log_warning << "Couldn't read subset dir '" << m_basedir << "'" << std::endl;
     return;
   }
 
-  for(const char* const* filename = files; *filename != 0; ++filename) {
-    if(StringUtil::has_suffix(*filename, ".stl")) {
-      Level level;
-      level.fullpath = path + *filename;
-      level.name = *filename;
-      levels.push_back(level);
+  for(const char* const* filename = files; *filename != 0; ++filename)
+  {
+    if(StringUtil::has_suffix(*filename, ".stl"))
+    {
+      m_levels.push_back(*filename);
     }
   }
   PHYSFS_freeList(files);
 
-  std::sort(levels.begin(), levels.end(),
-            [](const Level& lhs, const Level& rhs)
-            {
-              return StringUtil::numeric_less(lhs.fullpath, rhs.fullpath);
-            });
+  std::sort(m_levels.begin(), m_levels.end(), StringUtil::numeric_less);
 }
 
 void
 World::run()
 {
-  current_ = this;
-
   // create new squirrel table for persistent game state
   HSQUIRRELVM vm = scripting::global_vm;
 
@@ -144,123 +126,57 @@ World::run()
   sq_pushstring(vm, "state", -1);
   sq_newtable(vm);
   if(SQ_FAILED(sq_createslot(vm, -3)))
+  {
     throw scripting::SquirrelError(vm, "Couldn't create state table");
-  sq_pop(vm, 1);
-
-  load_state();
-
-  std::string filename = basedir + "/world.nut";
-  try {
-    IFileStreambuf ins(filename);
-    std::istream in(&ins);
-
-    sq_release(scripting::global_vm, &world_thread);
-    world_thread = scripting::create_thread(scripting::global_vm);
-    scripting::compile_and_run(scripting::object_to_vm(world_thread), in, filename);
-  } catch(std::exception& ) {
-    // fallback: try to load worldmap worldmap.stwm
-    using namespace worldmap;
-    g_screen_manager->push_screen(std::unique_ptr<Screen>(new WorldMap(basedir + "worldmap.stwm", get_player_status())));
-  }
-}
-
-void
-World::save_state()
-{
-  using namespace scripting;
-
-  lisp::Writer writer(savegame_filename);
-
-  writer.start_list("supertux-savegame");
-  writer.write("version", 1);
-
-  using namespace worldmap;
-  if(WorldMap::current() != NULL) {
-    std::ostringstream title;
-    title << WorldMap::current()->get_title();
-    title << " (" << WorldMap::current()->solved_level_count()
-          << "/" << WorldMap::current()->level_count() << ")";
-    writer.write("title", title.str());
   }
+  else
+  {
+    sq_pop(vm, 1);
 
-  writer.start_list("tux");
-  player_status->write(writer);
-  writer.end_list("tux");
+    load_state();
 
-  writer.start_list("state");
+    std::string filename = m_basedir + "/world.nut";
+    try
+    {
+      IFileStreambuf ins(filename);
+      std::istream in(&ins);
 
-  sq_pushroottable(global_vm);
-  sq_pushstring(global_vm, "state", -1);
-  if(SQ_SUCCEEDED(sq_get(global_vm, -2))) {
-    scripting::save_squirrel_table(global_vm, -1, writer);
-    sq_pop(global_vm, 1);
+      sq_release(scripting::global_vm, &m_world_thread);
+      m_world_thread = scripting::create_thread(scripting::global_vm);
+      scripting::compile_and_run(scripting::object_to_vm(m_world_thread), in, filename);
+    }
+    catch(const std::exception& )
+    {
+      // fallback: try to load worldmap worldmap.stwm
+      g_screen_manager->push_screen(std::unique_ptr<Screen>(
+                                      new worldmap::WorldMap(m_worldmap_filename,
+                                                             get_player_status())));
+    }
   }
-  sq_pop(global_vm, 1);
-  writer.end_list("state");
+}
 
-  writer.end_list("supertux-savegame");
+void
+World::save_state()
+{
+  m_world_state->save(m_savegame_filename);
 }
 
 void
 World::load_state()
 {
-  using namespace scripting;
-
-  if(!PHYSFS_exists(savegame_filename.c_str()))
-  {
-    log_info << savegame_filename << ": doesn't exist, not loading state" << std::endl;
-  }
-  else
-  {
-    try {
-      lisp::Parser parser;
-      const lisp::Lisp* root = parser.parse(savegame_filename);
-
-      const lisp::Lisp* lisp = root->get_lisp("supertux-savegame");
-      if(lisp == NULL)
-        throw std::runtime_error("file is not a supertux-savegame file");
-
-      int version = 1;
-      lisp->get("version", version);
-      if(version != 1)
-        throw std::runtime_error("incompatible savegame version");
-
-      const lisp::Lisp* tux = lisp->get_lisp("tux");
-      if(tux == NULL)
-        throw std::runtime_error("No tux section in savegame");
-      player_status->read(*tux);
-
-      const lisp::Lisp* state = lisp->get_lisp("state");
-      if(state == NULL)
-        throw std::runtime_error("No state section in savegame");
-
-      sq_pushroottable(global_vm);
-      sq_pushstring(global_vm, "state", -1);
-      if(SQ_FAILED(sq_deleteslot(global_vm, -2, SQFalse)))
-        sq_pop(global_vm, 1);
-
-      sq_pushstring(global_vm, "state", -1);
-      sq_newtable(global_vm);
-      load_squirrel_table(global_vm, -1, *state);
-      if(SQ_FAILED(sq_createslot(global_vm, -3)))
-        throw std::runtime_error("Couldn't create state table");
-      sq_pop(global_vm, 1);
-    } catch(std::exception& e) {
-      log_fatal << "Couldn't load savegame: " << e.what() << std::endl;
-    }
-  }
+  m_world_state->load(m_savegame_filename);
 }
 
-const std::string&
+std::string
 World::get_level_filename(unsigned int i) const
 {
-  return levels[i].fullpath;
+  return FileSystem::join(m_basedir, m_levels[i]);
 }
 
-unsigned int
+int
 World::get_num_levels() const
 {
-  return levels.size();
+  return static_cast<int>(m_levels.size());
 }
 
 int
@@ -286,27 +202,27 @@ World::get_num_solved_levels() const
     }
     else
     {
-      sq_pushstring(vm, worldname.c_str(), -1);
+      sq_pushstring(vm, m_worldmap_filename.c_str(), -1);
       if(SQ_FAILED(sq_get(vm, -2)))
       {
-        log_warning << "failed to get state.worlds['" << worldname << "']" << std::endl;
+        log_warning << "failed to get state.worlds['" << m_worldmap_filename << "']" << std::endl;
       }
       else
       {
         sq_pushstring(vm, "levels", -1);
         if(SQ_FAILED(sq_get(vm, -2)))
         {
-          log_warning << "failed to get state.worlds['" << worldname << "'].levels" << std::endl;
+          log_warning << "failed to get state.worlds['" << m_worldmap_filename << "'].levels" << std::endl;
         }
         else
         {
-          for(auto level : levels)
+          for(auto level : m_levels)
           {
-            sq_pushstring(vm, level.name.c_str(), -1);
+            sq_pushstring(vm, level.c_str(), -1);
             if(SQ_FAILED(sq_get(vm, -2)))
             {
-              log_warning << "failed to get state.worlds['" << worldname << "'].levels['"
-                          << level.name << "']" << std::endl;
+              log_warning << "failed to get state.worlds['" << m_worldmap_filename << "'].levels['"
+                          << level << "']" << std::endl;
             }
             else
             {
@@ -328,16 +244,16 @@ World::get_num_solved_levels() const
   return num_solved_levels;
 }
 
-const std::string&
+std::string
 World::get_basedir() const
 {
-  return basedir;
+  return m_basedir;
 }
 
-const std::string&
+std::string
 World::get_title() const
 {
-  return title;
+  return m_title;
 }
 
 /* EOF */
index 4200ed9..a952640 100644 (file)
 #ifndef HEADER_SUPERTUX_SUPERTUX_WORLD_HPP
 #define HEADER_SUPERTUX_SUPERTUX_WORLD_HPP
 
+#include <memory>
 #include <squirrel.h>
 #include <string>
 #include <vector>
 
-class PlayerStatus;
+#include "util/currenton.hpp"
+#include "supertux/world_state.hpp"
 
-class World
+class World : public Currenton<World>
 {
+private:
+  World();
+
+  void load_(const std::string& directory);
+
 public:
-  static World* current()
-  {
-    return current_;
-  }
+  /**
+      Load a World
 
-private:
-  static World* current_;
+      @param directory  Directory containing the info file, e.g. "levels/world1"
+  */
+  static std::unique_ptr<World> load(const std::string& directory);
 
 public:
-  World();
   ~World();
 
-  void set_savegame_filename(const std::string& filename);
-  void load(const std::string& filename);
-
   void save_state();
   void load_state();
 
-  unsigned int get_num_levels() const;
+  int get_num_levels() const;
   int get_num_solved_levels() const;
 
-  const std::string& get_level_filename(unsigned int i) const;
-  const std::string& get_basedir() const;
-  const std::string& get_title() const;
-  /** returns player status */
-  PlayerStatus* get_player_status() const { return player_status.get(); }
+  std::string get_level_filename(unsigned int i) const;
+  std::string get_basedir() const;
+  std::string get_title() const;
+
+  PlayerStatus* get_player_status() const { return m_world_state->get_player_status(); }
 
   void run();
 
-private:
-  std::string worldname;
-  struct Level
-  {
-    Level() : fullpath(), name() {}
-    std::string fullpath;
-    std::string name;
-  };
-
-  std::vector<Level> levels;
-  std::string basedir;
-  std::string savegame_filename;
-  /// squirrel table that saves persistent state (about the world)
-  HSQOBJECT state_table;
-  HSQOBJECT world_thread;
-  std::string title;
-  std::string description;
-  std::unique_ptr<PlayerStatus> player_status;
+  bool hide_from_contribs() const { return m_hide_from_contribs; }
+  bool is_levelset() const { return m_is_levelset; }
 
-public:
-  bool hide_from_contribs;
-  bool is_levelset;
+private:
+  std::vector<std::string> m_levels;
+  std::string m_basedir;
+  std::string m_worldmap_filename;
+  std::string m_savegame_filename;
+  HSQOBJECT m_world_thread;
+  std::string m_title;
+  std::string m_description;
+  std::unique_ptr<WorldState> m_world_state;
+
+  bool m_hide_from_contribs;
+  bool m_is_levelset;
 
 private:
   World(const World&) = delete;
diff --git a/src/supertux/world_state.cpp b/src/supertux/world_state.cpp
new file mode 100644 (file)
index 0000000..d0dbd8b
--- /dev/null
@@ -0,0 +1,165 @@
+//  SuperTux
+//  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
+//                2014 Ingo Ruhnke <grumbel@gmx.de>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "supertux/world_state.hpp"
+
+#include "lisp/lisp.hpp"
+#include "lisp/parser.hpp"
+#include "lisp/writer.hpp"
+#include "physfs/ifile_streambuf.hpp"
+#include "scripting/serialize.hpp"
+#include "scripting/squirrel_util.hpp"
+#include "supertux/player_status.hpp"
+#include "util/file_system.hpp"
+#include "util/log.hpp"
+#include "worldmap/worldmap.hpp"
+
+WorldState::WorldState() :
+  m_player_status(new PlayerStatus)
+{
+}
+
+void
+WorldState::load(const std::string& filename)
+{
+  if(!PHYSFS_exists(filename.c_str()))
+  {
+    log_info << filename << ": doesn't exist, not loading state" << std::endl;
+  }
+  else
+  {
+    try
+    {
+      HSQUIRRELVM vm = scripting::global_vm;
+
+      lisp::Parser parser;
+      const lisp::Lisp* root = parser.parse(filename);
+
+      const lisp::Lisp* lisp = root->get_lisp("supertux-savegame");
+      if(lisp == NULL)
+      {
+        throw std::runtime_error("file is not a supertux-savegame file");
+      }
+      else
+      {
+        int version = 1;
+        lisp->get("version", version);
+        if(version != 1)
+        {
+          throw std::runtime_error("incompatible savegame version");
+        }
+        else
+        {
+          const lisp::Lisp* tux = lisp->get_lisp("tux");
+          if(tux == NULL)
+          {
+            throw std::runtime_error("No tux section in savegame");
+          }
+          {
+            m_player_status->read(*tux);
+          }
+
+          const lisp::Lisp* state = lisp->get_lisp("state");
+          if(state == NULL)
+          {
+            throw std::runtime_error("No state section in savegame");
+          }
+          else
+          {
+            sq_pushroottable(vm);
+            sq_pushstring(vm, "state", -1);
+            if(SQ_FAILED(sq_deleteslot(vm, -2, SQFalse)))
+              sq_pop(vm, 1);
+
+            sq_pushstring(vm, "state", -1);
+            sq_newtable(vm);
+            scripting::load_squirrel_table(vm, -1, *state);
+            if(SQ_FAILED(sq_createslot(vm, -3)))
+              throw std::runtime_error("Couldn't create state table");
+            sq_pop(vm, 1);
+          }
+        }
+      }
+    }
+    catch(const std::exception& e)
+    {
+      log_fatal << "Couldn't load savegame: " << e.what() << std::endl;
+    }
+  }
+}
+
+void
+WorldState::save(const std::string& filename)
+{
+  { // make sure the savegame directory exists
+    std::string dirname = FileSystem::dirname(filename);
+    if(!PHYSFS_exists(dirname.c_str()))
+    {
+      if(!PHYSFS_mkdir(dirname.c_str()))
+      {
+        std::ostringstream msg;
+        msg << "Couldn't create directory for savegames '"
+            << dirname << "': " <<PHYSFS_getLastError();
+        throw std::runtime_error(msg.str());
+      }
+    }
+
+    if(!PHYSFS_isDirectory(dirname.c_str()))
+    {
+      std::ostringstream msg;
+      msg << "Savegame path '" << dirname << "' is not a directory";
+      throw std::runtime_error(msg.str());
+    }
+  }
+
+  HSQUIRRELVM vm = scripting::global_vm;
+
+  lisp::Writer writer(filename);
+
+  writer.start_list("supertux-savegame");
+  writer.write("version", 1);
+
+  using namespace worldmap;
+  if(WorldMap::current() != NULL)
+  {
+    std::ostringstream title;
+    title << WorldMap::current()->get_title();
+    title << " (" << WorldMap::current()->solved_level_count()
+          << "/" << WorldMap::current()->level_count() << ")";
+    writer.write("title", title.str());
+  }
+
+  writer.start_list("tux");
+  m_player_status->write(writer);
+  writer.end_list("tux");
+
+  writer.start_list("state");
+
+  sq_pushroottable(vm);
+  sq_pushstring(vm, "state", -1);
+  if(SQ_SUCCEEDED(sq_get(vm, -2)))
+  {
+    scripting::save_squirrel_table(vm, -1, writer);
+    sq_pop(vm, 1);
+  }
+  sq_pop(vm, 1);
+  writer.end_list("state");
+
+  writer.end_list("supertux-savegame");
+}
+
+/* EOF */
diff --git a/src/supertux/world_state.hpp b/src/supertux/world_state.hpp
new file mode 100644 (file)
index 0000000..a3d2c3e
--- /dev/null
@@ -0,0 +1,46 @@
+//  SuperTux
+//  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
+//                2014 Ingo Ruhnke <grumbel@gmx.de>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef HEADER_SUPERTUX_SUPERTUX_WORLD_STATE_HPP
+#define HEADER_SUPERTUX_SUPERTUX_WORLD_STATE_HPP
+
+#include <string>
+#include <memory>
+
+class PlayerStatus;
+
+class WorldState
+{
+private:
+  std::unique_ptr<PlayerStatus> m_player_status;
+
+public:
+  WorldState();
+
+  PlayerStatus* get_player_status() const { return m_player_status.get(); }
+
+  void save(const std::string& filename);
+  void load(const std::string& filename);
+
+private:
+  WorldState(const WorldState&) = delete;
+  WorldState& operator=(const WorldState&) = delete;
+};
+
+#endif
+
+/* EOF */
index ef8d564..fdd8995 100644 (file)
@@ -6,12 +6,12 @@
 **  it under the terms of the GNU General Public License as published by
 **  the Free Software Foundation, either version 3 of the License, or
 **  (at your option) any later version.
-**  
+**
 **  This program is distributed in the hope that it will be useful,
 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 **  GNU General Public License for more details.
-**  
+**
 **  You should have received a copy of the GNU General Public License
 **  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
@@ -21,7 +21,7 @@
 
 #include <assert.h>
 
-/** 
+/**
  *   A 'Currenton' allows access to the currently active instance of a
  *   class via the static current() function. It is kind of like a
  *   singleton, but without handling the object construction itself or
@@ -32,22 +32,25 @@ template<class C>
 class Currenton
 {
 private:
-  static C* s_current; 
+  static C* s_current;
 
 protected:
-  Currenton()  
-  { 
+  Currenton()
+  {
     // FIXME: temporarly disabled, as Sector() for the main menu,
     // doesn't get cleaned up before a real Sector() starts
-    // assert(!s_current); 
-    s_current = static_cast<C*>(this); 
+    // assert(!s_current);
+    s_current = static_cast<C*>(this);
   }
 
   virtual ~Currenton()
   {
-    s_current = 0; 
+    if (s_current == this)
+    {
+      s_current = 0;
+    }
   }
-  
+
 public:
   static C* current() { return s_current; }
 };
index a89b1b4..524d4a5 100644 (file)
@@ -103,6 +103,22 @@ std::string normalize(const std::string& filename)
   return result.str();
 }
 
+std::string join(const std::string& lhs, const std::string& rhs)
+{
+  if (lhs.empty())
+  {
+    return rhs;
+  }
+  else if (lhs.back() == '/')
+  {
+    return lhs + rhs;
+  }
+  else
+  {
+    return lhs + "/" + rhs;
+  }
+}
+
 } // namespace FileSystem
 
 /* EOF */
index 5cdda27..0e97835 100644 (file)
@@ -40,6 +40,11 @@ std::string strip_extension(const std::string& filename);
  */
 std::string normalize(const std::string& filename);
 
+/**
+ * join two filenames join("foo", "bar") -> "foo/bar"
+ */
+std::string join(const std::string& lhs, const std::string& rhs);
+
 } // namespace FileSystem
 
 #endif