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