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