Fixed crash when using Mr. Iceblock to break a brick
[supertux.git] / src / object / block.cpp
1 //  $Id$
2 //
3 //  SuperTux
4 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19
20 #include <config.h>
21
22 #include "block.hpp"
23 #include "log.hpp"
24
25 #include <stdexcept>
26
27 #include "resources.hpp"
28 #include "player.hpp"
29 #include "sector.hpp"
30 #include "sprite/sprite.hpp"
31 #include "sprite/sprite_manager.hpp"
32 #include "video/drawing_context.hpp"
33 #include "lisp/lisp.hpp"
34 #include "gameobjs.hpp"
35 #include "portable.hpp"
36 #include "moving_object.hpp"
37 #include "specialriser.hpp"
38 #include "growup.hpp"
39 #include "flower.hpp"
40 #include "oneup.hpp"
41 #include "star.hpp"
42 #include "player_status.hpp"
43 #include "badguy/badguy.hpp"
44 #include "coin.hpp"
45 #include "object_factory.hpp"
46 #include "lisp/list_iterator.hpp"
47 #include "object_factory.hpp"
48 #include "level.hpp"
49
50 static const float BOUNCY_BRICK_MAX_OFFSET = 8;
51 static const float BOUNCY_BRICK_SPEED = 90;
52 static const float EPSILON = .0001f;
53 static const float BUMP_ROTATION_ANGLE = 10;
54
55 Block::Block(Sprite* newsprite)
56   : sprite(newsprite), bouncing(false), breaking(false), bounce_dir(0), bounce_offset(0), original_y(-1)
57 {
58   bbox.set_size(32, 32.1f);
59   set_group(COLGROUP_STATIC);
60   sound_manager->preload("sounds/upgrade.wav");
61   sound_manager->preload("sounds/brick.wav");
62 }
63
64 Block::~Block()
65 {
66   delete sprite;
67 }
68
69 HitResponse
70 Block::collision(GameObject& other, const CollisionHit& )
71 {
72   Player* player = dynamic_cast<Player*> (&other);
73   if(player) {
74     if(player->get_bbox().get_top() > get_bbox().get_bottom() - 7.0) {
75       hit(*player);
76     }
77   }
78
79   // only interact with other objects if...
80   //   1) we are bouncing
81   //   2) the object is not portable (either never or not currently)
82   //   3) the object is being hit from below (baguys don't get killed for activating boxes)
83   Portable* portable = dynamic_cast<Portable*> (&other);
84   MovingObject* moving_object = dynamic_cast<MovingObject*> (&other);
85   bool is_portable = ((portable != 0) && portable->is_portable());
86   bool hit_mo_from_below = ((moving_object == 0) || (moving_object->get_bbox().get_bottom() > (get_bbox().get_top() - 7.0)));
87   if(bouncing && !is_portable && hit_mo_from_below) {
88
89     // Badguys get killed
90     BadGuy* badguy = dynamic_cast<BadGuy*> (&other);
91     if(badguy) {
92       badguy->kill_fall();
93     }
94
95     // Coins get collected
96     Coin* coin = dynamic_cast<Coin*> (&other);
97     if(coin) {
98       coin->collect();
99     }
100     
101     //Eggs get jumped
102     GrowUp* growup = dynamic_cast<GrowUp*> (&other);
103     if(growup) {
104       growup->do_jump();
105     }
106
107   }
108
109   return SOLID;
110 }
111
112 void
113 Block::update(float elapsed_time)
114 {
115   if(!bouncing)
116     return;
117
118   float offset = original_y - get_pos().y;
119   if(offset > BOUNCY_BRICK_MAX_OFFSET) {
120     bounce_dir = BOUNCY_BRICK_SPEED;
121     movement = Vector(0, bounce_dir * elapsed_time);
122     if(breaking){
123       break_me();
124     }
125   } else if(offset < BOUNCY_BRICK_SPEED * elapsed_time && bounce_dir > 0) {
126     movement = Vector(0, offset);
127     bounce_dir = 0;
128     bouncing = false;
129     sprite->set_angle(0);
130   } else {
131     movement = Vector(0, bounce_dir * elapsed_time);
132   }
133 }
134
135 void
136 Block::draw(DrawingContext& context)
137 {
138   sprite->draw(context, get_pos(), LAYER_OBJECTS+1);
139 }
140
141 void
142 Block::start_bounce(GameObject* hitter)
143 {
144   if(original_y == -1){
145     original_y = bbox.p1.y;
146   }
147   bouncing = true;
148   bounce_dir = -BOUNCY_BRICK_SPEED;
149   bounce_offset = 0;
150
151   MovingObject* hitter_mo = dynamic_cast<MovingObject*>(hitter);
152   if (hitter_mo) {
153     float center_of_hitter = hitter_mo->get_bbox().get_middle().x;
154     float offset = (get_bbox().get_middle().x - center_of_hitter)*2 / get_bbox().get_width();
155     sprite->set_angle(BUMP_ROTATION_ANGLE*offset);
156   }
157 }
158
159 void
160 Block::start_break(GameObject* hitter)
161 {
162   start_bounce(hitter);
163   breaking = true;
164 }
165
166 //---------------------------------------------------------------------------
167
168 BonusBlock::BonusBlock(const Vector& pos, int data)
169   : Block(sprite_manager->create("images/objects/bonus_block/bonusblock.sprite")), object(0)
170 {
171   bbox.set_pos(pos);
172   sprite->set_action("normal");
173   switch(data) {
174     case 1: contents = CONTENT_COIN; break;
175     case 2: contents = CONTENT_FIREGROW; break;
176     case 3: contents = CONTENT_STAR; break;
177     case 4: contents = CONTENT_1UP; break;
178     case 5: contents = CONTENT_ICEGROW; break;
179     default:
180       log_warning << "Invalid box contents" << std::endl;
181       contents = CONTENT_COIN;
182       break;
183   }
184 }
185
186 BonusBlock::BonusBlock(const lisp::Lisp& lisp)
187   : Block(sprite_manager->create("images/objects/bonus_block/bonusblock.sprite"))
188 {
189   Vector pos;
190
191   contents = CONTENT_COIN;
192   lisp::ListIterator iter(&lisp);
193   while(iter.next()) {
194     const std::string& token = iter.item();
195     if(token == "x") {
196       iter.value()->get(pos.x);
197     } else if(token == "y") {
198       iter.value()->get(pos.y);
199     } else if(token == "contents") {
200       std::string contentstring;
201       iter.value()->get(contentstring);
202       if(contentstring == "coin") {
203         contents = CONTENT_COIN;
204       } else if(contentstring == "firegrow") {
205         contents = CONTENT_FIREGROW;
206       } else if(contentstring == "icegrow") {
207         contents = CONTENT_ICEGROW;
208       } else if(contentstring == "star") {
209         contents = CONTENT_STAR;
210       } else if(contentstring == "1up") {
211         contents = CONTENT_1UP;
212       } else if(contentstring == "custom") {
213         contents = CONTENT_CUSTOM;
214       } else {
215         log_warning << "Invalid box contents '" << contentstring << "'" << std::endl;
216       }
217     } else {
218       if(contents == CONTENT_CUSTOM) {
219         GameObject* game_object = create_object(token, *(iter.lisp()));
220         object = dynamic_cast<MovingObject*> (game_object);
221         if(object == 0)
222           throw std::runtime_error(
223             "Only MovingObjects are allowed inside BonusBlocks");
224       } else {
225         log_warning << "Invalid element '" << token << "' in bonusblock" << std::endl;
226       }
227     }
228   }
229
230   if(contents == CONTENT_CUSTOM && object == 0)
231     throw std::runtime_error("Need to specify content object for custom block");
232
233   bbox.set_pos(pos);
234 }
235
236 BonusBlock::~BonusBlock()
237 {
238   delete object;
239 }
240
241 void
242 BonusBlock::hit(Player& )
243 {
244   try_open();
245 }
246
247 HitResponse
248 BonusBlock::collision(GameObject& other, const CollisionHit& hit){
249     BadGuy* badguy = dynamic_cast<BadGuy*> (&other);
250     if(badguy) {
251       // hit contains no information for collisions with blocks.
252       // Badguy's bottom has to be below the top of the bonusblock
253       // +7 is required to slide over one tile gaps.
254       if( badguy->can_break() && ( badguy->get_bbox().get_bottom() > get_bbox().get_top() + 7.0) ){
255         try_open();
256       }
257     }
258     Portable* portable = dynamic_cast<Portable*> (&other);
259     if(portable) {
260       MovingObject* moving = dynamic_cast<MovingObject*> (&other);
261       if(moving->get_bbox().get_top() > get_bbox().get_bottom() - 7.0) {
262         try_open();
263       }
264     }
265     return Block::collision(other, hit);
266 }
267
268 void
269 BonusBlock::try_open()
270 {
271   if(sprite->get_action() == "empty") {
272     sound_manager->play("sounds/brick.wav");
273     return;
274   }
275
276   Sector* sector = Sector::current();
277   assert(sector);
278   assert(sector->player);
279   Player& player = *(sector->player);
280   Direction direction = (player.get_bbox().get_middle().x > get_bbox().get_middle().x) ? LEFT : RIGHT;
281
282   switch(contents) {
283     case CONTENT_COIN:
284       Sector::current()->add_object(new BouncyCoin(get_pos(), true));
285       player.get_status()->add_coins(1);
286       Sector::current()->get_level()->stats.coins++;
287       break;
288
289     case CONTENT_FIREGROW:
290       if(player.get_status()->bonus == NO_BONUS) {
291         SpecialRiser* riser = new SpecialRiser(get_pos(), new GrowUp(direction));
292         sector->add_object(riser);
293       } else {
294         SpecialRiser* riser = new SpecialRiser(
295             get_pos(), new Flower(FIRE_BONUS));
296         sector->add_object(riser);
297       }
298       sound_manager->play("sounds/upgrade.wav");
299       break;
300
301     case CONTENT_ICEGROW:
302       if(player.get_status()->bonus == NO_BONUS) {
303         SpecialRiser* riser = new SpecialRiser(get_pos(), new GrowUp(direction));
304         sector->add_object(riser);
305       } else {
306         SpecialRiser* riser = new SpecialRiser(
307             get_pos(), new Flower(ICE_BONUS));
308         sector->add_object(riser);
309       }
310       sound_manager->play("sounds/upgrade.wav");
311       break;
312
313     case CONTENT_STAR:
314       sector->add_object(new Star(get_pos() + Vector(0, -32), direction));
315       break;
316
317     case CONTENT_1UP:
318       sector->add_object(new OneUp(get_pos(), direction));
319       break;
320
321     case CONTENT_CUSTOM:
322       SpecialRiser* riser = new SpecialRiser(get_pos(), object);
323       object = 0;
324       sector->add_object(riser);
325       sound_manager->play("sounds/upgrade.wav");
326       break;
327   }
328
329   start_bounce(&player);
330   sprite->set_action("empty");
331 }
332
333 void
334 Block::break_me()
335 {
336   Sector* sector = Sector::current();
337   sector->add_object(
338       new BrokenBrick(new Sprite(*sprite), get_pos(), Vector(-100, -400)));
339   sector->add_object(
340       new BrokenBrick(new Sprite(*sprite), get_pos() + Vector(0, 16),
341         Vector(-150, -300)));
342   sector->add_object(
343       new BrokenBrick(new Sprite(*sprite), get_pos() + Vector(16, 0),
344         Vector(100, -400)));
345   sector->add_object(
346       new BrokenBrick(new Sprite(*sprite), get_pos() + Vector(16, 16),
347         Vector(150, -300)));
348   remove_me();
349 }
350
351 IMPLEMENT_FACTORY(BonusBlock, "bonusblock");
352
353 //---------------------------------------------------------------------------
354
355 Brick::Brick(const Vector& pos, int data)
356   : Block(sprite_manager->create("images/objects/bonus_block/brick.sprite")), breakable(false),
357     coin_counter(0)
358 {
359   bbox.set_pos(pos);
360   if(data == 1)
361     coin_counter = 5;
362   else
363     breakable = true;
364 }
365
366 void
367 Brick::hit(Player& player)
368 {
369   if(sprite->get_action() == "empty")
370     return;
371
372   try_break(&player);
373 }
374
375 HitResponse
376 Brick::collision(GameObject& other, const CollisionHit& hit){
377
378     Player* player = dynamic_cast<Player*> (&other);
379     if (player) {
380       if (player->does_buttjump) try_break();
381     }
382
383     BadGuy* badguy = dynamic_cast<BadGuy*> (&other);
384     if(badguy) {
385       // hit contains no information for collisions with blocks.
386       // Badguy's bottom has to be below the top of the brick
387       // +7 is required to slide over one tile gaps.
388       if( badguy->can_break() && ( badguy->get_bbox().get_bottom() > get_bbox().get_top() + 7.0 ) ){
389         try_break();
390       }
391     }
392     Portable* portable = dynamic_cast<Portable*> (&other);
393     if(portable) {
394       MovingObject* moving = dynamic_cast<MovingObject*> (&other);
395       if(moving->get_bbox().get_top() > get_bbox().get_bottom() - 7.0) {
396         try_break();
397       }
398     }
399    return Block::collision(other, hit);
400 }
401
402 void
403 Brick::try_break(Player* player)
404 {
405   if(sprite->get_action() == "empty")
406     return;
407
408   sound_manager->play("sounds/brick.wav");
409   Sector* sector = Sector::current();
410   Player& player_one = *(sector->player);
411   if(coin_counter > 0) {
412     sector->add_object(new BouncyCoin(get_pos(),true));
413     coin_counter--;
414     player_one.get_status()->add_coins(1);
415     if(coin_counter == 0)
416       sprite->set_action("empty");
417     start_bounce(player);
418   } else if(breakable) {
419     if(player){
420       if(player->is_big()){
421         start_break(player);
422         return;
423       } else {
424         start_bounce(player);
425         return;
426       }
427     }
428    break_me();
429   }
430 }
431
432 //IMPLEMENT_FACTORY(Brick, "brick");