Random stuff that I should have committed ages ago.
[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     BadGuy* badguy = dynamic_cast<BadGuy*> (&other);
249     if(badguy) {
250       // hit contains no information for collisions with blocks.
251       // Badguy's bottom has to be below the top of the bonusblock
252       // SHIFT_DELTA is required to slide over one tile gaps.
253       if( badguy->can_break() && ( badguy->get_bbox().get_bottom() > get_bbox().get_top() + SHIFT_DELTA) ){
254         try_open();
255       }
256     }
257     Portable* portable = dynamic_cast<Portable*> (&other);
258     if(portable) {
259       MovingObject* moving = dynamic_cast<MovingObject*> (&other);
260       if(moving->get_bbox().get_top() > get_bbox().get_bottom() - SHIFT_DELTA) {
261         try_open();
262       }
263     }
264     return Block::collision(other, hit);
265 }
266
267 void
268 BonusBlock::try_open()
269 {
270   if(sprite->get_action() == "empty") {
271     sound_manager->play("sounds/brick.wav");
272     return;
273   }
274
275   Sector* sector = Sector::current();
276   assert(sector);
277   assert(sector->player);
278   Player& player = *(sector->player);
279   Direction direction = (player.get_bbox().get_middle().x > get_bbox().get_middle().x) ? LEFT : RIGHT;
280
281   switch(contents) {
282     case CONTENT_COIN:
283       Sector::current()->add_object(new BouncyCoin(get_pos(), true));
284       player.get_status()->add_coins(1);
285       Sector::current()->get_level()->stats.coins++;
286       break;
287
288     case CONTENT_FIREGROW:
289       if(player.get_status()->bonus == NO_BONUS) {
290         SpecialRiser* riser = new SpecialRiser(get_pos(), new GrowUp(direction));
291         sector->add_object(riser);
292       } else {
293         SpecialRiser* riser = new SpecialRiser(
294             get_pos(), new Flower(FIRE_BONUS));
295         sector->add_object(riser);
296       }
297       sound_manager->play("sounds/upgrade.wav");
298       break;
299
300     case CONTENT_ICEGROW:
301       if(player.get_status()->bonus == NO_BONUS) {
302         SpecialRiser* riser = new SpecialRiser(get_pos(), new GrowUp(direction));
303         sector->add_object(riser);
304       } else {
305         SpecialRiser* riser = new SpecialRiser(
306             get_pos(), new Flower(ICE_BONUS));
307         sector->add_object(riser);
308       }
309       sound_manager->play("sounds/upgrade.wav");
310       break;
311
312     case CONTENT_STAR:
313       sector->add_object(new Star(get_pos() + Vector(0, -32), direction));
314       break;
315
316     case CONTENT_1UP:
317       sector->add_object(new OneUp(get_pos(), direction));
318       break;
319
320     case CONTENT_CUSTOM:
321       SpecialRiser* riser = new SpecialRiser(get_pos(), object);
322       object = 0;
323       sector->add_object(riser);
324       sound_manager->play("sounds/upgrade.wav");
325       break;
326   }
327
328   start_bounce(&player);
329   sprite->set_action("empty");
330 }
331
332 void
333 Block::break_me()
334 {
335   Sector* sector = Sector::current();
336   sector->add_object(
337       new BrokenBrick(new Sprite(*sprite), get_pos(), Vector(-100, -400)));
338   sector->add_object(
339       new BrokenBrick(new Sprite(*sprite), get_pos() + Vector(0, 16),
340         Vector(-150, -300)));
341   sector->add_object(
342       new BrokenBrick(new Sprite(*sprite), get_pos() + Vector(16, 0),
343         Vector(100, -400)));
344   sector->add_object(
345       new BrokenBrick(new Sprite(*sprite), get_pos() + Vector(16, 16),
346         Vector(150, -300)));
347   remove_me();
348 }
349
350 IMPLEMENT_FACTORY(BonusBlock, "bonusblock");
351
352 //---------------------------------------------------------------------------
353
354 Brick::Brick(const Vector& pos, int data)
355   : Block(sprite_manager->create("images/objects/bonus_block/brick.sprite")), breakable(false),
356     coin_counter(0)
357 {
358   bbox.set_pos(pos);
359   if(data == 1)
360     coin_counter = 5;
361   else
362     breakable = true;
363 }
364
365 void
366 Brick::hit(Player& player)
367 {
368   if(sprite->get_action() == "empty")
369     return;
370
371   try_break(&player);
372 }
373
374 HitResponse
375 Brick::collision(GameObject& other, const CollisionHit& hit){
376
377     Player* player = dynamic_cast<Player*> (&other);
378     if (player) {
379       if (player->does_buttjump) try_break();
380     }
381
382     BadGuy* badguy = dynamic_cast<BadGuy*> (&other);
383     if(badguy) {
384       // hit contains no information for collisions with blocks.
385       // Badguy's bottom has to be below the top of the brick
386       // SHIFT_DELTA is required to slide over one tile gaps.
387       if( badguy->can_break() && ( badguy->get_bbox().get_bottom() > get_bbox().get_top() + SHIFT_DELTA ) ){
388         try_break();
389       }
390     }
391     Portable* portable = dynamic_cast<Portable*> (&other);
392     if(portable) {
393       MovingObject* moving = dynamic_cast<MovingObject*> (&other);
394       if(moving->get_bbox().get_top() > get_bbox().get_bottom() - SHIFT_DELTA) {
395         try_break();
396       }
397     }
398    return Block::collision(other, hit);
399 }
400
401 void
402 Brick::try_break(Player* player)
403 {
404   if(sprite->get_action() == "empty")
405     return;
406
407   sound_manager->play("sounds/brick.wav");
408   Sector* sector = Sector::current();
409   Player& player_one = *(sector->player);
410   if(coin_counter > 0) {
411     sector->add_object(new BouncyCoin(get_pos(),true));
412     coin_counter--;
413     player_one.get_status()->add_coins(1);
414     if(coin_counter == 0)
415       sprite->set_action("empty");
416     start_bounce(player);
417   } else if(breakable) {
418     if(player){
419       if(player->is_big()){
420         start_break(player);
421         return;
422       } else {
423         start_bounce(player);
424         return;
425       }
426     }
427    break_me();
428   }
429 }
430
431 //IMPLEMENT_FACTORY(Brick, "brick");