improve exception feedback a bit
[supertux.git] / src / sector.cpp
index 252cafb..4cde141 100644 (file)
 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
 #include <memory>
+#include <algorithm>
 #include <stdexcept>
 #include <iostream>
 #include <fstream>
 #include <stdexcept>
 
+#include "app/globals.h"
 #include "sector.h"
-#include "lispreader.h"
+#include "utils/lispreader.h"
 #include "badguy.h"
 #include "special.h"
 #include "gameobjs.h"
 #include "particlesystem.h"
 #include "tile.h"
 #include "tilemap.h"
-#include "sound_manager.h"
+#include "audio/sound_manager.h"
 #include "gameloop.h"
 #include "resources.h"
 #include "interactive_object.h"
 #include "door.h"
+#include "statistics.h"
 
 Sector* Sector::_current = 0;
 
 Sector::Sector()
-  : gravity(10), player(0), solids(0), background(0), camera(0),
+  : end_sequence_animation_type(NONE_ENDSEQ_ANIM),
+    gravity(10), player(0), solids(0), background(0), camera(0),
     currentmusic(LEVEL_MUSIC)
 {
   song_title = "Mortimers_chipdisko.mod";
@@ -64,6 +68,23 @@ Sector::~Sector()
     _current = 0;
 }
 
+Sector *Sector::create(const std::string& name, size_t width, size_t height)
+{
+  Sector *sector = new Sector;
+  sector->name = name;
+  TileMap *background = new TileMap(LAYER_BACKGROUNDTILES, false, width, height);
+  TileMap *interactive = new TileMap(LAYER_TILES, true, width, height);
+  TileMap *foreground = new TileMap(LAYER_FOREGROUNDTILES, false, width, height);
+  sector->add_object(background);
+  sector->add_object(interactive);
+  sector->add_object(foreground);
+  sector->solids = interactive;
+  sector->camera = new Camera(sector);
+  sector->add_object(sector->camera);
+  sector->update_game_objects();
+  return sector;
+}
+
 void
 Sector::parse(LispReader& lispreader)
 {
@@ -72,16 +93,21 @@ Sector::parse(LispReader& lispreader)
   for(lisp_object_t* cur = lispreader.get_lisp(); !lisp_nil_p(cur);
       cur = lisp_cdr(cur)) {
     std::string token = lisp_symbol(lisp_car(lisp_car(cur)));
+    // FIXME: doesn't handle empty data
     lisp_object_t* data = lisp_car(lisp_cdr(lisp_car(cur)));
     LispReader reader(lisp_cdr(lisp_car(cur)));
 
     if(token == "name") {
       name = lisp_string(data);
     } else if(token == "gravity") {
-      gravity = lisp_integer(data);
+      gravity = lisp_real(data);
     } else if(token == "music") {
       song_title = lisp_string(data);
       load_music();
+    } else if(token == "end-sequence-animation") {
+      std::string end_seq_anim = lisp_string(data);
+      if(end_seq_anim == "fireworks")
+        end_sequence_animation_type = FIREWORKS_ENDSEQ_ANIM;
     } else if(token == "camera") {
       if(camera) {
         std::cerr << "Warning: More than 1 camera defined in sector.\n";
@@ -93,7 +119,7 @@ Sector::parse(LispReader& lispreader)
     } else if(token == "background") {
       background = new Background(reader);
       add_object(background);
-    } else if(token == "playerspawn") {
+    } else if(token == "spawn-points") {
       SpawnPoint* sp = new SpawnPoint;
       reader.read_string("name", sp->name);
       reader.read_float("x", sp->pos.x);
@@ -152,6 +178,7 @@ Sector::parse_old_format(LispReader& reader)
   reader.read_string("background", backgroundimage);
   float bgspeed = .5;
   reader.read_float("bkgd_speed", bgspeed);
+  bgspeed /= 100;
 
   Color bkgd_top, bkgd_bottom;
   int r = 0, g = 0, b = 128;
@@ -179,6 +206,13 @@ Sector::parse_old_format(LispReader& reader)
     add_object(background);
   }
 
+  std::string end_seq_anim;
+  reader.read_string("end-sequence-animation", end_seq_anim);
+  if(end_seq_anim == "fireworks")
+    end_sequence_animation_type = FIREWORKS_ENDSEQ_ANIM;
+//  else
+//    end_sequence_animation = NONE_ENDSEQ_ANIM;
+
   std::string particlesystem;
   reader.read_string("particle_system", particlesystem);
   if(particlesystem == "clouds")
@@ -224,7 +258,27 @@ Sector::parse_old_format(LispReader& reader)
     add_object(tilemap);
   }
 
-  // TODO read resetpoints
+  // read reset-points (now spawn-points)
+  {
+    lisp_object_t* cur = 0;
+    if(reader.read_lisp("reset-points", cur)) {
+      while(!lisp_nil_p(cur)) {
+        lisp_object_t* data = lisp_car(cur);
+        LispReader reader(lisp_cdr(data));
+
+        Vector sp_pos;
+        if(reader.read_float("x", sp_pos.x) && reader.read_float("y", sp_pos.y))
+          {
+          SpawnPoint* sp = new SpawnPoint;
+          sp->name = "main";
+          sp->pos = sp_pos;
+          spawnpoints.push_back(sp);
+          }
+                                                             
+        cur = lisp_cdr(cur);
+      }
+    }
+  }
 
   // read objects
   {
@@ -262,7 +316,20 @@ Sector::write(LispWriter& writer)
 {
   writer.write_string("name", name);
   writer.write_float("gravity", gravity);
+  writer.write_string("music", song_title);
 
+  // write spawnpoints
+  for(SpawnPoints::iterator i = spawnpoints.begin(); i != spawnpoints.end();
+      ++i) {
+    SpawnPoint* spawn = *i;
+    writer.start_list("spawn-points");
+    writer.write_string("name", spawn->name);
+    writer.write_float("x", spawn->pos.x);
+    writer.write_float("y", spawn->pos.y);
+    writer.end_list("spawn-points");
+  }
+
+  // write objects
   for(GameObjects::iterator i = gameobjects.begin();
       i != gameobjects.end(); ++i) {
     Serializable* serializable = dynamic_cast<Serializable*> (*i);
@@ -272,6 +339,38 @@ Sector::write(LispWriter& writer)
 }
 
 void
+Sector::do_vertical_flip()
+{
+  for(GameObjects::iterator i = gameobjects_new.begin(); i != gameobjects_new.end(); ++i)
+    {
+    TileMap* tilemap = dynamic_cast<TileMap*> (*i);
+    if(tilemap)
+      {
+      tilemap->do_vertical_flip();
+      }
+
+    BadGuy* badguy = dynamic_cast<BadGuy*> (*i);
+    if(badguy)
+      badguy->start_position.y = solids->get_height()*32 - badguy->start_position.y - 32;
+    Trampoline* trampoline = dynamic_cast<Trampoline*> (*i);
+    if(trampoline)
+      trampoline->base.y = solids->get_height()*32 - trampoline->base.y - 32;
+    FlyingPlatform* flying_platform = dynamic_cast<FlyingPlatform*> (*i);
+    if(flying_platform)
+      flying_platform->base.y = solids->get_height()*32 - flying_platform->base.y - 32;
+    Door* door = dynamic_cast<Door*> (*i);
+    if(door)
+      door->set_area(door->get_area().x, solids->get_height()*32 - door->get_area().y - 32);
+    }
+
+  for(SpawnPoints::iterator i = spawnpoints.begin(); i != spawnpoints.end();
+      ++i) {
+    SpawnPoint* spawn = *i;
+    spawn->pos.y = solids->get_height()*32 - spawn->pos.y - 32;
+  }
+}
+
+void
 Sector::add_object(GameObject* object)
 {
   gameobjects_new.push_back(object);
@@ -314,6 +413,22 @@ Sector::activate(const std::string& spawnpoint)
   camera->reset(Vector(player->base.x, player->base.y));
 }
 
+Vector
+Sector::get_best_spawn_point(Vector pos)
+{
+Vector best_reset_point = Vector(-1,-1);
+
+for(SpawnPoints::iterator i = spawnpoints.begin(); i != spawnpoints.end();
+      ++i) {
+  if((*i)->name != "main")
+    continue;
+  if((*i)->pos.x > best_reset_point.x && (*i)->pos.x < pos.x)
+    best_reset_point = (*i)->pos;
+  }
+
+return best_reset_point;
+}
+
 void
 Sector::action(float elapsed_time)
 {
@@ -329,6 +444,12 @@ Sector::action(float elapsed_time)
   /* Handle all possible collisions. */
   collision_handler();
                                                                                 
+  update_game_objects();
+}
+
+void
+Sector::update_game_objects()
+{
   /** cleanup marked objects */
   for(std::vector<GameObject*>::iterator i = gameobjects.begin();
       i != gameobjects.end(); /* nothing */) {
@@ -369,7 +490,19 @@ Sector::action(float elapsed_time)
             std::remove(flying_platforms.begin(), flying_platforms.end(), flying_platform),
             flying_platforms.end());
       }
-                                                                                
+      SmokeCloud* smoke_cloud = dynamic_cast<SmokeCloud*> (*i);
+      if(smoke_cloud) {
+        smoke_clouds.erase(
+            std::remove(smoke_clouds.begin(), smoke_clouds.end(), smoke_cloud),
+            smoke_clouds.end());
+      }
+      Particles* particle = dynamic_cast<Particles*> (*i);
+      if(particle) {
+        particles.erase(
+            std::remove(particles.begin(), particles.end(), particle),
+            particles.end());
+      }
+
       delete *i;
       i = gameobjects.erase(i);
     } else {
@@ -400,11 +533,16 @@ Sector::action(float elapsed_time)
               = dynamic_cast<InteractiveObject*> (*i);
           if(interactive_object)
             interactive_objects.push_back(interactive_object);
+          SmokeCloud* smoke_cloud = dynamic_cast<SmokeCloud*> (*i);
+          if(smoke_cloud)
+            smoke_clouds.push_back(smoke_cloud);
+          Particles* particle = dynamic_cast<Particles*> (*i);
+          if(particle)
+            particles.push_back(particle);
 
           gameobjects.push_back(*i);
   }
   gameobjects_new.clear();
-
 }
 
 void
@@ -545,9 +683,9 @@ Sector::collision_handler()
 void
 Sector::add_score(const Vector& pos, int s)
 {
-  player_status.score += s;
+  global_stats.add_points(SCORE_STAT, s);
                                                                                 
-  add_object(new FloatingScore(pos, s));
+  add_object(new FloatingText(pos, s));
 }
                                                                                 
 void
@@ -580,10 +718,12 @@ Sector::add_bouncy_brick(const Vector& pos)
 }
 
 BadGuy*
-Sector::add_bad_guy(float x, float y, BadGuyKind kind)
+Sector::add_bad_guy(float x, float y, BadGuyKind kind, bool activate)
 {
   BadGuy* badguy = new BadGuy(kind, x, y);
   add_object(badguy);
+  if(activate)
+    badguy->activate(LEFT);
   return badguy;
 }
                                                                                 
@@ -615,12 +755,32 @@ Sector::add_bullet(const Vector& pos, float xm, Direction dir)
   else
     throw std::runtime_error("wrong bullet type.");
   add_object(new_bullet);
+
+  SoundManager::get()->play_sound(IDToSound(SND_SHOOT));
                                                                                 
-  sound_manager->play_sound(sounds[SND_SHOOT]);
-                                                                                
   return true;
 }
 
+bool
+Sector::add_smoke_cloud(const Vector& pos)
+{
+  add_object(new SmokeCloud(pos));
+  return true;
+}
+
+bool
+Sector::add_particles(const Vector& epicenter, int min_angle, int max_angle, const Vector& initial_velocity, const Vector& acceleration, int number, Color color, int size, int life_time, int drawing_layer)
+{
+  add_object(new Particles(epicenter, min_angle, max_angle, initial_velocity, acceleration, number, color, size, life_time, drawing_layer));
+  return true;
+}
+
+void
+Sector::add_floating_text(const Vector& pos, const std::string& text)
+{
+  add_object(new FloatingText(pos, text));
+}
+
 /* Break a brick: */
 bool
 Sector::trybreakbrick(const Vector& pos, bool small)
@@ -657,9 +817,10 @@ Sector::trybreakbrick(const Vector& pos, bool small)
               counting_distros = false;
               solids->change_at(pos, tile->next_tile);
             }
-                                                                                
-          sound_manager->play_sound(sounds[SND_DISTRO]);
-          player_status.score = player_status.score + SCORE_DISTRO;
+
+          SoundManager::get()->play_sound(IDToSound(SND_DISTRO));
+          global_stats.add_points(SCORE_STAT, SCORE_DISTRO);
+          global_stats.add_points(COINS_COLLECTED_STAT, 1);
           player_status.distros++;
           return true;
         }
@@ -674,8 +835,8 @@ Sector::trybreakbrick(const Vector& pos, bool small)
                                  (int)(pos.y / 32) * 32), tile);
                                                                                 
           /* Get some score: */
-          sound_manager->play_sound(sounds[SND_BRICK]);
-          player_status.score = player_status.score + SCORE_BRICK;
+          SoundManager::get()->play_sound(IDToSound(SND_BRICK));
+          global_stats.add_points(SCORE_STAT, SCORE_BRICK);
                                                                                 
           return true;
         }
@@ -712,8 +873,9 @@ Sector::tryemptybox(const Vector& pos, Direction col_side)
     {
     case 1: // Box with a distro!
       add_bouncy_distro(Vector(posx, posy));
-      sound_manager->play_sound(sounds[SND_DISTRO]);
-      player_status.score = player_status.score + SCORE_DISTRO;
+      SoundManager::get()->play_sound(IDToSound(SND_DISTRO));
+      global_stats.add_points(SCORE_STAT, SCORE_DISTRO);
+      global_stats.add_points(COINS_COLLECTED_STAT, 1);
       player_status.distros++;
       break;
                                                                                 
@@ -722,7 +884,7 @@ Sector::tryemptybox(const Vector& pos, Direction col_side)
         add_upgrade(Vector(posx, posy), col_side, UPGRADE_GROWUP);
       else     /* Tux is big, add a fireflower: */
         add_upgrade(Vector(posx, posy), col_side, UPGRADE_FIREFLOWER);
-      sound_manager->play_sound(sounds[SND_UPGRADE]);
+      SoundManager::get()->play_sound(IDToSound(SND_UPGRADE));
       break;
                                                                                 
     case 5: // Add an ice flower upgrade!
@@ -730,11 +892,11 @@ Sector::tryemptybox(const Vector& pos, Direction col_side)
         add_upgrade(Vector(posx, posy), col_side, UPGRADE_GROWUP);
       else     /* Tux is big, add an iceflower: */
         add_upgrade(Vector(posx, posy), col_side, UPGRADE_ICEFLOWER);
-      sound_manager->play_sound(sounds[SND_UPGRADE]);
+      SoundManager::get()->play_sound(IDToSound(SND_UPGRADE));
       break;
                                                                                 
     case 3: // Add a golden herring
-      add_upgrade(Vector(posx, posy), col_side, UPGRADE_HERRING);
+      add_upgrade(Vector(posx, posy), col_side, UPGRADE_STAR);
       break;
                                                                                 
     case 4: // Add a 1up extra
@@ -760,7 +922,7 @@ Sector::trygrabdistro(const Vector& pos, int bounciness)
     throw SuperTuxException(errmsg, __FILE__, __LINE__); */
     
     //Bad tiles (i.e. tiles that are not defined in supertux.stgt but appear in the map) are changed to ID 0 (blank tile)
-    std::cout << "Warning: Undefined tile at " <<(int)pos.x/32 << "/" << (int)pos.y/32 << " (ID: " << (int)solids->get_tile_id_at(pos) << ")" << std::endl;
+    std::cout << "Warning: Undefined tile at " <<(int)pos.x/32 << "/" << (int)pos.y/32 << " (ID: " << (int)solids->get_tile_id_at(pos).id << ")" << std::endl;
     solids->change_at(pos,0);
     tile = solids->get_tile_at(pos);
   }
@@ -770,15 +932,16 @@ Sector::trygrabdistro(const Vector& pos, int bounciness)
     return;
 
   solids->change_at(pos, tile->next_tile);
-  sound_manager->play_sound(sounds[SND_DISTRO]);
-                                                                            
+  SoundManager::get()->play_sound(IDToSound(SND_DISTRO));
+
   if (bounciness == BOUNCE)
     {
       add_bouncy_distro(Vector(((int)(pos.x + 1) / 32) * 32,
                               (int)(pos.y / 32) * 32));
     }
                                                                             
-  player_status.score = player_status.score + SCORE_DISTRO;
+  global_stats.add_points(SCORE_STAT, SCORE_DISTRO);
+  global_stats.add_points(COINS_COLLECTED_STAT, 1);
   player_status.distros++;
 
 }
@@ -815,7 +978,7 @@ Sector::load_music()
   char* song_path;
   char* song_subtitle;
                                                                                 
-  level_song = sound_manager->load_music(datadir + "/music/" + song_title);
+  level_song = SoundManager::get()->load_music(datadir + "/music/" + song_title);
                                                                                 
   song_path = (char *) malloc(sizeof(char) * datadir.length() +
                               strlen(song_title.c_str()) + 8 + 5);
@@ -823,10 +986,10 @@ Sector::load_music()
   strcpy(strstr(song_subtitle, "."), "\0");
   sprintf(song_path, "%s/music/%s-fast%s", datadir.c_str(),
           song_subtitle, strstr(song_title.c_str(), "."));
-  if(!sound_manager->exists_music(song_path)) {
+  if(!SoundManager::get()->exists_music(song_path)) {
     level_song_fast = level_song;
   } else {
-    level_song_fast = sound_manager->load_music(song_path);
+    level_song_fast = SoundManager::get()->load_music(song_path);
   }
   free(song_subtitle);
   free(song_path);
@@ -838,16 +1001,16 @@ Sector::play_music(int type)
   currentmusic = type;
   switch(currentmusic) {
     case HURRYUP_MUSIC:
-      sound_manager->play_music(level_song_fast);
+      SoundManager::get()->play_music(level_song_fast);
       break;
     case LEVEL_MUSIC:
-      sound_manager->play_music(level_song);
+      SoundManager::get()->play_music(level_song);
       break;
     case HERRING_MUSIC:
-      sound_manager->play_music(herring_song);
+      SoundManager::get()->play_music(herring_song);
       break;
     default:
-      sound_manager->halt_music();
+      SoundManager::get()->halt_music();
       break;
   }
 }
@@ -857,3 +1020,16 @@ Sector::get_music_type()
 {
   return currentmusic;
 }
+
+int
+Sector::get_total_badguys()
+{
+  int total_badguys = 0;
+  for(GameObjects::iterator i = gameobjects_new.begin(); i != gameobjects_new.end(); ++i)
+    {
+    BadGuy* badguy = dynamic_cast<BadGuy*> (*i);
+    if(badguy)
+      total_badguys++;
+    }
+  return total_badguys;
+}