Made code -Wshadow clean, missed a bunch of issues in the last commit
[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 <math.h>
21
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"
31
32 namespace {
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;
40 }
41
42 IceCrusher::IceCrusher(const Reader& reader) :
43   MovingSprite(reader, "images/creatures/icecrusher/icecrusher.sprite", LAYER_OBJECTS, COLGROUP_STATIC),
44   state(IDLE),
45   start_position(),
46   physic(),
47   cooldown_timer(0.0),
48   lefteye(),
49   righteye(),
50   whites(),
51   ic_size(NORMAL)
52 {
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");
56
57   start_position = get_bbox().p1;
58   set_state(state, true);
59
60   float sprite_width = sprite->get_width ();
61   if (sprite_width >= 128.0)
62     ic_size = LARGE;
63
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");
70 }
71
72 /*
73   IceCrusher::IceCrusher(const IceCrusher& other)
74   : MovingSprite(other),
75   state(other.state), speed(other.speed)
76   {
77   start_position = get_bbox().p1;
78   set_state(state, true);
79   }
80 */
81 void
82 IceCrusher::set_state(IceCrusherState state_, bool force)
83 {
84   if ((this->state == state_) && (!force)) return;
85   switch(state_) {
86     case IDLE:
87       set_group(COLGROUP_STATIC);
88       physic.enable_gravity (false);
89       sprite->set_action("idle");
90       break;
91     case CRUSHING:
92       set_group(COLGROUP_MOVING_STATIC);
93       physic.reset ();
94       physic.enable_gravity (true);
95       sprite->set_action("crushing");
96       break;
97     case RECOVERING:
98       set_group(COLGROUP_MOVING_STATIC);
99       physic.enable_gravity (false);
100       sprite->set_action("recovering");
101       break;
102     default:
103       log_debug << "IceCrusher in invalid state" << std::endl;
104       break;
105   }
106   this->state = state_;
107 }
108
109 HitResponse
110 IceCrusher::collision(GameObject& other, const CollisionHit& hit)
111 {
112   Player* player = dynamic_cast<Player*>(&other);
113
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);
121       return ABORT_MOVE;
122     }
123     player->kill(false);
124     if (state == CRUSHING)
125       set_state(RECOVERING);
126     return FORCE_MOVE;
127   }
128   BadGuy* badguy = dynamic_cast<BadGuy*>(&other);
129   if (badguy) {
130     badguy->kill_fall();
131   }
132   return FORCE_MOVE;
133 }
134
135 void
136 IceCrusher::collision_solid(const CollisionHit& hit)
137 {
138   switch(state) {
139     case IDLE:
140       break;
141     case CRUSHING:
142       if (hit.bottom) {
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++)
149           {
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));
158           }
159         }
160         else {
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++)
166           {
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));
175           }
176         }
177         set_state(RECOVERING);
178       }
179       break;
180     case RECOVERING:
181       break;
182     default:
183       log_debug << "IceCrusher in invalid state" << std::endl;
184       break;
185   }
186 }
187
188 void
189 IceCrusher::update(float elapsed_time)
190 {
191   if (cooldown_timer >= elapsed_time)
192   {
193     cooldown_timer -= elapsed_time;
194     return;
195   }
196   else if (cooldown_timer != 0.0)
197   {
198     elapsed_time -= cooldown_timer;
199     cooldown_timer = 0.0;
200   }
201
202   switch(state) {
203     case IDLE:
204       movement = Vector (0, 0);
205       if (found_victim())
206         set_state(CRUSHING);
207       break;
208     case CRUSHING:
209       movement = physic.get_movement (elapsed_time);
210       if (movement.y > MAX_DROP_SPEED)
211         movement.y = MAX_DROP_SPEED;
212       break;
213     case RECOVERING:
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;
219         else
220           cooldown_timer = PAUSE_TIME_NORMAL;
221         set_state(IDLE);
222       }
223       else {
224         if (ic_size == LARGE)
225           movement = Vector (0, RECOVER_SPEED_LARGE);
226         else
227           movement = Vector (0, RECOVER_SPEED_NORMAL);
228       }
229       break;
230     default:
231       log_debug << "IceCrusher in invalid state" << std::endl;
232       break;
233   }
234 }
235
236 void
237 IceCrusher::draw(DrawingContext& context)
238 {
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"))
243   {
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);
249   }
250   context.pop_target();
251 }
252
253 bool
254 IceCrusher::found_victim()
255 {
256   Player* player = Sector::current()->get_nearest_player (this->get_bbox ());
257   if (!player) return false;
258
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 */)
266     return true;
267   else
268     return false;
269 }
270
271 Vector
272 IceCrusher::eye_position(bool right)
273 {
274   if(state == IDLE)
275   {
276     Player* player = Sector::current()->get_nearest_player (this->get_bbox ());
277     if(player)
278     {
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;
292
293       return Vector(displacement_x/displacement_mag * weight_x, displacement_y/displacement_mag * weight_y - weight_y);
294     }
295   }
296   else if(state == RECOVERING)
297   {
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
309   }
310
311   return Vector(0,0);
312 }
313
314 /* EOF */