1 // IceCrusher - A block to stand on, which can drop down to crush the player
2 // Copyright (C) 2008 Christoph Sommer <christoph.sommer@2008.expires.deltadevelopment.de>
3 // Copyright (C) 2010 Florian Forster <supertux at octo.it>
5 // This program is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program. If not, see <http://www.gnu.org/licenses/>.
18 #include "object/icecrusher.hpp"
22 #include "audio/sound_manager.hpp"
23 #include "badguy/badguy.hpp"
24 #include "object/camera.hpp"
25 #include "object/particles.hpp"
26 #include "object/player.hpp"
27 #include "sprite/sprite.hpp"
28 #include "sprite/sprite_manager.hpp"
29 #include "supertux/object_factory.hpp"
30 #include "supertux/sector.hpp"
33 /* Maximum movement speed in pixels per LOGICAL_FPS */
34 const float MAX_DROP_SPEED = 10.0;
35 const float RECOVER_SPEED_NORMAL = -3.125;
36 const float RECOVER_SPEED_LARGE = -2.0;
37 const float DROP_ACTIVATION_DISTANCE = 4.0;
38 const float PAUSE_TIME_NORMAL = 0.5;
39 const float PAUSE_TIME_LARGE = 1.0;
42 IceCrusher::IceCrusher(const Reader& reader) :
43 MovingSprite(reader, "images/creatures/icecrusher/icecrusher.sprite", LAYER_OBJECTS, COLGROUP_STATIC),
53 // TODO: icecrusher hitting deserves its own sounds-
54 // one for hitting the ground, one for hitting Tux
55 sound_manager->preload("sounds/brick.wav");
57 start_position = get_bbox().p1;
58 set_state(state, true);
60 float sprite_width = sprite->get_width ();
61 if (sprite_width >= 128.0)
64 lefteye = sprite_manager->create(sprite_name);
65 lefteye->set_action("lefteye");
66 righteye = sprite_manager->create(sprite_name);
67 righteye->set_action("righteye");
68 whites = sprite_manager->create(sprite_name);
69 whites->set_action("whites");
73 IceCrusher::IceCrusher(const IceCrusher& other)
74 : MovingSprite(other),
75 state(other.state), speed(other.speed)
77 start_position = get_bbox().p1;
78 set_state(state, true);
82 IceCrusher::set_state(IceCrusherState state, bool force)
84 if ((this->state == state) && (!force)) return;
87 set_group(COLGROUP_STATIC);
88 physic.enable_gravity (false);
89 sprite->set_action("idle");
92 set_group(COLGROUP_MOVING_STATIC);
94 physic.enable_gravity (true);
95 sprite->set_action("crushing");
98 set_group(COLGROUP_MOVING_STATIC);
99 physic.enable_gravity (false);
100 sprite->set_action("recovering");
103 log_debug << "IceCrusher in invalid state" << std::endl;
110 IceCrusher::collision(GameObject& other, const CollisionHit& hit)
112 Player* player = dynamic_cast<Player*>(&other);
114 /* If the other object is the player, and the collision is at the bottom of
115 * the ice crusher, hurt the player. */
116 if (player && hit.bottom) {
117 sound_manager->play("sounds/brick.wav");
118 if(player->is_invincible()) {
119 if (state == CRUSHING)
120 set_state(RECOVERING);
124 if (state == CRUSHING)
125 set_state(RECOVERING);
128 BadGuy* badguy = dynamic_cast<BadGuy*>(&other);
136 IceCrusher::collision_solid(const CollisionHit& hit)
143 if (ic_size == LARGE) {
144 cooldown_timer = PAUSE_TIME_LARGE;
145 Sector::current()->camera->shake (/* frequency = */ .125f, /* x = */ 0.0, /* y = */ 16.0);
146 sound_manager->play("sounds/brick.wav");
147 // throw some particles, bigger and more for large icecrusher
148 for(int j = 0; j < 9; j++)
150 Sector::current()->add_object(
151 new Particles(Vector(get_bbox().p2.x - j*8 - 4, get_bbox().p2.y),
152 0, 90-5*j, Vector(140, -380), Vector(0, 300),
153 1, Color(.6f, .6f, .6f), 5, 1.8f, LAYER_OBJECTS+1));
154 Sector::current()->add_object(
155 new Particles(Vector(get_bbox().p1.x + j*8 + 4, get_bbox().p2.y),
156 270+5*j, 360, Vector(140, -380), Vector(0, 300),
157 1, Color(.6f, .6f, .6f), 5, 1.8f, LAYER_OBJECTS+1));
161 cooldown_timer = PAUSE_TIME_NORMAL;
162 Sector::current()->camera->shake (/* frequency = */ .1f, /* x = */ 0.0, /* y = */ 8.0);
163 sound_manager->play("sounds/brick.wav");
164 // throw some particles
165 for(int j = 0; j < 5; j++)
167 Sector::current()->add_object(
168 new Particles(Vector(get_bbox().p2.x - j*8 - 4, get_bbox().p2.y),
169 0, 90+10*j, Vector(140, -260), Vector(0, 300),
170 1, Color(.6f, .6f, .6f), 4, 1.6f, LAYER_OBJECTS+1));
171 Sector::current()->add_object(
172 new Particles(Vector(get_bbox().p1.x + j*8 + 4, get_bbox().p2.y),
173 270+10*j, 360, Vector(140, -260), Vector(0, 300),
174 1, Color(.6f, .6f, .6f), 4, 1.6f, LAYER_OBJECTS+1));
177 set_state(RECOVERING);
183 log_debug << "IceCrusher in invalid state" << std::endl;
189 IceCrusher::update(float elapsed_time)
191 if (cooldown_timer >= elapsed_time)
193 cooldown_timer -= elapsed_time;
196 else if (cooldown_timer != 0.0)
198 elapsed_time -= cooldown_timer;
199 cooldown_timer = 0.0;
204 movement = Vector (0, 0);
209 movement = physic.get_movement (elapsed_time);
210 if (movement.y > MAX_DROP_SPEED)
211 movement.y = MAX_DROP_SPEED;
214 if (get_bbox().p1.y <= start_position.y+1) {
215 set_pos(start_position);
216 movement = Vector (0, 0);
217 if (ic_size == LARGE)
218 cooldown_timer = PAUSE_TIME_LARGE;
220 cooldown_timer = PAUSE_TIME_NORMAL;
224 if (ic_size == LARGE)
225 movement = Vector (0, RECOVER_SPEED_LARGE);
227 movement = Vector (0, RECOVER_SPEED_NORMAL);
231 log_debug << "IceCrusher in invalid state" << std::endl;
237 IceCrusher::draw(DrawingContext& context)
239 context.push_target();
240 context.set_target(DrawingContext::NORMAL);
241 sprite->draw(context, get_pos(), layer);
242 if(!(state == CRUSHING) && sprite->has_action("whites"))
244 // draw icecrusher's eyes slightly behind
245 lefteye->draw(context, get_pos()+eye_position(false), layer-1);
246 righteye->draw(context, get_pos()+eye_position(true), layer-1);
247 // draw the whites of icecrusher's eyes even further behind
248 whites->draw(context, get_pos(), layer-2);
250 context.pop_target();
254 IceCrusher::found_victim()
256 Player* player = Sector::current()->get_nearest_player (this->get_bbox ());
257 if (!player) return false;
259 const Rectf& player_bbox = player->get_bbox();
260 const Rectf& crusher_bbox = get_bbox();
261 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));
262 if ((player_bbox.p1.y >= crusher_bbox.p2.y) /* player is below crusher */
263 && (player_bbox.p2.x > (crusher_bbox.p1.x - DROP_ACTIVATION_DISTANCE))
264 && (player_bbox.p1.x < (crusher_bbox.p2.x + DROP_ACTIVATION_DISTANCE))
265 && (Sector::current()->is_free_of_statics(crush_area, this, false))/* and area to player is free of objects */)
272 IceCrusher::eye_position(bool right)
276 Player* player = Sector::current()->get_nearest_player (this->get_bbox ());
279 // Icecrusher focuses on approximate position of player's head
280 const float player_focus_x = (player->get_bbox().p2.x + player->get_bbox().p1.x) * 0.5;
281 const float player_focus_y = player->get_bbox().p2.y * 0.25 + player->get_bbox().p1.y * 0.75;
282 // Icecrusher's approximate origin of line-of-sight
283 const float crusher_origin_x = (get_bbox().p2.x + get_bbox().p1.x) * 0.5;
284 const float crusher_origin_y = (get_bbox().p2.y + get_bbox().p1.y) * 0.5;
285 // Line-of-sight displacement from icecrusher to player
286 const float displacement_x = player_focus_x - crusher_origin_x;
287 const float displacement_y = player_focus_y - crusher_origin_y;
288 const float displacement_mag = pow(pow(displacement_x, 2.0) + pow(displacement_y, 2.0), 0.5);
289 // Determine weighting for eye displacement along x given icecrusher eye shape
290 int weight_x = sprite->get_width()/64 * (((displacement_x > 0) == right) ? 1 : 4);
291 int weight_y = sprite->get_width()/64 * 2;
293 return Vector(displacement_x/displacement_mag * weight_x, displacement_y/displacement_mag * weight_y - weight_y);
296 else if(state == RECOVERING)
298 // Eyes spin while icecrusher is recovering, giving a dazed impression
299 return Vector(sin((right ? 1 : -1) * // X motion of each eye is opposite of the other
300 (get_pos().y/13 - // Phase factor due to y position
301 (ic_size==NORMAL ? RECOVER_SPEED_NORMAL : RECOVER_SPEED_LARGE) + cooldown_timer*13)) * //Phase factor due to cooldown timer
302 sprite->get_width()/64 * 2 - (right ? 1 : -1) * // Amplitude dependent on size
303 sprite->get_width()/64 * 2, // Offset to keep eyes visible
304 cos((right ? 3.1415 : 0) + // Eyes spin out of phase of eachother
305 get_pos().y/13 - // Phase factor due to y position
306 (ic_size==NORMAL ? RECOVER_SPEED_NORMAL : RECOVER_SPEED_LARGE) + cooldown_timer*13) * //Phase factor due to cooldown timer
307 sprite->get_width()/64 * 2 - // Amplitude dependent on size
308 sprite->get_width()/64 * 2); // Offset to keep eyes visible