Changed collision code, we now have several collision groups:
[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   set_group(COLGROUP_STATIC);
55   flags |= FLAG_SOLID;
56 }
57
58 Block::~Block()
59 {
60   delete sprite;
61 }
62
63 HitResponse
64 Block::collision(GameObject& other, const CollisionHit& hitdata)
65 {
66   Player* player = dynamic_cast<Player*> (&other);
67   if(player) {
68     // collided from below?
69     if(hitdata.normal.x == 0 && hitdata.normal.y < 0) {
70       hit(*player);
71     }
72   }
73
74   if(bouncing) {
75     BadGuy* badguy = dynamic_cast<BadGuy*> (&other);
76     if(badguy) {
77       badguy->kill_fall();
78     }
79     Coin* coin = dynamic_cast<Coin*> (&other);
80     if(coin) {
81       coin->collect();
82     }
83   }
84
85   return FORCE_MOVE;
86 }
87
88 void
89 Block::update(float elapsed_time)
90 {
91   if(!bouncing)
92     return;
93   
94   float offset = original_y - get_pos().y;
95   if(offset > BOUNCY_BRICK_MAX_OFFSET) {
96     bounce_dir = BOUNCY_BRICK_SPEED;
97     movement = Vector(0, bounce_dir * elapsed_time);
98   } else if(offset < BOUNCY_BRICK_SPEED * elapsed_time && bounce_dir > 0) {
99     movement = Vector(0, offset);
100     bounce_dir = 0;
101     bouncing = false;
102   } else {
103     movement = Vector(0, bounce_dir * elapsed_time);
104   }
105 }
106
107 void
108 Block::draw(DrawingContext& context)
109 {
110   sprite->draw(context, get_pos(), LAYER_OBJECTS+1);
111 }
112
113 void
114 Block::start_bounce()
115 {
116   original_y = bbox.p1.y;
117   bouncing = true;
118   bounce_dir = -BOUNCY_BRICK_SPEED;
119   bounce_offset = 0;
120 }
121
122 //---------------------------------------------------------------------------
123
124 BonusBlock::BonusBlock(const Vector& pos, int data)
125   : Block(sprite_manager->create("bonusblock")), object(0)
126 {
127   bbox.set_pos(pos);
128   sprite->set_action("normal");
129   switch(data) {
130     case 1: contents = CONTENT_COIN; break;
131     case 2: contents = CONTENT_FIREGROW; break;
132     case 3: contents = CONTENT_STAR; break;
133     case 4: contents = CONTENT_1UP; break;
134     case 5: contents = CONTENT_ICEGROW; break;
135     default:
136       std::cerr << "Invalid box contents!\n";
137       contents = CONTENT_COIN;
138       break;
139   }          
140 }
141
142 BonusBlock::BonusBlock(const lisp::Lisp& lisp)
143   : Block(sprite_manager->create("bonusblock"))
144 {
145   Vector pos;
146
147   contents = CONTENT_COIN;
148   lisp::ListIterator iter(&lisp);
149   while(iter.next()) {
150     const std::string& token = iter.item();
151     if(token == "x") {
152       iter.value()->get(pos.x);
153     } else if(token == "y") {
154       iter.value()->get(pos.y);
155     } else if(token == "contents") {
156       std::string contentstring;
157       iter.value()->get(contentstring);
158       if(contentstring == "coin") {
159         contents = CONTENT_COIN;
160       } else if(contentstring == "firegrow") {
161         contents = CONTENT_FIREGROW;
162       } else if(contentstring == "icegrow") {
163         contents = CONTENT_ICEGROW;
164       } else if(contentstring == "star") {
165         contents = CONTENT_STAR;
166       } else if(contentstring == "1up") {
167         contents = CONTENT_1UP;
168       } else if(contentstring == "custom") {
169         contents = CONTENT_CUSTOM;
170       } else {
171         std::cerr << "Invalid box contents '" << contentstring << "'.\n";
172       }
173     } else {
174       if(contents == CONTENT_CUSTOM) {
175         GameObject* game_object = create_object(token, *(iter.lisp()));
176         object = dynamic_cast<MovingObject*> (game_object);
177         if(object == 0)
178           throw std::runtime_error(
179             "Only MovingObjects are allowed inside BonusBlocks");
180       } else {
181         std::cerr << "Invalid element '" << token << "' in bonusblock.\n";
182       }
183     }  
184   }
185
186   if(contents == CONTENT_CUSTOM && object == 0)
187     throw std::runtime_error("Need to specify content object for custom block");
188   
189   bbox.set_pos(pos);
190 }
191
192 BonusBlock::~BonusBlock()
193 {
194   delete object;
195 }
196
197 void
198 BonusBlock::hit(Player& )
199 {
200   try_open();
201 }
202
203 void
204 BonusBlock::try_open()
205 {
206   if(sprite->get_action_name() == "empty") {
207     sound_manager->play("sounds/brick.wav");
208     return;
209   }
210   
211   Sector* sector = Sector::current();
212   Player& player = *(sector->player);
213   switch(contents) {
214     case CONTENT_COIN:
215       Sector::current()->add_object(new BouncyCoin(get_pos()));
216       player.get_status()->incCoins();
217       break;
218
219     case CONTENT_FIREGROW:
220       if(player.get_status()->bonus == NO_BONUS) {
221         SpecialRiser* riser = new SpecialRiser(get_pos(), new GrowUp());
222         sector->add_object(riser);
223       } else {
224         SpecialRiser* riser = new SpecialRiser(
225             get_pos(), new Flower(Flower::FIREFLOWER));
226         sector->add_object(riser);
227       }
228       sound_manager->play("sounds/upgrade.wav");
229       break;
230
231     case CONTENT_ICEGROW:
232       if(player.get_status()->bonus == NO_BONUS) {
233         SpecialRiser* riser = new SpecialRiser(get_pos(), new GrowUp());
234         sector->add_object(riser);                                            
235       } else {
236         SpecialRiser* riser = new SpecialRiser(
237             get_pos(), new Flower(Flower::ICEFLOWER));
238         sector->add_object(riser);
239       }      
240       sound_manager->play("sounds/upgrade.wav");
241       break;
242
243     case CONTENT_STAR:
244       sector->add_object(new Star(get_pos() + Vector(0, -32)));
245       break;
246
247     case CONTENT_1UP:
248       sector->add_object(new OneUp(get_pos()));
249       break;
250
251     case CONTENT_CUSTOM:
252       SpecialRiser* riser = new SpecialRiser(get_pos(), object);
253       object = 0;
254       sector->add_object(riser);
255       sound_manager->play("sounds/upgrade.wav");
256       break;
257
258     //default:
259       //assert(false);
260   }
261
262   start_bounce();
263   sprite->set_action("empty");
264 }
265
266 IMPLEMENT_FACTORY(BonusBlock, "bonusblock");
267
268 //---------------------------------------------------------------------------
269
270 Brick::Brick(const Vector& pos, int data)
271   : Block(sprite_manager->create("brick")), breakable(false),
272     coin_counter(0)
273 {
274   bbox.set_pos(pos);
275   if(data == 1)
276     coin_counter = 5;
277   else
278     breakable = true;
279 }
280
281 void
282 Brick::hit(Player& )
283 {
284   if(sprite->get_action_name() == "empty")
285     return;
286   
287   try_break(true);
288 }
289
290 void
291 Brick::try_break(bool playerhit)
292 {
293   if(sprite->get_action_name() == "empty")
294     return;
295   
296   sound_manager->play("sounds/brick.wav");
297   Sector* sector = Sector::current();
298   Player& player = *(sector->player);
299   if(coin_counter > 0) {
300     sector->add_object(new BouncyCoin(get_pos()));
301     coin_counter--;
302     player.get_status()->incCoins();
303     if(coin_counter == 0)
304       sprite->set_action("empty");
305     start_bounce();
306   } else if(breakable) {
307     if(playerhit && !player.is_big()) {
308       start_bounce();
309       return;
310     }
311
312     sector->add_object(
313         new BrokenBrick(new Sprite(*sprite), get_pos(), Vector(-100, -400)));
314     sector->add_object(
315         new BrokenBrick(new Sprite(*sprite), get_pos() + Vector(0, 16),
316           Vector(-150, -300)));
317     sector->add_object(
318         new BrokenBrick(new Sprite(*sprite), get_pos() + Vector(16, 0),
319           Vector(100, -400)));
320     sector->add_object(
321         new BrokenBrick(new Sprite(*sprite), get_pos() + Vector(16, 16),
322           Vector(150, -300)));
323     remove_me();
324   }
325 }
326
327 //IMPLEMENT_FACTORY(Brick, "brick");
328