X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=src%2Fobject%2Ficecrusher.cpp;h=72220a40acfe215b307f3079030f34066121659e;hb=6492679a300bff2c17505c5d9bc9d333eeba384d;hp=de27a7791e7d009387c7bd19d401010f42db7010;hpb=729bc6717bf68314dc9fad25db3a9f728062263e;p=supertux.git diff --git a/src/object/icecrusher.cpp b/src/object/icecrusher.cpp index de27a7791..72220a40a 100644 --- a/src/object/icecrusher.cpp +++ b/src/object/icecrusher.cpp @@ -1,5 +1,6 @@ // IceCrusher - A block to stand on, which can drop down to crush the player // Copyright (C) 2008 Christoph Sommer +// Copyright (C) 2010 Florian Forster // // 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 @@ -16,74 +17,112 @@ #include "object/icecrusher.hpp" +#include + +#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 { -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 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(), - speed(Vector(0,0)) + 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); - 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(&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(&other); @@ -92,15 +131,51 @@ IceCrusher::collision(GameObject& other, const CollisionHit& hit) } 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( + 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( + 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( + 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( + 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; @@ -113,52 +188,127 @@ IceCrusher::collision_solid(const CollisionHit& ) 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 players = Sector::current()->get_players(); - for (std::vector::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); + } + } + 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 false; + + return Vector(0,0); } /* EOF */