Patch by mathnerd314 <man.is.allan@gmail.com> resolves issue 0000302: Mr. Ice Block...
[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(float center_of_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   float offset = (get_bbox().get_middle().x - center_of_hitter)*2 / get_bbox().get_width();
152   sprite->set_angle(BUMP_ROTATION_ANGLE*offset);
153 }
154
155 void
156 Block::start_break(float center_of_hitter)
157 {
158   start_bounce(center_of_hitter);
159   breaking = true;
160 }
161
162 //---------------------------------------------------------------------------
163
164 BonusBlock::BonusBlock(const Vector& pos, int data)
165   : Block(sprite_manager->create("images/objects/bonus_block/bonusblock.sprite")), object(0)
166 {
167   bbox.set_pos(pos);
168   sprite->set_action("normal");
169   switch(data) {
170     case 1: contents = CONTENT_COIN; break;
171     case 2: contents = CONTENT_FIREGROW; break;
172     case 3: contents = CONTENT_STAR; break;
173     case 4: contents = CONTENT_1UP; break;
174     case 5: contents = CONTENT_ICEGROW; break;
175     default:
176       log_warning << "Invalid box contents" << std::endl;
177       contents = CONTENT_COIN;
178       break;
179   }
180 }
181
182 BonusBlock::BonusBlock(const lisp::Lisp& lisp)
183   : Block(sprite_manager->create("images/objects/bonus_block/bonusblock.sprite"))
184 {
185   Vector pos;
186
187   contents = CONTENT_COIN;
188   lisp::ListIterator iter(&lisp);
189   while(iter.next()) {
190     const std::string& token = iter.item();
191     if(token == "x") {
192       iter.value()->get(pos.x);
193     } else if(token == "y") {
194       iter.value()->get(pos.y);
195     } else if(token == "contents") {
196       std::string contentstring;
197       iter.value()->get(contentstring);
198       if(contentstring == "coin") {
199         contents = CONTENT_COIN;
200       } else if(contentstring == "firegrow") {
201         contents = CONTENT_FIREGROW;
202       } else if(contentstring == "icegrow") {
203         contents = CONTENT_ICEGROW;
204       } else if(contentstring == "star") {
205         contents = CONTENT_STAR;
206       } else if(contentstring == "1up") {
207         contents = CONTENT_1UP;
208       } else if(contentstring == "custom") {
209         contents = CONTENT_CUSTOM;
210       } else {
211         log_warning << "Invalid box contents '" << contentstring << "'" << std::endl;
212       }
213     } else {
214       if(contents == CONTENT_CUSTOM) {
215         GameObject* game_object = create_object(token, *(iter.lisp()));
216         object = dynamic_cast<MovingObject*> (game_object);
217         if(object == 0)
218           throw std::runtime_error(
219             "Only MovingObjects are allowed inside BonusBlocks");
220       } else {
221         log_warning << "Invalid element '" << token << "' in bonusblock" << std::endl;
222       }
223     }
224   }
225
226   if(contents == CONTENT_CUSTOM && object == 0)
227     throw std::runtime_error("Need to specify content object for custom block");
228
229   bbox.set_pos(pos);
230 }
231
232 BonusBlock::~BonusBlock()
233 {
234   delete object;
235 }
236
237 void
238 BonusBlock::hit(Player& )
239 {
240   try_open();
241 }
242
243 HitResponse
244 BonusBlock::collision(GameObject& other, const CollisionHit& hit){
245     BadGuy* badguy = dynamic_cast<BadGuy*> (&other);
246     if(badguy) {
247       // hit contains no information for collisions with blocks.
248       // Badguy's bottom has to be below the top of the bonusblock
249       // +7 is required to slide over one tile gaps.
250       if( badguy->can_break() && ( badguy->get_bbox().get_bottom() > get_bbox().get_top() + 7.0) ){
251         try_open();
252       }
253     }
254     Portable* portable = dynamic_cast<Portable*> (&other);
255     if(portable) {
256       MovingObject* moving = dynamic_cast<MovingObject*> (&other);
257       if(moving->get_bbox().get_top() > get_bbox().get_bottom() - 7.0) {
258         try_open();
259       }
260     }
261     return Block::collision(other, hit);
262 }
263
264 void
265 BonusBlock::try_open()
266 {
267   if(sprite->get_action() == "empty") {
268     sound_manager->play("sounds/brick.wav");
269     return;
270   }
271
272   Sector* sector = Sector::current();
273   assert(sector);
274   assert(sector->player);
275   Player& player = *(sector->player);
276   Direction direction = (player.get_bbox().get_middle().x > get_bbox().get_middle().x) ? LEFT : RIGHT;
277
278   switch(contents) {
279     case CONTENT_COIN:
280       Sector::current()->add_object(new BouncyCoin(get_pos(), true));
281       player.get_status()->add_coins(1);
282       Sector::current()->get_level()->stats.coins++;
283       break;
284
285     case CONTENT_FIREGROW:
286       if(player.get_status()->bonus == NO_BONUS) {
287         SpecialRiser* riser = new SpecialRiser(get_pos(), new GrowUp(direction));
288         sector->add_object(riser);
289       } else {
290         SpecialRiser* riser = new SpecialRiser(
291             get_pos(), new Flower(FIRE_BONUS));
292         sector->add_object(riser);
293       }
294       sound_manager->play("sounds/upgrade.wav");
295       break;
296
297     case CONTENT_ICEGROW:
298       if(player.get_status()->bonus == NO_BONUS) {
299         SpecialRiser* riser = new SpecialRiser(get_pos(), new GrowUp(direction));
300         sector->add_object(riser);
301       } else {
302         SpecialRiser* riser = new SpecialRiser(
303             get_pos(), new Flower(ICE_BONUS));
304         sector->add_object(riser);
305       }
306       sound_manager->play("sounds/upgrade.wav");
307       break;
308
309     case CONTENT_STAR:
310       sector->add_object(new Star(get_pos() + Vector(0, -32), direction));
311       break;
312
313     case CONTENT_1UP:
314       sector->add_object(new OneUp(get_pos(), direction));
315       break;
316
317     case CONTENT_CUSTOM:
318       SpecialRiser* riser = new SpecialRiser(get_pos(), object);
319       object = 0;
320       sector->add_object(riser);
321       sound_manager->play("sounds/upgrade.wav");
322       break;
323   }
324
325   start_bounce(player.get_bbox().get_middle().x);
326   sprite->set_action("empty");
327 }
328
329 void
330 Block::break_me()
331 {
332   Sector* sector = Sector::current();
333   sector->add_object(
334       new BrokenBrick(new Sprite(*sprite), get_pos(), Vector(-100, -400)));
335   sector->add_object(
336       new BrokenBrick(new Sprite(*sprite), get_pos() + Vector(0, 16),
337         Vector(-150, -300)));
338   sector->add_object(
339       new BrokenBrick(new Sprite(*sprite), get_pos() + Vector(16, 0),
340         Vector(100, -400)));
341   sector->add_object(
342       new BrokenBrick(new Sprite(*sprite), get_pos() + Vector(16, 16),
343         Vector(150, -300)));
344   remove_me();
345 }
346
347 IMPLEMENT_FACTORY(BonusBlock, "bonusblock");
348
349 //---------------------------------------------------------------------------
350
351 Brick::Brick(const Vector& pos, int data)
352   : Block(sprite_manager->create("images/objects/bonus_block/brick.sprite")), breakable(false),
353     coin_counter(0)
354 {
355   bbox.set_pos(pos);
356   if(data == 1)
357     coin_counter = 5;
358   else
359     breakable = true;
360 }
361
362 void
363 Brick::hit(Player& player)
364 {
365   if(sprite->get_action() == "empty")
366     return;
367
368   try_break(&player);
369 }
370
371 HitResponse
372 Brick::collision(GameObject& other, const CollisionHit& hit){
373
374     Player* player = dynamic_cast<Player*> (&other);
375     if (player) {
376       if (player->does_buttjump) try_break();
377     }
378
379     BadGuy* badguy = dynamic_cast<BadGuy*> (&other);
380     if(badguy) {
381       // hit contains no information for collisions with blocks.
382       // Badguy's bottom has to be below the top of the brick
383       // +7 is required to slide over one tile gaps.
384       if( badguy->can_break() && ( badguy->get_bbox().get_bottom() > get_bbox().get_top() + 7.0 ) ){
385         try_break();
386       }
387     }
388     Portable* portable = dynamic_cast<Portable*> (&other);
389     if(portable) {
390       MovingObject* moving = dynamic_cast<MovingObject*> (&other);
391       if(moving->get_bbox().get_top() > get_bbox().get_bottom() - 7.0) {
392         try_break();
393       }
394     }
395    return Block::collision(other, hit);
396 }
397
398 void
399 Brick::try_break(Player* player)
400 {
401   if(sprite->get_action() == "empty")
402     return;
403
404   sound_manager->play("sounds/brick.wav");
405   Sector* sector = Sector::current();
406   Player& player_one = *(sector->player);
407   if(coin_counter > 0) {
408     sector->add_object(new BouncyCoin(get_pos(),true));
409     coin_counter--;
410     player_one.get_status()->add_coins(1);
411     if(coin_counter == 0)
412       sprite->set_action("empty");
413     start_bounce(player->get_bbox().get_middle().x);
414   } else if(breakable) {
415     if(player){
416       if(player->is_big()){
417         start_break(player->get_bbox().get_middle().x);
418         return;
419       } else {
420         start_bounce(player->get_bbox().get_middle().x);
421         return;
422       }
423     }
424    break_me();
425   }
426 }
427
428 //IMPLEMENT_FACTORY(Brick, "brick");