-// $Id$
-//
// IceCrusher - A block to stand on, which can drop down to crush the player
// Copyright (C) 2008 Christoph Sommer <christoph.sommer@2008.expires.deltadevelopment.de>
+// Copyright (C) 2010 Florian Forster <supertux at octo.it>
//
-// 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 2
-// of the License, or (at your option) any later version.
+// 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
// 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.
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include <config.h>
+#include "object/icecrusher.hpp"
-#include "icecrusher.hpp"
+#include <math.h>
-#include <stdexcept>
-#include "log.hpp"
-#include "video/drawing_context.hpp"
-#include "resources.hpp"
+#include "audio/sound_manager.hpp"
#include "badguy/badguy.hpp"
+#include "object/camera.hpp"
+#include "object/particles.hpp"
+#include "object/player.hpp"
#include "sprite/sprite.hpp"
-#include "lisp/lisp.hpp"
-#include "object_factory.hpp"
-#include "sector.hpp"
+#include "sprite/sprite_manager.hpp"
+#include "supertux/object_factory.hpp"
+#include "supertux/sector.hpp"
namespace {
- const float DROP_SPEED = 500;
- const float RECOVER_SPEED = 200;
+/* Maximum movement speed in pixels per LOGICAL_FPS */
+const float MAX_DROP_SPEED = 10.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 lisp::Lisp& reader)
- : MovingSprite(reader, "images/creatures/icecrusher/icecrusher.sprite", LAYER_OBJECTS, COLGROUP_STATIC),
- state(IDLE), speed(Vector(0,0))
+IceCrusher::IceCrusher(const Reader& reader) :
+ MovingSprite(reader, "images/creatures/icecrusher/icecrusher.sprite", LAYER_OBJECTS, COLGROUP_STATIC),
+ state(IDLE),
+ start_position(),
+ 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)
-{
+/*
+ IceCrusher::IceCrusher(const IceCrusher& other)
+ : 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);
- speed=Vector(0,0);
+ physic.enable_gravity (false);
sprite->set_action("idle");
break;
case CRUSHING:
set_group(COLGROUP_MOVING_STATIC);
- speed=Vector(0, DROP_SPEED);
- sprite->set_action("idle");
+ physic.reset ();
+ physic.enable_gravity (true);
+ sprite->set_action("crushing");
break;
case RECOVERING:
set_group(COLGROUP_MOVING_STATIC);
- speed=Vector(0, -RECOVER_SPEED);
- sprite->set_action("idle");
+ physic.enable_gravity (false);
+ sprite->set_action("recovering");
break;
default:
log_debug << "IceCrusher in invalid state" << std::endl;
break;
}
- this->state = state;
+ this->state = state_;
}
HitResponse
IceCrusher::collision(GameObject& other, const CollisionHit& hit)
{
Player* player = dynamic_cast<Player*>(&other);
+
+ /* 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);
+ if (state == CRUSHING)
+ set_state(RECOVERING);
return ABORT_MOVE;
}
player->kill(false);
- if (state == CRUSHING) set_state(RECOVERING);
+ if (state == CRUSHING)
+ set_state(RECOVERING);
return FORCE_MOVE;
}
BadGuy* badguy = dynamic_cast<BadGuy*>(&other);
}
return FORCE_MOVE;
}
-
-void
-IceCrusher::collision_solid(const CollisionHit& )
+
+void
+IceCrusher::collision_solid(const CollisionHit& hit)
{
switch(state) {
case IDLE:
break;
case CRUSHING:
- set_state(RECOVERING);
+ 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;
case RECOVERING:
break;
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:
- if (found_victim()) set_state(CRUSHING);
+ movement = Vector (0, 0);
+ if (found_victim())
+ set_state(CRUSHING);
break;
case CRUSHING:
+ movement = physic.get_movement (elapsed_time);
+ if (movement.y > MAX_DROP_SPEED)
+ movement.y = MAX_DROP_SPEED;
break;
case RECOVERING:
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 {
+ if (ic_size == LARGE)
+ movement = Vector (0, RECOVER_SPEED_LARGE);
+ else
+ movement = Vector (0, RECOVER_SPEED_NORMAL);
+ }
break;
default:
log_debug << "IceCrusher in invalid state" << std::endl;
break;
}
- movement = speed * 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 Rect& pr = player->get_bbox();
- const Rect& br = get_bbox();
- if ((pr.p2.x > br.p1.x) && (pr.p1.x < br.p2.x) && (pr.p1.y >= br.p2.y)) {
+ 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 - 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);
+ }
}
- return false;
+ 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);
}
-IMPLEMENT_FACTORY(IceCrusher, "icecrusher");
+/* EOF */