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