hopefully fixed the crash on exit, keep sectors script bundled in the sector and...
[supertux.git] / src / world.cpp
index 8cdee76..bdd4407 100644 (file)
@@ -1,9 +1,7 @@
-//  $Id$
+//  $Id: level_subset.cpp 3118 2006-03-25 17:29:08Z sommer $
 // 
 //  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
 //  along with this program; if not, write to the Free Software
 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 //  02111-1307, USA.
-
-#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"
-
-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_particle_systems();
-  get_level()->load_song();
+#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 "script_manager.hpp"
+#include "scripting/wrapper_util.hpp"
+#include "scripting/serialize.hpp"
+#include "msg.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)
+World::World()
 {
-  // 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();
-
-  set_defaults();
-
-  get_level()->load_gfx();
-  activate_bad_guys();
-  activate_particle_systems();
-  get_level()->load_song();
+  is_levelset = true;
+  hide_from_contribs = false;
 }
 
 World::~World()
 {
-  halt_music(); // just to be sure (because levelmusic is freed now)
-  delete level;
-}
-
-void
-World::set_defaults()
-{
-  // 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_particle_systems()
+World::set_savegame_filename(const std::string& filename)
 {
-  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", "");
-    }
+  this->savegame_filename = filename;
 }
 
 void
-World::draw()
+World::load(const std::string& filename)
 {
-  int y,x;
-
-  /* Draw the real background */
-  if(get_level()->bkgd_image[0] != '\0')
-    {
-      int s = ((int)scroll_x / 2)%640;
-      level->img_bkgd->draw_part(s, 0,0,0,level->img_bkgd->w - s, level->img_bkgd->h);
-      level->img_bkgd->draw_part(0, 0,screen->w - s ,0,s,level->img_bkgd->h);
-    }
-  else
-    {
-      drawgradient(level->bkgd_top, level->bkgd_bottom);
-    }
-    
-  /* 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 < 15; ++y)
-    {
-      for (x = 0; x < 21; ++x)
-        {
-          Tile::draw(32*x - fmodf(scroll_x, 32), y * 32,
-                     level->bg_tiles[(int)y][(int)x + (int)(scroll_x / 32)]);
-        }
-    }
-
-  /* Draw interactive tiles: */
-  for (y = 0; y < 15; ++y)
-    {
-      for (x = 0; x < 21; ++x)
-        {
-          Tile::draw(32*x - fmodf(scroll_x, 32), y * 32,
-                     level->ia_tiles[(int)y][(int)x + (int)(scroll_x / 32)]);
-        }
-    }
-
-  /* (Bouncy bricks): */
-  for (unsigned int i = 0; i < bouncy_bricks.size(); ++i)
-    bouncy_bricks[i].draw();
-
-  for (unsigned int i = 0; i < bad_guys.size(); ++i)
-    bad_guys[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 < 15; ++y)
-    {
-      for (x = 0; x < 21; ++x)
-        {
-          Tile::draw(32*x - fmodf(scroll_x, 32), y * 32,
-                     level->fg_tiles[(int)y][(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);
-    }
-}
-
-void
-World::action(double frame_ratio)
-{
-  tux.action(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 distro counting: */
-  if (counting_distros)
-    {
-      distro_counter--;
-
-      if (distro_counter <= 0)
-        counting_distros = -1;
-    }
-
-  // Handle all kinds of game objects
-  for (unsigned int i = 0; i < bouncy_bricks.size(); i++)
-    bouncy_bricks[i].action(frame_ratio);
+  basedir = FileSystem::dirname(filename);
   
-  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 (unsigned int i = 0; i < bad_guys.size(); i++)
-    bad_guys[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();
-}
-
-
-void
-World::collision_handler()
-{
-  // CO_BULLET & CO_BADGUY check
-  for(unsigned int i = 0; i < bullets.size(); ++i)
-    {
-      for(unsigned int j = 0; j < bad_guys.size(); ++j)
-        {
-          if(bad_guys[j].dying != DYING_NOT)
-            continue;
-          if(rectcollision(&bullets[i].base, &bad_guys[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
-              bad_guys[j].collision(0, CO_BULLET);
-              bullets[i].collision(CO_BADGUY);
-              break; // bullet is invalid now, so break
-            }
-        }
-    }
-
-  /* CO_BADGUY & CO_BADGUY check */
-  for(unsigned int i = 0; i < bad_guys.size(); ++i)
-    {
-      if(bad_guys[i].dying != DYING_NOT)
-        continue;
+  lisp::Parser parser;
+  std::auto_ptr<lisp::Lisp> root (parser.parse(filename));
+
+  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");
+
+  hide_from_contribs = false;
+  is_levelset = true;
+
+  info->get("title", title);
+  info->get("description", description);
+  info->get("levelset", is_levelset);
+  info->get_vector("levels", levels);
+  info->get("hide-from-contribs", hide_from_contribs);
+
+  // Level info file doesn't define any levels, so read the
+  // directory to see what we can find
       
-      for(unsigned int j = i+1; j < bad_guys.size(); ++j)
-        {
-          if(j == i || bad_guys[j].dying != DYING_NOT)
-            continue;
-
-          if(rectcollision(&bad_guys[i].base, &bad_guys[j].base))
-            {
-              // We have detected a collision and now call the
-              // collision functions of the collided objects.
-              bad_guys[j].collision(&bad_guys[i], CO_BADGUY);
-              bad_guys[i].collision(&bad_guys[j], CO_BADGUY);
-            }
-        }
-    }
-
-  if(tux.dying != DYING_NOT) return;
-    
-  // CO_BADGUY & CO_PLAYER check 
-  for(unsigned int i = 0; i < bad_guys.size(); ++i)
-    {
-      if(bad_guys[i].dying != DYING_NOT)
-        continue;
-      
-      if(rectcollision_offset(&bad_guys[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 
-              < bad_guys[i].base.y + bad_guys[i].base.height/2)
-            {
-              bad_guys[i].collision(&tux, CO_PLAYER, COLLISION_SQUISH);
-            }
-          else
-            {
-              tux.collision(&bad_guys[i], CO_BADGUY);
-              bad_guys[i].collision(&tux, CO_PLAYER, COLLISION_NORMAL);
-            }
-        }
-    }
+  std::string path = basedir + "/";
+  char** files = PHYSFS_enumerateFiles(path.c_str());
+  if(!files) {
+    msg_warning << "Couldn't read subset dir '" << path << "'" << std::endl;
+    return;
+  }
 
-  // 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);
-        }
+  for(const char* const* filename = files; *filename != 0; ++filename) {
+    if(has_suffix(*filename, ".stl")) {
+      levels.push_back(path + *filename);
     }
+  }
+  PHYSFS_freeList(files);
 }
 
 void
-World::add_score(float x, float y, int s)
-{
-  player_status.score += s;
-
-  FloatingScore new_floating_score;
-  new_floating_score.init(x,y,s);
-  floating_scores.push_back(new_floating_score);
-}
-
-void
-World::add_bouncy_distro(float x, float y)
-{
-  BouncyDistro new_bouncy_distro;
-  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);
-
-  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_broken_brick.init(tile, x, y, xm, ym);
-  broken_bricks.push_back(new_broken_brick);
-}
-
-void
-World::add_bouncy_brick(float x, float y)
+World::run()
 {
-  BouncyBrick new_bouncy_brick;
-  new_bouncy_brick.init(x,y);
-  bouncy_bricks.push_back(new_bouncy_brick);
-}
+  // create new squirrel table for persisten game state
+  HSQUIRRELVM vm = ScriptManager::instance->get_vm();
 
-void
-World::add_bad_guy(float x, float y, BadGuyKind kind, bool stay_on_platform)
-{
-  bad_guys.push_back(BadGuy());
-  BadGuy& new_bad_guy = bad_guys.back();
+  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);
   
-  new_bad_guy.init(x,y,kind, stay_on_platform);
+  std::string filename = basedir + "/world.nut";
+  IFileStream in(filename);
+
+  HSQUIRRELVM new_vm = ScriptManager::instance->create_thread();
+  Scripting::compile_and_run(new_vm, in, filename);
 }
 
 void
-World::add_upgrade(float x, float y, Direction dir, UpgradeKind kind)
+World::save()
 {
-  Upgrade new_upgrade;
-  new_upgrade.init(x,y,dir,kind);
-  upgrades.push_back(new_upgrade);
-}
+  lisp::Writer writer(savegame_filename);
 
-void 
-World::add_bullet(float x, float y, float xm, Direction dir)
-{
-  if(bullets.size() > MAX_BULLETS-1)
-    return;
+  writer.start_list("supertux-savegame");
+  writer.write_int("version", 1);
 
-  Bullet new_bullet;
-  new_bullet.init(x,y,xm,dir);
-  bullets.push_back(new_bullet);
-  
-  play_sound(sounds[SND_SHOOT], SOUND_CENTER_SPEAKER);
-}
+  writer.start_list("tux");
+  player_status->write(writer);
+  writer.end_list("tux");
 
-void
-World::play_music(int musictype)
-{
-  currentmusic = musictype;
-  switch(currentmusic) {
-    case HURRYUP_MUSIC:
-      ::play_music(get_level()->get_level_music_fast());
-      break;
-    case LEVEL_MUSIC:
-      ::play_music(get_level()->get_level_music());
-      break;
-    case HERRING_MUSIC:
-      ::play_music(herring_song);
-      break;
-    default:
-      ::halt_music();
-      break;
+  writer.start_list("state");
+  HSQUIRRELVM vm = ScriptManager::instance->get_vm();
+  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);
   }
-}
-
-int
-World::get_music_type()
-{
-  return currentmusic;
-}
-
-/* Break a brick: */
-void
-World::trybreakbrick(float x, float y, bool small)
-{
-  Level* plevel = get_level();
+  sq_pop(vm, 1);
+  writer.end_list("state");
   
-  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);
-
-          if (!counting_distros)
-            {
-              counting_distros = true;
-              distro_counter = 50;
-            }
-
-          if (distro_counter <= 0)
-            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++;
-        }
-      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;
-        }
-    }
+  writer.end_list("supertux-savegame");
 }
 
-/* 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 an 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)
-{
-  // Bad guys: 
-  for (unsigned int i = 0; i < bad_guys.size(); i++)
-    {
-      if (bad_guys[i].base.x >= x - 32 && bad_guys[i].base.x <= x + 32 &&
-          bad_guys[i].base.y >= y - 16 && bad_guys[i].base.y <= y + 16)
-        {
-          bad_guys[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].base.xm = -upgrades[i].base.xm;
-          upgrades[i].base.ym = -8;
-          play_sound(sounds[SND_BUMP_UPGRADE], SOUND_CENTER_SPEAKER);
-        }
-    }
-}
-
-/* EOF */
-