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