dd2e11c8455183208a53d8a5812a867689af5ec3
[supertux.git] / src / object / block.cpp
1 //  $Id$
2 // 
3 //  SuperTux
4 //  Copyright (C) 2005 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
19 //  02111-1307, USA.
20 #include <config.h>
21
22 #include "block.hpp"
23
24 #include <stdexcept>
25
26 #include "resources.hpp"
27 #include "player.hpp"
28 #include "sector.hpp"
29 #include "sprite/sprite.hpp"
30 #include "sprite/sprite_manager.hpp"
31 #include "video/drawing_context.hpp"
32 #include "lisp/lisp.hpp"
33 #include "gameobjs.hpp"
34 #include "specialriser.hpp"
35 #include "growup.hpp"
36 #include "flower.hpp"
37 #include "oneup.hpp"
38 #include "star.hpp"
39 #include "player_status.hpp"
40 #include "badguy/badguy.hpp"
41 #include "coin.hpp"
42 #include "object_factory.hpp"
43 #include "lisp/list_iterator.hpp"
44 #include "object_factory.hpp"
45
46 static const float BOUNCY_BRICK_MAX_OFFSET=8;
47 static const float BOUNCY_BRICK_SPEED=90;
48 static const float EPSILON = .0001;
49
50 Block::Block(Sprite* newsprite)
51   : sprite(newsprite), bouncing(false), bounce_dir(0), bounce_offset(0)
52 {
53   bbox.set_size(32, 32.1);
54   flags |= FLAG_SOLID;
55 }
56
57 Block::~Block()
58 {
59   delete sprite;
60 }
61
62 HitResponse
63 Block::collision(GameObject& other, const CollisionHit& hitdata)
64 {
65   Player* player = dynamic_cast<Player*> (&other);
66   if(player) {
67     // collided from below?
68     if(hitdata.normal.x == 0 && hitdata.normal.y < 0) {
69       hit(*player);
70     }
71   }
72
73   if(bouncing) {
74     BadGuy* badguy = dynamic_cast<BadGuy*> (&other);
75     if(badguy) {
76       badguy->kill_fall();
77     }
78     Coin* coin = dynamic_cast<Coin*> (&other);
79     if(coin) {
80       coin->collect();
81     }
82   }
83
84   return FORCE_MOVE;
85 }
86
87 void
88 Block::update(float elapsed_time)
89 {
90   if(!bouncing)
91     return;
92   
93   float offset = original_y - get_pos().y;
94   if(offset > BOUNCY_BRICK_MAX_OFFSET) {
95     bounce_dir = BOUNCY_BRICK_SPEED;
96     movement = Vector(0, bounce_dir * elapsed_time);
97   } else if(offset < BOUNCY_BRICK_SPEED * elapsed_time && bounce_dir > 0) {
98     movement = Vector(0, offset);
99     bounce_dir = 0;
100     bouncing = false;
101   } else {
102     movement = Vector(0, bounce_dir * elapsed_time);
103   }
104 }
105
106 void
107 Block::draw(DrawingContext& context)
108 {
109   sprite->draw(context, get_pos(), LAYER_OBJECTS+1);
110 }
111
112 void
113 Block::start_bounce()
114 {
115   original_y = bbox.p1.y;
116   bouncing = true;
117   bounce_dir = -BOUNCY_BRICK_SPEED;
118   bounce_offset = 0;
119 }
120
121 //---------------------------------------------------------------------------
122
123 BonusBlock::BonusBlock(const Vector& pos, int data)
124   : Block(sprite_manager->create("bonusblock")), object(0)
125 {
126   bbox.set_pos(pos);
127   sprite->set_action("normal");
128   switch(data) {
129     case 1: contents = CONTENT_COIN; break;
130     case 2: contents = CONTENT_FIREGROW; break;
131     case 3: contents = CONTENT_STAR; break;
132     case 4: contents = CONTENT_1UP; break;
133     case 5: contents = CONTENT_ICEGROW; break;
134     default:
135       std::cerr << "Invalid box contents!\n";
136       contents = CONTENT_COIN;
137       break;
138   }          
139 }
140
141 BonusBlock::BonusBlock(const lisp::Lisp& lisp)
142   : Block(sprite_manager->create("bonusblock"))
143 {
144   Vector pos;
145
146   contents = CONTENT_COIN;
147   lisp::ListIterator iter(&lisp);
148   while(iter.next()) {
149     const std::string& token = iter.item();
150     if(token == "x") {
151       iter.value()->get(pos.x);
152     } else if(token == "y") {
153       iter.value()->get(pos.y);
154     } else if(token == "contents") {
155       std::string contentstring;
156       iter.value()->get(contentstring);
157       if(contentstring == "coin") {
158         contents = CONTENT_COIN;
159       } else if(contentstring == "firegrow") {
160         contents = CONTENT_FIREGROW;
161       } else if(contentstring == "icegrow") {
162         contents = CONTENT_ICEGROW;
163       } else if(contentstring == "star") {
164         contents = CONTENT_STAR;
165       } else if(contentstring == "1up") {
166         contents = CONTENT_1UP;
167       } else if(contentstring == "custom") {
168         contents = CONTENT_CUSTOM;
169       } else {
170         std::cerr << "Invalid box contents '" << contentstring << "'.\n";
171       }
172     } else {
173       if(contents == CONTENT_CUSTOM) {
174         GameObject* game_object = create_object(token, *(iter.lisp()));
175         object = dynamic_cast<MovingObject*> (game_object);
176         if(object == 0)
177           throw std::runtime_error(
178             "Only MovingObjects are allowed inside BonusBlocks");
179       } else {
180         std::cerr << "Invalid element '" << token << "' in bonusblock.\n";
181       }
182     }  
183   }
184
185   if(contents == CONTENT_CUSTOM && object == 0)
186     throw std::runtime_error("Need to specify content object for custom block");
187   
188   bbox.set_pos(pos);
189 }
190
191 BonusBlock::~BonusBlock()
192 {
193   delete object;
194 }
195
196 void
197 BonusBlock::hit(Player& )
198 {
199   try_open();
200 }
201
202 void
203 BonusBlock::try_open()
204 {
205   if(sprite->get_action_name() == "empty") {
206     sound_manager->play("sounds/brick.ogg");
207     return;
208   }
209   
210   Sector* sector = Sector::current();
211   Player& player = *(sector->player);
212   switch(contents) {
213     case CONTENT_COIN:
214       Sector::current()->add_object(new BouncyCoin(get_pos()));
215       player.get_status()->incCoins();
216       break;
217
218     case CONTENT_FIREGROW:
219       if(player.get_status()->bonus == NO_BONUS) {
220         SpecialRiser* riser = new SpecialRiser(get_pos(), new GrowUp());
221         sector->add_object(riser);
222       } else {
223         SpecialRiser* riser = new SpecialRiser(
224             get_pos(), new Flower(Flower::FIREFLOWER));
225         sector->add_object(riser);
226       }
227       sound_manager->play("sounds/upgrade.ogg");
228       break;
229
230     case CONTENT_ICEGROW:
231       if(player.get_status()->bonus == NO_BONUS) {
232         SpecialRiser* riser = new SpecialRiser(get_pos(), new GrowUp());
233         sector->add_object(riser);                                            
234       } else {
235         SpecialRiser* riser = new SpecialRiser(
236             get_pos(), new Flower(Flower::ICEFLOWER));
237         sector->add_object(riser);
238       }      
239       sound_manager->play("sounds/upgrade.ogg");
240       break;
241
242     case CONTENT_STAR:
243       sector->add_object(new Star(get_pos() + Vector(0, -32)));
244       break;
245
246     case CONTENT_1UP:
247       sector->add_object(new OneUp(get_pos()));
248       break;
249
250     case CONTENT_CUSTOM:
251       SpecialRiser* riser = new SpecialRiser(get_pos(), object);
252       object = 0;
253       sector->add_object(riser);
254       sound_manager->play("sounds/upgrade.ogg");
255       break;
256
257     //default:
258       //assert(false);
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("brick")), 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_name() == "empty")
284     return;
285   
286   try_break(true);
287 }
288
289 void
290 Brick::try_break(bool playerhit)
291 {
292   if(sprite->get_action_name() == "empty")
293     return;
294   
295   sound_manager->play("sounds/brick.ogg");
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()->incCoins();
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