Finish making powerups drop out of bonus blocks when hit with butt-jump
[supertux.git] / src / object / bonus_block.cpp
1 //  SuperTux
2 //  Copyright (C) 2009 Ingo Ruhnke <grumbel@gmx.de>
3 //
4 //  This program is free software: you can redistribute it and/or modify
5 //  it under the terms of the GNU General Public License as published by
6 //  the Free Software Foundation, either version 3 of the License, or
7 //  (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 #include "object/bonus_block.hpp"
18
19 #include "audio/sound_manager.hpp"
20 #include "badguy/badguy.hpp"
21 #include "lisp/list_iterator.hpp"
22 #include "object/broken_brick.hpp"
23 #include "object/flower.hpp"
24 #include "object/bouncy_coin.hpp"
25 #include "object/coin_explode.hpp"
26 #include "object/coin_rain.hpp"
27 #include "object/growup.hpp"
28 #include "object/oneup.hpp"
29 #include "object/player.hpp"
30 #include "object/portable.hpp"
31 #include "object/powerup.hpp"
32 #include "object/specialriser.hpp"
33 #include "object/star.hpp"
34 #include "object/trampoline.hpp"
35 #include "sprite/sprite_manager.hpp"
36 #include "supertux/constants.hpp"
37 #include "supertux/level.hpp"
38 #include "supertux/object_factory.hpp"
39 #include "supertux/sector.hpp"
40
41 #include <stdexcept>
42
43 BonusBlock::BonusBlock(const Vector& pos, int data) :
44   Block(sprite_manager->create("images/objects/bonus_block/bonusblock.sprite")), 
45   contents(),
46   object(0),
47   hit_counter(1),
48   lightsprite()
49 {
50   bbox.set_pos(pos);
51   sprite->set_action("normal");
52   switch(data) {
53     case 1: contents = CONTENT_COIN; break;
54     case 2: contents = CONTENT_FIREGROW; break;
55     case 3: contents = CONTENT_STAR; break;
56     case 4: contents = CONTENT_1UP; break;
57     case 5: contents = CONTENT_ICEGROW; break;
58     case 6: contents = CONTENT_LIGHT; 
59       sound_manager->preload("sounds/switch.ogg"); 
60       lightsprite=Surface::create("/images/objects/lightmap_light/bonusblock_light.png");
61       break;
62     case 7: contents = CONTENT_TRAMPOLINE; break;
63     case 8: contents = CONTENT_PORTTRAMPOLINE; break;
64     case 9: contents = CONTENT_ROCK; break;
65     case 10: contents = CONTENT_RAIN; break;
66     case 11: contents = CONTENT_EXPLODE; break;
67     default:
68       log_warning << "Invalid box contents" << std::endl;
69       contents = CONTENT_COIN;
70       break;
71   }
72 }
73
74 BonusBlock::BonusBlock(const Reader& lisp) :
75   Block(sprite_manager->create("images/objects/bonus_block/bonusblock.sprite")),
76   contents(),
77   object(0),
78   hit_counter(1),
79   lightsprite()
80 {
81   Vector pos;
82
83   contents = CONTENT_COIN;
84   lisp::ListIterator iter(&lisp);
85   while(iter.next()) {
86     const std::string& token = iter.item();
87     if(token == "x") {
88       iter.value()->get(pos.x);
89     } else if(token == "y") {
90       iter.value()->get(pos.y);
91     } else if(token == "sprite") {
92       iter.value()->get(sprite_name);
93       sprite = sprite_manager->create(sprite_name);
94     } else if(token == "count") {
95       iter.value()->get(hit_counter);
96     } else if(token == "script") { // use when bonusblock is to contain ONLY a script
97       iter.value()->get(script);
98     } else if(token == "contents") {
99       std::string contentstring;
100       iter.value()->get(contentstring);
101       if(contentstring == "coin") {
102         contents = CONTENT_COIN;
103       } else if(contentstring == "firegrow") {
104         contents = CONTENT_FIREGROW;
105       } else if(contentstring == "icegrow") {
106         contents = CONTENT_ICEGROW;
107       } else if(contentstring == "star") {
108         contents = CONTENT_STAR;
109       } else if(contentstring == "1up") {
110         contents = CONTENT_1UP;
111       } else if(contentstring == "custom") {
112         contents = CONTENT_CUSTOM;
113       } else if(contentstring == "script") {
114         contents = CONTENT_SCRIPT;
115       } else if(contentstring == "light") {
116         contents = CONTENT_LIGHT;
117         sound_manager->preload("sounds/switch.ogg");
118       } else if(contentstring == "trampoline") {
119         contents = CONTENT_TRAMPOLINE;
120       } else if(contentstring == "porttrampoline") {
121         contents = CONTENT_PORTTRAMPOLINE;
122       } else if(contentstring == "rock") {
123         contents = CONTENT_ROCK;
124       } else if(contentstring == "rain") {
125         contents = CONTENT_RAIN;
126       } else if(contentstring == "explode") {
127         contents = CONTENT_EXPLODE;
128       } else {
129         log_warning << "Invalid box contents '" << contentstring << "'" << std::endl;
130       }
131     } else {
132       if(contents == CONTENT_CUSTOM) {
133         GameObject* game_object = ObjectFactory::instance().create(token, *(iter.lisp()));
134         object = dynamic_cast<MovingObject*> (game_object);
135         if(object == 0)
136           throw std::runtime_error(
137             "Only MovingObjects are allowed inside BonusBlocks");
138       } else {
139         log_warning << "Invalid element '" << token << "' in bonusblock" << std::endl;
140       }
141     }
142   }
143
144   if(contents == CONTENT_CUSTOM && object == 0)
145     throw std::runtime_error("Need to specify content object for custom block");
146   if(contents == CONTENT_LIGHT)
147     lightsprite = Surface::create("/images/objects/lightmap_light/bonusblock_light.png");
148
149   bbox.set_pos(pos);
150 }
151
152 BonusBlock::~BonusBlock()
153 {
154   delete object;
155 }
156
157 void
158 BonusBlock::hit(Player & player)
159 {
160   try_open(&player);
161 }
162
163 HitResponse
164 BonusBlock::collision(GameObject& other, const CollisionHit& hit){
165
166   Player* player = dynamic_cast<Player*> (&other);
167   if (player) {
168     if (player->does_buttjump)
169       try_drop(player);
170   }
171
172   BadGuy* badguy = dynamic_cast<BadGuy*> (&other);
173   if(badguy) {
174     // hit contains no information for collisions with blocks.
175     // Badguy's bottom has to be below the top of the block
176     // SHIFT_DELTA is required to slide over one tile gaps.
177     if( badguy->can_break() && ( badguy->get_bbox().get_bottom() > get_bbox().get_top() + SHIFT_DELTA ) ){
178       try_open(player);
179     }
180   }
181   Portable* portable = dynamic_cast<Portable*> (&other);
182   if(portable) {
183     MovingObject* moving = dynamic_cast<MovingObject*> (&other);
184     if(moving->get_bbox().get_top() > get_bbox().get_bottom() - SHIFT_DELTA) {
185       try_open(player);
186     }
187   }
188   return Block::collision(other, hit);
189 }
190
191 void
192 BonusBlock::try_open(Player *player)
193 {
194   if(sprite->get_action() == "empty") {
195     sound_manager->play("sounds/brick.wav");
196     return;
197   }
198
199   Sector* sector = Sector::current();
200   assert(sector);
201
202   if (player == NULL)
203     player = sector->player;
204
205   if (player == NULL)
206     return;
207
208   Direction direction = (player->get_bbox().get_middle().x > get_bbox().get_middle().x) ? LEFT : RIGHT;
209
210   switch(contents) {
211     case CONTENT_COIN:
212     {
213       Sector::current()->add_object(new BouncyCoin(get_pos(), true));
214       player->get_status()->add_coins(1);
215       if (hit_counter != 0)
216         Sector::current()->get_level()->stats.coins++;
217       break;
218     }
219
220     case CONTENT_FIREGROW:
221     {
222       if(player->get_status()->bonus == NO_BONUS) {
223         SpecialRiser* riser = new SpecialRiser(get_pos(), new GrowUp(direction));
224         sector->add_object(riser);
225       } else {
226         SpecialRiser* riser = new SpecialRiser(
227           get_pos(), new Flower(FIRE_BONUS));
228         sector->add_object(riser);
229       }
230       sound_manager->play("sounds/upgrade.wav");
231       break;
232     }
233
234     case CONTENT_ICEGROW:
235     {
236       if(player->get_status()->bonus == NO_BONUS) {
237         SpecialRiser* riser = new SpecialRiser(get_pos(), new GrowUp(direction));
238         sector->add_object(riser);
239       } else {
240         SpecialRiser* riser = new SpecialRiser(
241           get_pos(), new Flower(ICE_BONUS));
242         sector->add_object(riser);
243       }
244       sound_manager->play("sounds/upgrade.wav");
245       break;
246     }
247
248     case CONTENT_STAR:
249     {
250       sector->add_object(new Star(get_pos() + Vector(0, -32), direction));
251       break;
252     }
253
254     case CONTENT_1UP:
255     {
256       sector->add_object(new OneUp(get_pos(), direction));
257       break;
258     }
259
260     case CONTENT_CUSTOM:
261     {
262       SpecialRiser* riser = new SpecialRiser(get_pos(), object);
263       object = 0;
264       sector->add_object(riser);
265       sound_manager->play("sounds/upgrade.wav");
266       break;
267     }
268
269     case CONTENT_SCRIPT:
270     { break; } // because scripts always run, this prevents default contents from being assumed
271
272     case CONTENT_LIGHT:
273     {
274       if(sprite->get_action() == "on")
275         sprite->set_action("off");
276       else
277         sprite->set_action("on");
278       sound_manager->play("sounds/switch.ogg");
279       break;
280     }
281     case CONTENT_TRAMPOLINE:
282     {
283       SpecialRiser* riser = new SpecialRiser(get_pos(), new Trampoline(get_pos(), false));
284       sector->add_object(riser);
285       sound_manager->play("sounds/upgrade.wav");
286       break;
287     }
288     case CONTENT_PORTTRAMPOLINE:
289     {
290       SpecialRiser* riser = new SpecialRiser(get_pos(), new Trampoline(get_pos(), true));
291       sector->add_object(riser);
292       sound_manager->play("sounds/upgrade.wav");
293       break;
294     }
295     case CONTENT_ROCK:
296     {
297       SpecialRiser* riser = new SpecialRiser(get_pos(), 
298         new Rock(get_pos(), "images/objects/rock/rock.sprite"));
299       sector->add_object(riser);
300       sound_manager->play("sounds/upgrade.wav");
301       break;
302     }
303
304     case CONTENT_RAIN:
305     {
306       hit_counter = 1; // multiple hits of coin rain is not allowed
307       Sector::current()->add_object(new CoinRain(get_pos(), true));
308       sound_manager->play("sounds/upgrade.wav");
309       break;
310     }
311     case CONTENT_EXPLODE:
312     {
313       hit_counter = 1; // multiple hits of coin explode is not allowed
314       Sector::current()->add_object(new CoinExplode(get_pos() + Vector (0, -40), 1));
315       sound_manager->play("sounds/upgrade.wav");
316       break;
317     }
318   }
319
320   if(script != "") { // scripts always run if defined
321     std::istringstream stream(script);
322     Sector::current()->run_script(stream, "powerup-script");
323   }
324
325   start_bounce(player);
326   if(hit_counter <= 0 || contents == CONTENT_LIGHT){ //use 0 to allow infinite hits
327   }else if(hit_counter == 1){
328     sprite->set_action("empty");
329   }else{
330     hit_counter--;
331   }
332 }
333
334 void
335 BonusBlock::try_drop(Player *player)
336 {
337   if(sprite->get_action() == "empty") {
338     sound_manager->play("sounds/brick.wav");
339     return;
340   }
341
342   Sector* sector = Sector::current();
343   assert(sector);
344
345   // First what's below the bonus block, if solid send it up anyway (excepting doll)
346   Rectf dest;
347   dest.p1.x = bbox.get_left() + 1;
348   dest.p1.y = bbox.get_bottom() + 1;
349   dest.p2.x = bbox.get_right() - 1;
350   dest.p2.y = dest.p1.y + 30;
351   if (!Sector::current()->is_free_of_statics(dest, this, true) && !(contents == CONTENT_1UP)) {
352     try_open(player);
353     return;
354   }
355
356   if (player == NULL)
357     player = sector->player;
358
359   if (player == NULL)
360     return;
361
362   Direction direction = (player->get_bbox().get_middle().x > get_bbox().get_middle().x) ? LEFT : RIGHT;
363
364   switch(contents) {
365     case CONTENT_COIN:
366     {
367       try_open(player);
368       break;
369     }
370
371     case CONTENT_FIREGROW:
372     {
373       sector->add_object(new PowerUp(get_pos() + Vector(0, 32), "images/powerups/fireflower/fireflower.sprite"));
374       sound_manager->play("sounds/upgrade.wav");
375       break;
376     }
377
378     case CONTENT_ICEGROW:
379     {
380       sector->add_object(new PowerUp(get_pos() + Vector(0, 32), "images/powerups/iceflower/iceflower.sprite"));
381       sound_manager->play("sounds/upgrade.wav");
382       break;
383     }
384
385     case CONTENT_STAR:
386     {
387       sector->add_object(new Star(get_pos() + Vector(0, 32), direction));
388       break;
389     }
390
391     case CONTENT_1UP:
392     {
393       sector->add_object(new OneUp(get_pos(), DOWN));
394       break;
395     }
396
397     case CONTENT_CUSTOM:
398     {
399       //TODO: confirm this works
400       object->set_pos(get_pos() +  Vector(0, 32));
401       sector->add_object(object);
402       object = 0;
403       sound_manager->play("sounds/upgrade.wav");
404       break;
405     }
406
407     case CONTENT_SCRIPT:
408     { break; } // because scripts always run, this prevents default contents from being assumed
409
410     case CONTENT_LIGHT:
411     {
412       try_open(player);
413       break;
414     }
415     case CONTENT_TRAMPOLINE:
416     {
417       try_open(player);
418       break;
419     }
420     case CONTENT_PORTTRAMPOLINE:
421     {
422       Sector::current()->add_object(new Trampoline(get_pos() + Vector (0, 32), true));
423       sound_manager->play("sounds/upgrade.wav");
424       break;
425     }
426     case CONTENT_ROCK:
427     {
428       Sector::current()->add_object(new Rock(get_pos() + Vector (0, 32), "images/objects/rock/rock.sprite"));
429       sound_manager->play("sounds/upgrade.wav");
430       break;
431     }
432
433     case CONTENT_RAIN:
434     {
435       try_open(player);
436       break;
437     }
438     case CONTENT_EXPLODE:
439     {
440       hit_counter = 1; // multiple hits of coin explode is not allowed
441       Sector::current()->add_object(new CoinExplode(get_pos() + Vector (0, 40), -1));
442       sound_manager->play("sounds/upgrade.wav");
443       break;
444     }
445   }
446
447   if(script != "") { // scripts always run if defined
448     std::istringstream stream(script);
449     Sector::current()->run_script(stream, "powerup-script");
450   }
451
452   if(hit_counter <= 0 || contents == CONTENT_LIGHT){ //use 0 to allow infinite hits
453   }else if(hit_counter == 1){
454     sprite->set_action("empty");
455   }else{
456     hit_counter--;
457   }
458 }
459
460 void
461 Block::break_me()
462 {
463   Sector* sector = Sector::current();
464   sector->add_object(
465     new BrokenBrick(sprite->clone(), get_pos(), Vector(-100, -400)));
466   sector->add_object(
467     new BrokenBrick(sprite->clone(), get_pos() + Vector(0, 16),
468                     Vector(-150, -300)));
469   sector->add_object(
470     new BrokenBrick(sprite->clone(), get_pos() + Vector(16, 0),
471                     Vector(100, -400)));
472   sector->add_object(
473     new BrokenBrick(sprite->clone(), get_pos() + Vector(16, 16),
474                     Vector(150, -300)));
475   remove_me();
476 }
477
478 void
479 BonusBlock::draw(DrawingContext& context){
480   // draw regular sprite
481   sprite->draw(context, get_pos(), 10);
482   //Draw light if on.
483   if(sprite->get_action() == "on") {
484     Vector pos = get_pos() + (bbox.get_size() - lightsprite->get_size()) / 2;
485     context.push_target();
486     context.set_target(DrawingContext::LIGHTMAP);
487     context.draw_surface(lightsprite, pos, 10);
488     context.pop_target();
489   }
490 }
491 /* EOF */