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