Icecrusher makes noise when hitting and throws up particles when hitting the ground.
[supertux.git] / src / object / icecrusher.cpp
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>
4 //
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.
9 //
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.
14 //
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/>.
17
18 #include "object/icecrusher.hpp"
19
20 #include "audio/sound_manager.hpp"
21 #include "badguy/badguy.hpp"
22 //#include "math/random_generator.hpp"
23 #include "object/camera.hpp"
24 #include "object/particles.hpp"
25 #include "object/player.hpp"
26 #include "sprite/sprite.hpp"
27 #include "supertux/object_factory.hpp"
28 #include "supertux/sector.hpp"
29
30 namespace {
31 /* Maximum movement speed in pixels per LOGICAL_FPS */
32 const float MAX_DROP_SPEED = 10.0;
33 const float RECOVER_SPEED_NORMAL = -3.125;
34 const float RECOVER_SPEED_LARGE  = -2.0;
35 const float DROP_ACTIVATION_DISTANCE = 4.0;
36 const float PAUSE_TIME_NORMAL = 0.5;
37 const float PAUSE_TIME_LARGE  = 1.0;
38 }
39
40 IceCrusher::IceCrusher(const Reader& reader) :
41   MovingSprite(reader, "images/creatures/icecrusher/icecrusher.sprite", LAYER_OBJECTS, COLGROUP_STATIC), 
42   state(IDLE), 
43   start_position(),
44   physic(),
45   cooldown_timer(0.0),
46   ic_size(NORMAL)
47 {
48   // TODO: icecrusher hitting deserves its own sounds-
49   // one for hitting the ground, one for hitting Tux
50   sound_manager->preload("sounds/brick.wav");
51
52   start_position = get_bbox().p1;
53   set_state(state, true);
54   
55   float sprite_width = sprite->get_width ();
56   if (sprite_width >= 128.0)
57     ic_size = LARGE;
58 }
59
60 /*
61   IceCrusher::IceCrusher(const IceCrusher& other)
62   : MovingSprite(other), 
63   state(other.state), speed(other.speed) 
64   {
65   start_position = get_bbox().p1;
66   set_state(state, true);
67   }
68 */
69 void 
70 IceCrusher::set_state(IceCrusherState state, bool force) 
71 {
72   if ((this->state == state) && (!force)) return;
73   switch(state) {
74     case IDLE:
75       set_group(COLGROUP_STATIC);
76       physic.enable_gravity (false);
77       sprite->set_action("idle");
78       break;
79     case CRUSHING:
80       set_group(COLGROUP_MOVING_STATIC);
81       physic.reset ();
82       physic.enable_gravity (true);
83       sprite->set_action("crushing");
84       break;
85     case RECOVERING:
86       set_group(COLGROUP_MOVING_STATIC);
87       physic.enable_gravity (false);
88       sprite->set_action("recovering");
89       break;
90     default:
91       log_debug << "IceCrusher in invalid state" << std::endl;
92       break;
93   }
94   this->state = state;
95 }
96
97 HitResponse
98 IceCrusher::collision(GameObject& other, const CollisionHit& hit)
99 {
100   Player* player = dynamic_cast<Player*>(&other);
101
102   /* If the other object is the player, and the collision is at the bottom of
103    * the ice crusher, hurt the player. */
104   if (player && hit.bottom) {
105     sound_manager->play("sounds/brick.wav");
106     if(player->is_invincible()) {
107       if (state == CRUSHING)
108         set_state(RECOVERING);
109       return ABORT_MOVE;
110     }
111     player->kill(false);
112     if (state == CRUSHING)
113       set_state(RECOVERING);
114     return FORCE_MOVE;
115   }
116   BadGuy* badguy = dynamic_cast<BadGuy*>(&other);
117   if (badguy) {
118     badguy->kill_fall();
119   }
120   return FORCE_MOVE;
121 }
122     
123 void 
124 IceCrusher::collision_solid(const CollisionHit& hit)
125 {
126   switch(state) {
127     case IDLE:
128       break;
129     case CRUSHING:
130       if (hit.bottom) {
131         if (ic_size == LARGE) {
132           cooldown_timer = PAUSE_TIME_LARGE;
133           Sector::current()->camera->shake (/* frequency = */ .125f, /* x = */ 0.0, /* y = */ 16.0);
134           sound_manager->play("sounds/brick.wav");
135           // throw some particles, bigger and more for large icecrusher
136           for(int j = 0; j < 9; j++)
137           {
138           Sector::current()->add_object(
139             new Particles(Vector(get_bbox().p2.x - j*8 - 4, get_bbox().p2.y),
140               0, 90-5*j, Vector(140, -380), Vector(0, 300),
141               1, Color(.6f, .6f, .6f), 5, 1.8f, LAYER_OBJECTS+1));
142           Sector::current()->add_object(
143             new Particles(Vector(get_bbox().p1.x + j*8 + 4, get_bbox().p2.y),
144               270+5*j, 360, Vector(140, -380), Vector(0, 300),
145               1, Color(.6f, .6f, .6f), 5, 1.8f, LAYER_OBJECTS+1));
146           }
147         }
148         else {
149           cooldown_timer = PAUSE_TIME_NORMAL;
150           Sector::current()->camera->shake (/* frequency = */ .1f, /* x = */ 0.0, /* y = */ 8.0);
151           sound_manager->play("sounds/brick.wav");
152           // throw some particles
153           for(int j = 0; j < 5; j++)
154           {
155           Sector::current()->add_object(
156             new Particles(Vector(get_bbox().p2.x - j*8 - 4, get_bbox().p2.y),
157               0, 90+10*j, Vector(140, -260), Vector(0, 300),
158               1, Color(.6f, .6f, .6f), 4, 1.6f, LAYER_OBJECTS+1));
159           Sector::current()->add_object(
160             new Particles(Vector(get_bbox().p1.x + j*8 + 4, get_bbox().p2.y),
161               270+10*j, 360, Vector(140, -260), Vector(0, 300),
162               1, Color(.6f, .6f, .6f), 4, 1.6f, LAYER_OBJECTS+1));
163           }
164         }
165         set_state(RECOVERING);
166       }
167       break;
168     case RECOVERING:
169       break;
170     default:
171       log_debug << "IceCrusher in invalid state" << std::endl;
172       break;
173   }
174 }
175
176 void
177 IceCrusher::update(float elapsed_time)
178 {
179   if (cooldown_timer >= elapsed_time)
180   {
181     cooldown_timer -= elapsed_time;
182     return;
183   }
184   else if (cooldown_timer != 0.0)
185   {
186     elapsed_time -= cooldown_timer;
187     cooldown_timer = 0.0;
188   }
189
190   switch(state) {
191     case IDLE:
192       movement = Vector (0, 0);
193       if (found_victim())
194         set_state(CRUSHING);
195       break;
196     case CRUSHING:
197       movement = physic.get_movement (elapsed_time);
198       if (movement.y > MAX_DROP_SPEED)
199         movement.y = MAX_DROP_SPEED;
200       break;
201     case RECOVERING:
202       if (get_bbox().p1.y <= start_position.y+1) {
203         set_pos(start_position);
204         movement = Vector (0, 0);
205         if (ic_size == LARGE)
206           cooldown_timer = PAUSE_TIME_LARGE;
207         else
208           cooldown_timer = PAUSE_TIME_NORMAL;
209         set_state(IDLE);
210       }
211       else {
212         if (ic_size == LARGE)
213           movement = Vector (0, RECOVER_SPEED_LARGE);
214         else
215           movement = Vector (0, RECOVER_SPEED_NORMAL);
216       }
217       break;
218     default:
219       log_debug << "IceCrusher in invalid state" << std::endl;
220       break;
221   }
222 }
223
224 bool
225 IceCrusher::found_victim()
226 {
227   Player* player = Sector::current()->get_nearest_player (this->get_bbox ());
228   if (!player) return false;
229
230   const Rectf& player_bbox = player->get_bbox();
231   const Rectf& crusher_bbox = get_bbox();
232   if ((player_bbox.p1.y >= crusher_bbox.p2.y) /* player is below crusher */
233       && (player_bbox.p2.x > (crusher_bbox.p1.x - DROP_ACTIVATION_DISTANCE))
234       && (player_bbox.p1.x < (crusher_bbox.p2.x + DROP_ACTIVATION_DISTANCE)))
235     return true;
236   else
237     return false;
238 }
239
240 /* EOF */