merged new collision detection branch back into mainline
[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 void
206 BonusBlock::try_open()
207 {
208   if(sprite->get_action() == "empty") {
209     sound_manager->play("sounds/brick.wav");
210     return;
211   }
212   
213   Sector* sector = Sector::current();
214   Player& player = *(sector->player);
215   switch(contents) {
216     case CONTENT_COIN:
217       Sector::current()->add_object(new BouncyCoin(get_pos()));
218       player.get_status()->add_coins(1);
219       break;
220
221     case CONTENT_FIREGROW:
222       if(player.get_status()->bonus == NO_BONUS) {
223         SpecialRiser* riser = new SpecialRiser(get_pos(), new GrowUp());
224         sector->add_object(riser);
225       } else {
226         SpecialRiser* riser = new SpecialRiser(
227             get_pos(), new Flower(FIRE_BONUS));
228         sector->add_object(riser);
229       }
230       sound_manager->play("sounds/upgrade.wav");
231       break;
232
233     case CONTENT_ICEGROW:
234       if(player.get_status()->bonus == NO_BONUS) {
235         SpecialRiser* riser = new SpecialRiser(get_pos(), new GrowUp());
236         sector->add_object(riser);                                            
237       } else {
238         SpecialRiser* riser = new SpecialRiser(
239             get_pos(), new Flower(ICE_BONUS));
240         sector->add_object(riser);
241       }      
242       sound_manager->play("sounds/upgrade.wav");
243       break;
244
245     case CONTENT_STAR:
246       sector->add_object(new Star(get_pos() + Vector(0, -32)));
247       break;
248
249     case CONTENT_1UP:
250       sector->add_object(new OneUp(get_pos()));
251       break;
252
253     case CONTENT_CUSTOM:
254       SpecialRiser* riser = new SpecialRiser(get_pos(), object);
255       object = 0;
256       sector->add_object(riser);
257       sound_manager->play("sounds/upgrade.wav");
258       break;
259   }
260
261   start_bounce();
262   sprite->set_action("empty");
263 }
264
265 IMPLEMENT_FACTORY(BonusBlock, "bonusblock");
266
267 //---------------------------------------------------------------------------
268
269 Brick::Brick(const Vector& pos, int data)
270   : Block(sprite_manager->create("images/objects/bonus_block/brick.sprite")), breakable(false),
271     coin_counter(0)
272 {
273   bbox.set_pos(pos);
274   if(data == 1)
275     coin_counter = 5;
276   else
277     breakable = true;
278 }
279
280 void
281 Brick::hit(Player& )
282 {
283   if(sprite->get_action() == "empty")
284     return;
285   
286   try_break(true);
287 }
288
289 void
290 Brick::try_break(bool playerhit)
291 {
292   if(sprite->get_action() == "empty")
293     return;
294   
295   sound_manager->play("sounds/brick.wav");
296   Sector* sector = Sector::current();
297   Player& player = *(sector->player);
298   if(coin_counter > 0) {
299     sector->add_object(new BouncyCoin(get_pos()));
300     coin_counter--;
301     player.get_status()->add_coins(1);
302     if(coin_counter == 0)
303       sprite->set_action("empty");
304     start_bounce();
305   } else if(breakable) {
306     if(playerhit && !player.is_big()) {
307       start_bounce();
308       return;
309     }
310
311     sector->add_object(
312         new BrokenBrick(new Sprite(*sprite), get_pos(), Vector(-100, -400)));
313     sector->add_object(
314         new BrokenBrick(new Sprite(*sprite), get_pos() + Vector(0, 16),
315           Vector(-150, -300)));
316     sector->add_object(
317         new BrokenBrick(new Sprite(*sprite), get_pos() + Vector(16, 0),
318           Vector(100, -400)));
319     sector->add_object(
320         new BrokenBrick(new Sprite(*sprite), get_pos() + Vector(16, 16),
321           Vector(150, -300)));
322     remove_me();
323   }
324 }
325
326 //IMPLEMENT_FACTORY(Brick, "brick");
327