Remove bogus assert
[supertux.git] / src / object / icecrusher.cpp
index 38e2507..72220a4 100644 (file)
 
 #include "object/icecrusher.hpp"
 
+#include <math.h>
+
+#include "audio/sound_manager.hpp"
 #include "badguy/badguy.hpp"
-#include "sprite/sprite.hpp"
+#include "object/camera.hpp"
+#include "object/particles.hpp"
 #include "object/player.hpp"
+#include "sprite/sprite.hpp"
+#include "sprite/sprite_manager.hpp"
 #include "supertux/object_factory.hpp"
 #include "supertux/sector.hpp"
 
 namespace {
 /* Maximum movement speed in pixels per LOGICAL_FPS */
 const float MAX_DROP_SPEED = 10.0;
-const float RECOVER_SPEED = -3.125;
-const float ACTIVATION_DISTANCE = 4.0;
+const float RECOVER_SPEED_NORMAL = -3.125;
+const float RECOVER_SPEED_LARGE  = -2.0;
+const float DROP_ACTIVATION_DISTANCE = 4.0;
+const float PAUSE_TIME_NORMAL = 0.5;
+const float PAUSE_TIME_LARGE  = 1.0;
 }
 
 IceCrusher::IceCrusher(const Reader& reader) :
-  MovingSprite(reader, "images/creatures/icecrusher/icecrusher.sprite", LAYER_OBJECTS, COLGROUP_STATIC), 
-  state(IDLE), 
+  MovingSprite(reader, "images/creatures/icecrusher/icecrusher.sprite", LAYER_OBJECTS, COLGROUP_STATIC),
+  state(IDLE),
   start_position(),
-  physic()
+  physic(),
+  cooldown_timer(0.0),
+  lefteye(),
+  righteye(),
+  whites(),
+  ic_size(NORMAL)
 {
+  // TODO: icecrusher hitting deserves its own sounds-
+  // one for hitting the ground, one for hitting Tux
+  SoundManager::current()->preload("sounds/brick.wav");
+
   start_position = get_bbox().p1;
   set_state(state, true);
+
+  float sprite_width = sprite->get_width ();
+  if (sprite_width >= 128.0)
+    ic_size = LARGE;
+
+  lefteye = SpriteManager::current()->create(sprite_name);
+  lefteye->set_action("lefteye");
+  righteye = SpriteManager::current()->create(sprite_name);
+  righteye->set_action("righteye");
+  whites = SpriteManager::current()->create(sprite_name);
+  whites->set_action("whites");
 }
 
 /*
   IceCrusher::IceCrusher(const IceCrusher& other)
-  : MovingSprite(other), 
-  state(other.state), speed(other.speed) 
+  : MovingSprite(other),
+  state(other.state), speed(other.speed)
   {
   start_position = get_bbox().p1;
   set_state(state, true);
   }
 */
-void 
-IceCrusher::set_state(IceCrusherState state, bool force) 
+void
+IceCrusher::set_state(IceCrusherState state_, bool force)
 {
-  if ((this->state == state) && (!force)) return;
-  switch(state) {
+  if ((this->state == state_) && (!force)) return;
+  switch(state_) {
     case IDLE:
       set_group(COLGROUP_STATIC);
       physic.enable_gravity (false);
@@ -74,7 +103,7 @@ IceCrusher::set_state(IceCrusherState state, bool force)
       log_debug << "IceCrusher in invalid state" << std::endl;
       break;
   }
-  this->state = state;
+  this->state = state_;
 }
 
 HitResponse
@@ -85,6 +114,7 @@ IceCrusher::collision(GameObject& other, const CollisionHit& hit)
   /* If the other object is the player, and the collision is at the bottom of
    * the ice crusher, hurt the player. */
   if (player && hit.bottom) {
+    SoundManager::current()->play("sounds/brick.wav");
     if(player->is_invincible()) {
       if (state == CRUSHING)
         set_state(RECOVERING);
@@ -101,8 +131,8 @@ IceCrusher::collision(GameObject& other, const CollisionHit& hit)
   }
   return FORCE_MOVE;
 }
-    
-void 
+
+void
 IceCrusher::collision_solid(const CollisionHit& hit)
 {
   switch(state) {
@@ -110,6 +140,40 @@ IceCrusher::collision_solid(const CollisionHit& hit)
       break;
     case CRUSHING:
       if (hit.bottom) {
+        if (ic_size == LARGE) {
+          cooldown_timer = PAUSE_TIME_LARGE;
+          Sector::current()->camera->shake (/* frequency = */ .125f, /* x = */ 0.0, /* y = */ 16.0);
+          SoundManager::current()->play("sounds/brick.wav");
+          // throw some particles, bigger and more for large icecrusher
+          for(int j = 0; j < 9; j++)
+          {
+          Sector::current()->add_object(std::make_shared<Particles>(
+                                          Vector(get_bbox().p2.x - j*8 - 4, get_bbox().p2.y),
+                                          0, 90-5*j, 140, 380, Vector(0, 300),
+                                          1, Color(.6f, .6f, .6f), 5, 1.8f, LAYER_OBJECTS+1));
+          Sector::current()->add_object(std::make_shared<Particles>(
+                                          Vector(get_bbox().p1.x + j*8 + 4, get_bbox().p2.y),
+                                          270+5*j, 360, 140, 380, Vector(0, 300),
+                                          1, Color(.6f, .6f, .6f), 5, 1.8f, LAYER_OBJECTS+1));
+          }
+        }
+        else {
+          cooldown_timer = PAUSE_TIME_NORMAL;
+          Sector::current()->camera->shake (/* frequency = */ .1f, /* x = */ 0.0, /* y = */ 8.0);
+          SoundManager::current()->play("sounds/brick.wav");
+          // throw some particles
+          for(int j = 0; j < 5; j++)
+          {
+          Sector::current()->add_object(std::make_shared<Particles>(
+                                          Vector(get_bbox().p2.x - j*8 - 4, get_bbox().p2.y),
+                                          0, 90+10*j, 140, 260, Vector(0, 300),
+                                          1, Color(.6f, .6f, .6f), 4, 1.6f, LAYER_OBJECTS+1));
+          Sector::current()->add_object(std::make_shared<Particles>(
+                                          Vector(get_bbox().p1.x + j*8 + 4, get_bbox().p2.y),
+                                          270+10*j, 360, 140, 260, Vector(0, 300),
+                                          1, Color(.6f, .6f, .6f), 4, 1.6f, LAYER_OBJECTS+1));
+          }
+        }
         set_state(RECOVERING);
       }
       break;
@@ -124,6 +188,17 @@ IceCrusher::collision_solid(const CollisionHit& hit)
 void
 IceCrusher::update(float elapsed_time)
 {
+  if (cooldown_timer >= elapsed_time)
+  {
+    cooldown_timer -= elapsed_time;
+    return;
+  }
+  else if (cooldown_timer != 0.0)
+  {
+    elapsed_time -= cooldown_timer;
+    cooldown_timer = 0.0;
+  }
+
   switch(state) {
     case IDLE:
       movement = Vector (0, 0);
@@ -139,10 +214,17 @@ IceCrusher::update(float elapsed_time)
       if (get_bbox().p1.y <= start_position.y+1) {
         set_pos(start_position);
         movement = Vector (0, 0);
+        if (ic_size == LARGE)
+          cooldown_timer = PAUSE_TIME_LARGE;
+        else
+          cooldown_timer = PAUSE_TIME_NORMAL;
         set_state(IDLE);
       }
       else {
-        movement = Vector (0, RECOVER_SPEED);
+        if (ic_size == LARGE)
+          movement = Vector (0, RECOVER_SPEED_LARGE);
+        else
+          movement = Vector (0, RECOVER_SPEED_NORMAL);
       }
       break;
     default:
@@ -151,35 +233,82 @@ IceCrusher::update(float elapsed_time)
   }
 }
 
-Player*
-IceCrusher::get_nearest_player()
+void
+IceCrusher::draw(DrawingContext& context)
 {
-  // FIXME: does not really return nearest player
-
-  std::vector<Player*> players = Sector::current()->get_players();
-  for (std::vector<Player*>::iterator playerIter = players.begin(); playerIter != players.end(); ++playerIter) {
-    Player* player = *playerIter;
-    if (player->is_dying() || player->is_dead()) continue;
-    return player;
+  context.push_target();
+  context.set_target(DrawingContext::NORMAL);
+  sprite->draw(context, get_pos(), layer+2);
+  if(!(state == CRUSHING) && sprite->has_action("whites"))
+  {
+    // draw icecrusher's eyes slightly behind
+    lefteye->draw(context, get_pos()+eye_position(false), layer+1);
+    righteye->draw(context, get_pos()+eye_position(true), layer+1);
+    // draw the whites of icecrusher's eyes even further behind
+    whites->draw(context, get_pos(), layer);
   }
-
-  return 0;
+  context.pop_target();
 }
 
 bool
 IceCrusher::found_victim()
 {
-  Player* player = this->get_nearest_player();
+  Player* player = Sector::current()->get_nearest_player (this->get_bbox ());
   if (!player) return false;
 
   const Rectf& player_bbox = player->get_bbox();
   const Rectf& crusher_bbox = get_bbox();
+  Rectf crush_area = Rectf(crusher_bbox.p1.x+1, crusher_bbox.p2.y, crusher_bbox.p2.x-1, std::max(crusher_bbox.p2.y,player_bbox.p1.y-1));
   if ((player_bbox.p1.y >= crusher_bbox.p2.y) /* player is below crusher */
-      && (player_bbox.p2.x > (crusher_bbox.p1.x - ACTIVATION_DISTANCE))
-      && (player_bbox.p1.x < (crusher_bbox.p2.x + ACTIVATION_DISTANCE)))
+      && (player_bbox.p2.x > (crusher_bbox.p1.x - DROP_ACTIVATION_DISTANCE))
+      && (player_bbox.p1.x < (crusher_bbox.p2.x + DROP_ACTIVATION_DISTANCE))
+      && (Sector::current()->is_free_of_statics(crush_area, this, false))/* and area to player is free of objects */)
     return true;
   else
     return false;
 }
 
+Vector
+IceCrusher::eye_position(bool right)
+{
+  if(state == IDLE)
+  {
+    Player* player = Sector::current()->get_nearest_player (this->get_bbox ());
+    if(player)
+    {
+      // Icecrusher focuses on approximate position of player's head
+      const float player_focus_x = (player->get_bbox().p2.x + player->get_bbox().p1.x) * 0.5;
+      const float player_focus_y = player->get_bbox().p2.y * 0.25 + player->get_bbox().p1.y * 0.75;
+      // Icecrusher's approximate origin of line-of-sight
+      const float crusher_origin_x = (get_bbox().p2.x + get_bbox().p1.x) * 0.5;
+      const float crusher_origin_y = (get_bbox().p2.y + get_bbox().p1.y) * 0.5;
+      // Line-of-sight displacement from icecrusher to player
+      const float displacement_x = player_focus_x - crusher_origin_x;
+      const float displacement_y = player_focus_y - crusher_origin_y;
+      const float displacement_mag = pow(pow(displacement_x, 2.0) + pow(displacement_y, 2.0), 0.5);
+      // Determine weighting for eye displacement along x given icecrusher eye shape
+      int weight_x = sprite->get_width()/64 * (((displacement_x > 0) == right) ? 1 : 4);
+      int weight_y = sprite->get_width()/64 * 2;
+
+      return Vector(displacement_x/displacement_mag * weight_x, displacement_y/displacement_mag * weight_y - weight_y);
+    }
+  }
+  else if(state == RECOVERING)
+  {
+    // Eyes spin while icecrusher is recovering, giving a dazed impression
+    return Vector(sin((right ? 1 : -1) * // X motion of each eye is opposite of the other
+                  (get_pos().y/13 - // Phase factor due to y position
+                  (ic_size==NORMAL ? RECOVER_SPEED_NORMAL : RECOVER_SPEED_LARGE) + cooldown_timer*13)) * //Phase factor due to cooldown timer
+                  sprite->get_width()/64 * 2 - (right ? 1 : -1) * // Amplitude dependent on size
+                  sprite->get_width()/64 * 2, // Offset to keep eyes visible
+                  cos((right ? 3.1415 : 0) + // Eyes spin out of phase of eachother
+                  get_pos().y/13 - // Phase factor due to y position
+                  (ic_size==NORMAL ? RECOVER_SPEED_NORMAL : RECOVER_SPEED_LARGE) + cooldown_timer*13) * //Phase factor due to cooldown timer
+                  sprite->get_width()/64 * 2 -  // Amplitude dependent on size
+                  sprite->get_width()/64 * 2); // Offset to keep eyes visible
+  }
+
+  return Vector(0,0);
+}
+
 /* EOF */