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