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