New grow and skid sounds from remaxim
[supertux.git] / src / world.cpp
index 2eca737..a5ec785 100644 (file)
@@ -1,9 +1,7 @@
 //  $Id$
-// 
+//
 //  SuperTux
-//  Copyright (C) 2000 Bill Kendrick <bill@newbreedsoftware.com>
-//  Copyright (C) 2004 Tobias Glaesser <tobi.web@gmx.de>
-//  Copyright (C) 2004 Ingo Ruhnke <grumbel@gmx.de>
+//  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
 //
 //  This program is free software; you can redistribute it and/or
 //  modify it under the terms of the GNU General Public License
 //  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, write to the Free Software
 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 //  02111-1307, USA.
-
-#include <iostream>
-#include <math.h>
-#include <stdlib.h>
-#include <string.h>
-#include "globals.h"
-#include "scene.h"
-#include "screen.h"
-#include "defines.h"
-#include "world.h"
-#include "level.h"
-#include "tile.h"
-#include "resources.h"
-#include "gameobjs.h"
-
-Surface* img_distro[4];
-
-World* World::current_ = 0;
-
-World::World(const std::string& filename)
-{
-  // FIXME: Move this to action and draw and everywhere else where the
-  // world calls child functions
-  current_ = this;
-
-  level = new Level(filename);
-  tux.init();
-
-  set_defaults();
-
-  get_level()->load_gfx();
-  activate_bad_guys();
-  activate_objects();
-  activate_particle_systems();
-  get_level()->load_song();
-
-  apply_bonuses();
-
-  scrolling_timer.init(true);
+#include <config.h>
+
+#include <stddef.h>
+#include <physfs.h>
+#include <stdexcept>
+
+#include "world.hpp"
+#include "file_system.hpp"
+#include "lisp/parser.hpp"
+#include "lisp/lisp.hpp"
+#include "physfs/physfs_stream.hpp"
+#include "scripting/squirrel_util.hpp"
+#include "scripting/serialize.hpp"
+#include "log.hpp"
+#include "worldmap/worldmap.hpp"
+#include "mainloop.hpp"
+#include "player_status.hpp"
+
+static bool has_suffix(const std::string& data, const std::string& suffix)
+{
+  if (data.length() >= suffix.length())
+    return data.compare(data.length() - suffix.length(), suffix.length(), suffix) == 0;
+  else
+    return false;
 }
 
-World::World(const std::string& subset, int level_nr)
-{
-  // FIXME: Move this to action and draw and everywhere else where the
-  // world calls child functions
-  current_ = this;
-
-  level = new Level(subset, level_nr);
-  tux.init();
+World* World::current_ = NULL;
 
-  set_defaults();
-
-  get_level()->load_gfx();
-  activate_bad_guys();
-  activate_objects();
-  activate_particle_systems();
-  get_level()->load_song();
-
-  apply_bonuses();
-
-  scrolling_timer.init(true);
-}
-
-void
-World::apply_bonuses()
+World::World()
 {
-  // Apply bonuses from former levels
-  switch (player_status.bonus)
-    {
-    case PlayerStatus::NO_BONUS:
-      break;
-
-    case PlayerStatus::FLOWER_BONUS:
-      tux.got_power = tux.FIRE_POWER;  // FIXME: add ice power to here
-      // fall through
-
-    case PlayerStatus::GROWUP_BONUS:
-      // FIXME: Move this to Player class
-      tux.size = BIG;
-      tux.base.height = 64;
-      tux.base.y -= 32;
-      break;
-    }
+  is_levelset = true;
+  hide_from_contribs = false;
+  sq_resetobject(&world_thread);
 }
 
 World::~World()
 {
-  for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
-    delete *i;
-
-  for (Trampolines::iterator i = trampolines.begin(); i != trampolines.end(); ++i)
-    delete *i;
-
-  for (ParticleSystems::iterator i = particle_systems.begin();
-          i != particle_systems.end(); ++i)
-    delete *i;
-
-  for (std::vector<BouncyDistro*>::iterator i = bouncy_distros.begin();
-       i != bouncy_distros.end(); ++i)
-    delete *i;
-  
-  for (std::vector<BrokenBrick*>::iterator i = broken_bricks.begin();
-       i != broken_bricks.end(); ++i)
-    delete *i;
-  
-  for (std::vector<BouncyBrick*>::iterator i = bouncy_bricks.begin();
-       i != bouncy_bricks.end(); ++i)
-    delete *i;
-
-  for (std::vector<FloatingScore*>::iterator i = floating_scores.begin();
-       i != floating_scores.end(); ++i)
-    delete *i;
-  
-  delete level;
+  sq_release(Scripting::global_vm, &world_thread);
+  if(current_ == this)
+    current_ = NULL;
 }
 
 void
-World::set_defaults()
+World::set_savegame_filename(const std::string& filename)
 {
-  // Set defaults: 
-  scroll_x = 0;
-
-  player_status.score_multiplier = 1;
-
-  counting_distros = false;
-  distro_counter = 0;
-
-  /* set current song/music */
-  currentmusic = LEVEL_MUSIC;
-}
-
-void
-World::activate_bad_guys()
-{
-  for (std::vector<BadGuyData>::iterator i = level->badguy_data.begin();
-       i != level->badguy_data.end();
-       ++i)
-    {
-      add_bad_guy(i->x, i->y, i->kind, i->stay_on_platform);
-    }
-}
-
-void
-World::activate_objects()
-{
-  for (std::vector< ObjectData<TrampolineData> >::iterator i = level->trampoline_data.begin();
-       i != level->trampoline_data.end();
-       ++i)
-  {
-    add_object<Trampoline, ObjectData<TrampolineData> >(*i);
+  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());
+      }
   }
-}
 
-void
-World::activate_particle_systems()
-{
-  if (level->particle_system == "clouds")
-    {
-      particle_systems.push_back(new CloudParticleSystem);
-    }
-  else if (level->particle_system == "snow")
-    {
-      particle_systems.push_back(new SnowParticleSystem);
-    }
-  else if (level->particle_system != "")
-    {
-      st_abort("unknown particle system specified in level", "");
-    }
-}
-
-void
-World::draw()
-{
-  int y,x;
-
-  /* Draw the real background */
-  drawgradient(level->bkgd_top, level->bkgd_bottom);
-  if(level->img_bkgd)
-      level->draw_bg();
-
-    
-  /* Draw particle systems (background) */
-  std::vector<ParticleSystem*>::iterator p;
-  for(p = particle_systems.begin(); p != particle_systems.end(); ++p)
-    {
-      (*p)->draw(scroll_x, 0, 0);
-    }
-
-  /* Draw background: */
-  for (y = 0; y < VISIBLE_TILES_Y && y < level->height; ++y)
-    {
-      for (x = 0; x < VISIBLE_TILES_X; ++x)
-        {
-          Tile::draw(32*x - fmodf(scroll_x, 32), y * 32 - fmodf(scroll_y, 32),
-                     level->bg_tiles[(int)y + (int)(scroll_y / 32)][(int)x + (int)(scroll_x / 32)]);
-        }
-    }
-
-  /* Draw interactive tiles: */
-  for (y = 0; y < VISIBLE_TILES_Y && y < level->height; ++y)
-    {
-      for (x = 0; x < VISIBLE_TILES_X; ++x)
-        {
-          Tile::draw(32*x - fmodf(scroll_x, 32), y * 32 - fmodf(scroll_y, 32),
-                     level->ia_tiles[(int)y + (int)(scroll_y / 32)][(int)x + (int)(scroll_x / 32)]);
-        }
-    }
-
-  /* (Bouncy bricks): */
-  for (unsigned int i = 0; i < bouncy_bricks.size(); ++i)
-    bouncy_bricks[i]->draw();
-
-  for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
-    (*i)->draw();
-
-  for (Trampolines::iterator i = trampolines.begin(); i != trampolines.end(); ++i)
-    (*i)->draw();
-
-  tux.draw();
-
-  for (unsigned int i = 0; i < bullets.size(); ++i)
-    bullets[i].draw();
-
-  for (unsigned int i = 0; i < floating_scores.size(); ++i)
-    floating_scores[i]->draw();
-
-  for (unsigned int i = 0; i < upgrades.size(); ++i)
-    upgrades[i].draw();
-
-  for (unsigned int i = 0; i < bouncy_distros.size(); ++i)
-    bouncy_distros[i]->draw();
-
-  for (unsigned int i = 0; i < broken_bricks.size(); ++i)
-    broken_bricks[i]->draw();
-
-  /* Draw foreground: */
-  for (y = 0; y < VISIBLE_TILES_Y && y < level->height; ++y)
-    {
-      for (x = 0; x < VISIBLE_TILES_X; ++x)
-        {
-          Tile::draw(32*x - fmodf(scroll_x, 32), y * 32 - fmodf(scroll_y, 32),
-                     level->fg_tiles[(int)y + (int)(scroll_y / 32)][(int)x + (int)(scroll_x / 32)]);
-        }
-    }
-
-  /* Draw particle systems (foreground) */
-  for(p = particle_systems.begin(); p != particle_systems.end(); ++p)
-    {
-      (*p)->draw(scroll_x, 0, 1);
-    }
+  if(!PHYSFS_isDirectory(dirname.c_str())) {
+      std::ostringstream msg;
+      msg << "Savegame path '" << dirname << "' is not a directory";
+      throw std::runtime_error(msg.str());
+  }
 }
 
 void
-World::action(double frame_ratio)
+World::load(const std::string& filename)
 {
-  tux.action(frame_ratio);
-  tux.check_bounds(level->back_scrolling, (bool)level->hor_autoscroll_speed);
-  scrolling(frame_ratio);
-
-  /* Handle bouncy distros: */
-  for (unsigned int i = 0; i < bouncy_distros.size(); i++)
-    bouncy_distros[i]->action(frame_ratio);
-
-  /* Handle broken bricks: */
-  for (unsigned int i = 0; i < broken_bricks.size(); i++)
-    broken_bricks[i]->action(frame_ratio);
-
-  // Handle all kinds of game objects
-  for (unsigned int i = 0; i < bouncy_bricks.size(); i++)
-    bouncy_bricks[i]->action(frame_ratio);
-  
-  for (unsigned int i = 0; i < floating_scores.size(); i++)
-    floating_scores[i]->action(frame_ratio);
-
-  for (unsigned int i = 0; i < bullets.size(); ++i)
-    bullets[i].action(frame_ratio);
-  
-  for (unsigned int i = 0; i < upgrades.size(); i++)
-    upgrades[i].action(frame_ratio);
-
-  for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
-    (*i)->action(frame_ratio);
-
-  for (Trampolines::iterator i = trampolines.begin(); i != trampolines.end(); ++i)
-     (*i)->action(frame_ratio);
-
-  /* update particle systems */
-  std::vector<ParticleSystem*>::iterator p;
-  for(p = particle_systems.begin(); p != particle_systems.end(); ++p)
-    {
-      (*p)->simulate(frame_ratio);
-    }
-
-  /* Handle all possible collisions. */
-  collision_handler();
-  
-  // Cleanup marked badguys
-  for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end();
-      /* ++i handled at end of the loop */) {
-    if ((*i)->is_removable()) {
-      delete *i;
-      i =  bad_guys.erase(i);
-    } else {
-      ++i;
-    }
-  }
-}
-
-/* the space that it takes for the screen to start scrolling, regarding */
-/* screen bounds (in pixels) */
-// should be higher than screen->w/2 (400)
-#define X_SPACE (500-16)
-// should be less than screen->h/2 (300)
-#define Y_SPACE 250
-
-// the time it takes to move the camera (in ms)
-#define CHANGE_DIR_SCROLL_SPEED 2000
+  basedir = FileSystem::dirname(filename);
 
-/* This functions takes cares of the scrolling */
-void World::scrolling(double frame_ratio)
-{
-  /* Y-axis scrolling */
+  lisp::Parser parser;
+  const lisp::Lisp* root = parser.parse(filename);
 
-  float tux_pos_y = tux.base.y + (tux.base.height/2);
+  const lisp::Lisp* info = root->get_lisp("supertux-world");
+  if(info == NULL)
+    info = root->get_lisp("supertux-level-subset");
+  if(info == NULL)
+    throw std::runtime_error("File is not a world or levelsubset file");
 
-  if(level->height > VISIBLE_TILES_Y-1 && !tux.dying)
-    {
-    if (scroll_y < tux_pos_y - (screen->h - Y_SPACE))
-      scroll_y = tux_pos_y - (screen->h - Y_SPACE);
-    else if (scroll_y > tux_pos_y - Y_SPACE)
-      scroll_y = tux_pos_y - Y_SPACE;
-    }
+  hide_from_contribs = false;
+  is_levelset = true;
 
-  // this code prevent the screen to scroll before the start or after the level's end
-  if(scroll_y > level->height * 32 - screen->h)
-    scroll_y = level->height * 32 - screen->h;
-  if(scroll_y < 0)
-    scroll_y = 0;
+  info->get("title", title);
+  info->get("description", description);
+  info->get("levelset", is_levelset);
+  info->get("levels", levels);
+  info->get("hide-from-contribs", hide_from_contribs);
 
-  /* X-axis scrolling */
+  // Level info file doesn't define any levels, so read the
+  // directory to see what we can find
 
-  /* Auto scrolling */
-  if(level->hor_autoscroll_speed)
-  {
-    scroll_x += level->hor_autoscroll_speed * frame_ratio;
+  std::string path = basedir;
+  char** files = PHYSFS_enumerateFiles(path.c_str());
+  if(!files) {
+    log_warning << "Couldn't read subset dir '" << path << "'" << std::endl;
     return;
   }
 
-
-  /* Horizontal backscrolling */
-  float tux_pos_x = tux.base.x + (tux.base.width/2);
-
-  if(tux.old_dir != tux.dir && level->back_scrolling)
-    scrolling_timer.start(CHANGE_DIR_SCROLL_SPEED);
-
-  bool right = false;
-  bool left = false;
-  if (tux.physic.get_velocity_x() > 0)
-    right = true;
-  else if (tux.physic.get_velocity_x() < 0)
-    left = true;
-  else
-    {
-    if (tux.dir == RIGHT)
-      right = true;
-    else
-      left = true;
+  for(const char* const* filename = files; *filename != 0; ++filename) {
+    if(has_suffix(*filename, ".stl")) {
+      levels.push_back(path + *filename);
     }
-
-  if(scrolling_timer.check())
-  {
-    float final_scroll_x;
-    if (right)
-      final_scroll_x = tux_pos_x - (screen->w - X_SPACE);
-    else
-      final_scroll_x = tux_pos_x - X_SPACE;
-
-    scroll_x +=   (final_scroll_x - scroll_x)
-                / (frame_ratio * (CHANGE_DIR_SCROLL_SPEED / 100))
-                + (tux.physic.get_velocity_x() * frame_ratio + tux.physic.get_acceleration_x() * frame_ratio * frame_ratio);
-  }
-  else
-  {
-    if (right && scroll_x < tux_pos_x - (screen->w - X_SPACE))
-      scroll_x = tux_pos_x - (screen->w - X_SPACE);
-    else if (left && scroll_x > tux_pos_x - X_SPACE && level->back_scrolling)
-      scroll_x = tux_pos_x - X_SPACE;
   }
-
-  // this code prevent the screen to scroll before the start or after the level's end
-  if(scroll_x > level->width * 32 - screen->w)
-    scroll_x = level->width * 32 - screen->w;
-  if(scroll_x < 0)
-    scroll_x = 0;
+  PHYSFS_freeList(files);
 }
 
 void
-World::collision_handler()
+World::run()
 {
-  // CO_BULLET & CO_BADGUY check
-  for(unsigned int i = 0; i < bullets.size(); ++i)
-    {
-      for (BadGuys::iterator j = bad_guys.begin(); j != bad_guys.end(); ++j)
-        {
-          if((*j)->dying != DYING_NOT)
-            continue;
-          
-          if(rectcollision(bullets[i].base, (*j)->base))
-            {
-              // We have detected a collision and now call the
-              // collision functions of the collided objects.
-              // collide with bad_guy first, since bullet_collision will
-              // delete the bullet
-              (*j)->collision(&bullets[i], CO_BULLET);
-              bullets[i].collision(CO_BADGUY);
-              break; // bullet is invalid now, so break
-            }
-        }
-    }
+  using namespace Scripting;
 
-  /* CO_BADGUY & CO_BADGUY check */
-  for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
-    {
-      if((*i)->dying != DYING_NOT)
-        continue;
-      
-      BadGuys::iterator j = i;
-      ++j;
-      for (; j != bad_guys.end(); ++j)
-        {
-          if(j == i || (*j)->dying != DYING_NOT)
-            continue;
-
-          if(rectcollision((*i)->base, (*j)->base))
-            {
-              // We have detected a collision and now call the
-              // collision functions of the collided objects.
-              (*j)->collision(*i, CO_BADGUY);
-              (*i)->collision(*j, CO_BADGUY);
-            }
-        }
-    }
-
-  if(tux.dying != DYING_NOT) return;
-    
-  // CO_BADGUY & CO_PLAYER check 
-  for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
-    {
-      if((*i)->dying != DYING_NOT)
-        continue;
-      
-      if(rectcollision_offset((*i)->base, tux.base, 0, 0))
-        {
-          // We have detected a collision and now call the collision
-          // functions of the collided objects.
-          if (tux.previous_base.y < tux.base.y &&
-              tux.previous_base.y + tux.previous_base.height 
-              < (*i)->base.y + (*i)->base.height/2
-              && !tux.invincible_timer.started())
-            {
-              (*i)->collision(&tux, CO_PLAYER, COLLISION_SQUISH);
-            }
-          else
-            {
-              tux.collision(*i, CO_BADGUY);
-              (*i)->collision(&tux, CO_PLAYER, COLLISION_NORMAL);
-            }
-        }
-    }
-
-  // CO_UPGRADE & CO_PLAYER check
-  for(unsigned int i = 0; i < upgrades.size(); ++i)
-    {
-      if(rectcollision(upgrades[i].base, tux.base))
-        {
-          // We have detected a collision and now call the collision
-          // functions of the collided objects.
-          upgrades[i].collision(&tux, CO_PLAYER, COLLISION_NORMAL);
-        }
-    }
+  current_ = this;
 
-  // CO_TRAMPOLINE & (CO_PLAYER or CO_BADGUY)
-  for (Trampolines::iterator i = trampolines.begin(); i != trampolines.end(); ++i)
-  {
-    if (rectcollision((*i)->base, tux.base))
-    {
-      if (tux.previous_base.y < tux.base.y &&
-          tux.previous_base.y + tux.previous_base.height 
-          < (*i)->base.y + (*i)->base.height/2)
-      {
-        (*i)->collision(&tux, CO_PLAYER, COLLISION_SQUISH);
-      }
-      else if (tux.previous_base.y <= tux.base.y)
-      {
-        tux.collision(*i, CO_TRAMPOLINE);
-        (*i)->collision(&tux, CO_PLAYER, COLLISION_NORMAL);
-      }
-    }
+  // create new squirrel table for persistent game state
+  HSQUIRRELVM vm = Scripting::global_vm;
+
+  sq_pushroottable(vm);
+  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 {
+    IFileStream in(filename);
+
+    sq_release(global_vm, &world_thread);
+    world_thread = create_thread(global_vm);
+    compile_and_run(object_to_vm(world_thread), in, filename);
+  } catch(std::exception& ) {
+    // fallback: try to load worldmap worldmap.stwm
+    using namespace WorldMapNS;
+    main_loop->push_screen(new WorldMap(basedir + "worldmap.stwm"));
   }
 }
 
 void
-World::add_score(float x, float y, int s)
+World::save_state()
 {
-  player_status.score += s;
+  using namespace Scripting;
 
-  FloatingScore* new_floating_score = new FloatingScore();
-  new_floating_score->init(x-scroll_x, y-scroll_y, s);
-  floating_scores.push_back(new_floating_score);
-}
+  lisp::Writer writer(savegame_filename);
 
-void
-World::add_bouncy_distro(float x, float y)
-{
-  BouncyDistro* new_bouncy_distro = new BouncyDistro();
-  new_bouncy_distro->init(x, y);
-  bouncy_distros.push_back(new_bouncy_distro);
-}
-
-void
-World::add_broken_brick(Tile* tile, float x, float y)
-{
-  add_broken_brick_piece(tile, x, y, -1, -4);
-  add_broken_brick_piece(tile, x, y + 16, -1.5, -3);
+  writer.start_list("supertux-savegame");
+  writer.write("version", 1);
 
-  add_broken_brick_piece(tile, x + 16, y, 1, -4);
-  add_broken_brick_piece(tile, x + 16, y + 16, 1.5, -3);
-}
-
-void
-World::add_broken_brick_piece(Tile* tile, float x, float y, float xm, float ym)
-{
-  BrokenBrick* new_broken_brick = new BrokenBrick();
-  new_broken_brick->init(tile, x, y, xm, ym);
-  broken_bricks.push_back(new_broken_brick);
-}
-
-void
-World::add_bouncy_brick(float x, float y)
-{
-  BouncyBrick* new_bouncy_brick = new BouncyBrick();
-  new_bouncy_brick->init(x,y);
-  bouncy_bricks.push_back(new_bouncy_brick);
-}
+  using namespace WorldMapNS;
+  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());
+  }
 
-BadGuy*
-World::add_bad_guy(float x, float y, BadGuyKind kind, bool stay_on_platform)
-{
-  BadGuy* badguy = new BadGuy(x,y,kind, stay_on_platform);
-  bad_guys.push_back(badguy);
-  return badguy;
-}
+  writer.start_list("tux");
+  player_status->write(writer);
+  writer.end_list("tux");
 
-template<class T, class U>
-T*
-World::add_object(U data)
-{
-  T* tobject = new T(data);
+  writer.start_list("state");
 
-  if (data.type == OBJ_TRAMPOLINE)
-    trampolines.push_back(tobject);
+  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_pop(global_vm, 1);
+  writer.end_list("state");
 
-  return tobject;
+  writer.end_list("supertux-savegame");
 }
 
 void
-World::add_upgrade(float x, float y, Direction dir, UpgradeKind kind)
+World::load_state()
 {
-  Upgrade new_upgrade;
-  new_upgrade.init(x,y,dir,kind);
-  upgrades.push_back(new_upgrade);
-}
+  using namespace Scripting;
 
-void 
-World::add_bullet(float x, float y, float xm, Direction dir)
-{
-  if(tux.got_power == tux.FIRE_POWER)
-    {
-    if(bullets.size() > MAX_FIRE_BULLETS-1)
-      return;
-    }
-  else if(tux.got_power == tux.ICE_POWER)
-    {
-    if(bullets.size() > MAX_ICE_BULLETS-1)
-      return;
-    }
+  try {
+    lisp::Parser parser;
+    const lisp::Lisp* root = parser.parse(savegame_filename);
 
-  Bullet new_bullet;
-  if(tux.got_power == tux.FIRE_POWER)
-    new_bullet.init(x,y,xm,dir, FIRE_BULLET);
-  else if(tux.got_power == tux.ICE_POWER)
-    new_bullet.init(x,y,xm,dir, ICE_BULLET);
-  bullets.push_back(new_bullet);
-  
-  play_sound(sounds[SND_SHOOT], SOUND_CENTER_SPEAKER);
-}
+    const lisp::Lisp* lisp = root->get_lisp("supertux-savegame");
+    if(lisp == NULL)
+      throw std::runtime_error("file is not a supertux-savegame file");
 
-void
-World::play_music(int musictype)
-{
-  currentmusic = musictype;
-  switch(currentmusic) {
-    case HURRYUP_MUSIC:
-      music_manager->play_music(get_level()->get_level_music_fast());
-      break;
-    case LEVEL_MUSIC:
-      music_manager->play_music(get_level()->get_level_music());
-      break;
-    case HERRING_MUSIC:
-      music_manager->play_music(herring_song);
-      break;
-    default:
-      music_manager->halt_music();
-      break;
-  }
-}
+    int version = 1;
+    lisp->get("version", version);
+    if(version != 1)
+      throw std::runtime_error("incompatible savegame version");
 
-int
-World::get_music_type()
-{
-  return currentmusic;
-}
+    const lisp::Lisp* tux = lisp->get_lisp("tux");
+    if(tux == NULL)
+      throw std::runtime_error("No tux section in savegame");
+    player_status->read(*tux);
 
-/* Break a brick: */
-bool
-World::trybreakbrick(float x, float y, bool small)
-{
-  Level* plevel = get_level();
-  
-  Tile* tile = gettile(x, y);
-  if (tile->brick)
-    {
-      if (tile->data > 0)
-        {
-          /* Get a distro from it: */
-          add_bouncy_distro(((int)(x + 1) / 32) * 32,
-                                  (int)(y / 32) * 32);
-
-          // TODO: don't handle this in a global way but per-tile...
-          if (!counting_distros)
-            {
-              counting_distros = true;
-              distro_counter = 5;
-            }
-          else
-            {
-              distro_counter--;
-            }
-
-          if (distro_counter <= 0)
-            {
-              counting_distros = false;
-              plevel->change(x, y, TM_IA, tile->next_tile);
-            }
-
-          play_sound(sounds[SND_DISTRO], SOUND_CENTER_SPEAKER);
-          player_status.score = player_status.score + SCORE_DISTRO;
-          player_status.distros++;
-          return true;
-        }
-      else if (!small)
-        {
-          /* Get rid of it: */
-          plevel->change(x, y, TM_IA, tile->next_tile);
-          
-          /* Replace it with broken bits: */
-          add_broken_brick(tile, 
-                                 ((int)(x + 1) / 32) * 32,
-                                 (int)(y / 32) * 32);
-          
-          /* Get some score: */
-          play_sound(sounds[SND_BRICK], SOUND_CENTER_SPEAKER);
-          player_status.score = player_status.score + SCORE_BRICK;
-          
-          return true;
-        }
-    }
+    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);
 
-  return false;
+    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_debug << "Couldn't load savegame: " << e.what() << std::endl;
+  }
 }
 
-/* Empty a box: */
-void
-World::tryemptybox(float x, float y, Direction col_side)
+const std::string&
+World::get_level_filename(unsigned int i) const
 {
-  Tile* tile = gettile(x,y);
-  if (!tile->fullbox)
-    return;
-
-  // according to the collision side, set the upgrade direction
-  if(col_side == LEFT)
-    col_side = RIGHT;
-  else
-    col_side = LEFT;
-
-  int posx = ((int)(x+1) / 32) * 32;
-  int posy = (int)(y/32) * 32 - 32;
-  switch(tile->data)
-    {
-    case 1: // Box with a distro!
-      add_bouncy_distro(posx, posy);
-      play_sound(sounds[SND_DISTRO], SOUND_CENTER_SPEAKER);
-      player_status.score = player_status.score + SCORE_DISTRO;
-      player_status.distros++;
-      break;
-
-    case 2: // Add a fire flower upgrade!
-      if (tux.size == SMALL)     /* Tux is small, add mints! */
-        add_upgrade(posx, posy, col_side, UPGRADE_GROWUP);
-      else     /* Tux is big, add a fireflower: */
-        add_upgrade(posx, posy, col_side, UPGRADE_FIREFLOWER);
-      play_sound(sounds[SND_UPGRADE], SOUND_CENTER_SPEAKER);
-      break;
-    
-    case 5: // Add an ice flower upgrade!
-      if (tux.size == SMALL)     /* Tux is small, add mints! */
-        add_upgrade(posx, posy, col_side, UPGRADE_GROWUP);
-      else     /* Tux is big, add an iceflower: */
-        add_upgrade(posx, posy, col_side, UPGRADE_ICEFLOWER);
-      play_sound(sounds[SND_UPGRADE], SOUND_CENTER_SPEAKER);
-      break;
-
-    case 3: // Add a golden herring
-      add_upgrade(posx, posy, col_side, UPGRADE_HERRING);
-      break;
-
-    case 4: // Add a 1up extra
-      add_upgrade(posx, posy, col_side, UPGRADE_1UP);
-      break;
-    default:
-      break;
-    }
-
-  /* Empty the box: */
-  level->change(x, y, TM_IA, tile->next_tile);
+  return levels[i];
 }
 
-/* Try to grab a distro: */
-void
-World::trygrabdistro(float x, float y, int bounciness)
+unsigned int
+World::get_num_levels() const
 {
-  Tile* tile = gettile(x, y);
-  if (tile && tile->distro)
-    {
-      level->change(x, y, TM_IA, tile->next_tile);
-      play_sound(sounds[SND_DISTRO], SOUND_CENTER_SPEAKER);
-
-      if (bounciness == BOUNCE)
-        {
-          add_bouncy_distro(((int)(x + 1) / 32) * 32,
-                                  (int)(y / 32) * 32);
-        }
-
-      player_status.score = player_status.score + SCORE_DISTRO;
-      player_status.distros++;
-    }
+  return levels.size();
 }
 
-/* Try to bump a bad guy from below: */
-void
-World::trybumpbadguy(float x, float y)
+const std::string&
+World::get_basedir() const
 {
-  // Bad guys: 
-  for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
-    {
-      if ((*i)->base.x >= x - 32 && (*i)->base.x <= x + 32 &&
-          (*i)->base.y >= y - 16 && (*i)->base.y <= y + 16)
-        {
-          (*i)->collision(&tux, CO_PLAYER, COLLISION_BUMP);
-        }
-    }
-
-  // Upgrades:
-  for (unsigned int i = 0; i < upgrades.size(); i++)
-    {
-      if (upgrades[i].base.height == 32 &&
-          upgrades[i].base.x >= x - 32 && upgrades[i].base.x <= x + 32 &&
-          upgrades[i].base.y >= y - 16 && upgrades[i].base.y <= y + 16)
-        {
-          upgrades[i].collision(&tux, CO_PLAYER, COLLISION_BUMP);
-        }
-    }
+  return basedir;
 }
-
-/* EOF */
-