Cleaned up some junk in bonus_block.
[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;
63       //object = new Trampoline(get_pos(), false); //needed if this is to be moved to custom
64       break;
65     case 8: contents = CONTENT_CUSTOM;
66       object = new Trampoline(get_pos(), true);
67       break;
68     case 9: contents = CONTENT_CUSTOM;
69       object = new Rock(get_pos(), "images/objects/rock/rock.sprite"); 
70       break;
71     case 10: contents = CONTENT_RAIN; break;
72     case 11: contents = CONTENT_EXPLODE; break;
73     case 12: contents = CONTENT_CUSTOM;
74       object = new PowerUp(get_pos(), "images/powerups/potions/red-potion.sprite");
75       break;
76     default:
77       log_warning << "Invalid box contents" << std::endl;
78       contents = CONTENT_COIN;
79       break;
80   }
81 }
82
83 BonusBlock::BonusBlock(const Reader& lisp) :
84   Block(sprite_manager->create("images/objects/bonus_block/bonusblock.sprite")),
85   contents(),
86   object(0),
87   hit_counter(1),
88   lightsprite()
89 {
90   Vector pos;
91
92   contents = CONTENT_COIN;
93   lisp::ListIterator iter(&lisp);
94   while(iter.next()) {
95     const std::string& token = iter.item();
96     if(token == "x") {
97       iter.value()->get(pos.x);
98     } else if(token == "y") {
99       iter.value()->get(pos.y);
100     } else if(token == "sprite") {
101       iter.value()->get(sprite_name);
102       sprite = sprite_manager->create(sprite_name);
103     } else if(token == "count") {
104       iter.value()->get(hit_counter);
105     } else if(token == "script") {
106       iter.value()->get(script);
107     } else if(token == "contents") {
108       std::string contentstring;
109       iter.value()->get(contentstring);
110       if(contentstring == "coin") {
111         contents = CONTENT_COIN;
112       } else if(contentstring == "firegrow") {
113         contents = CONTENT_FIREGROW;
114       } else if(contentstring == "icegrow") {
115         contents = CONTENT_ICEGROW;
116       } else if(contentstring == "star") {
117         contents = CONTENT_STAR;
118       } else if(contentstring == "1up") {
119         contents = CONTENT_1UP;
120       } else if(contentstring == "custom") {
121         contents = CONTENT_CUSTOM;
122       } else if(contentstring == "script") { // use when bonusblock is to contain ONLY a script
123         contents = CONTENT_SCRIPT;
124       } else if(contentstring == "light") {
125         contents = CONTENT_LIGHT;
126         sound_manager->preload("sounds/switch.ogg");
127       } else if(contentstring == "trampoline") {
128         contents = CONTENT_TRAMPOLINE;
129       } else if(contentstring == "rain") {
130         contents = CONTENT_RAIN;
131       } else if(contentstring == "explode") {
132         contents = CONTENT_EXPLODE;
133       } else {
134         log_warning << "Invalid box contents '" << contentstring << "'" << std::endl;
135       }
136     } else {
137       if(contents == CONTENT_CUSTOM) {
138         GameObject* game_object = ObjectFactory::instance().create(token, *(iter.lisp()));
139         object = dynamic_cast<MovingObject*> (game_object);
140         if(object == 0)
141           throw std::runtime_error(
142             "Only MovingObjects are allowed inside BonusBlocks");
143       } else {
144         log_warning << "Invalid element '" << token << "' in bonusblock" << std::endl;
145       }
146     }
147   }
148
149   if(contents == CONTENT_CUSTOM && object == 0)
150     throw std::runtime_error("Need to specify content object for custom block");
151   if(contents == CONTENT_LIGHT)
152     lightsprite = Surface::create("/images/objects/lightmap_light/bonusblock_light.png");
153
154   bbox.set_pos(pos);
155 }
156
157 BonusBlock::~BonusBlock()
158 {
159   delete object;
160 }
161
162 void
163 BonusBlock::hit(Player & player)
164 {
165   try_open(&player);
166 }
167
168 HitResponse
169 BonusBlock::collision(GameObject& other, const CollisionHit& hit){
170
171   Player* player = dynamic_cast<Player*> (&other);
172   if (player) {
173     if (player->does_buttjump)
174       try_drop(player);
175   }
176
177   BadGuy* badguy = dynamic_cast<BadGuy*> (&other);
178   if(badguy) {
179     // hit contains no information for collisions with blocks.
180     // Badguy's bottom has to be below the top of the block
181     // SHIFT_DELTA is required to slide over one tile gaps.
182     if( badguy->can_break() && ( badguy->get_bbox().get_bottom() > get_bbox().get_top() + SHIFT_DELTA ) ){
183       try_open(player);
184     }
185   }
186   Portable* portable = dynamic_cast<Portable*> (&other);
187   if(portable) {
188     MovingObject* moving = dynamic_cast<MovingObject*> (&other);
189     if(moving->get_bbox().get_top() > get_bbox().get_bottom() - SHIFT_DELTA) {
190       try_open(player);
191     }
192   }
193   return Block::collision(other, hit);
194 }
195
196 void
197 BonusBlock::try_open(Player *player)
198 {
199   if(sprite->get_action() == "empty") {
200     sound_manager->play("sounds/brick.wav");
201     return;
202   }
203
204   Sector* sector = Sector::current();
205   assert(sector);
206
207   if (player == NULL)
208     player = sector->player;
209
210   if (player == NULL)
211     return;
212
213   Direction direction = (player->get_bbox().get_middle().x > get_bbox().get_middle().x) ? LEFT : RIGHT;
214
215   switch(contents) {
216     case CONTENT_COIN:
217     {
218       Sector::current()->add_object(new BouncyCoin(get_pos(), true));
219       player->get_status()->add_coins(1);
220       if (hit_counter != 0)
221         Sector::current()->get_level()->stats.coins++;
222       break;
223     }
224
225     case CONTENT_FIREGROW:
226     {
227       if(player->get_status()->bonus == NO_BONUS) {
228         SpecialRiser* riser = new SpecialRiser(get_pos(), new GrowUp(direction));
229         sector->add_object(riser);
230       } else {
231         SpecialRiser* riser = new SpecialRiser(
232           get_pos(), new Flower(FIRE_BONUS));
233         sector->add_object(riser);
234       }
235       sound_manager->play("sounds/upgrade.wav");
236       break;
237     }
238
239     case CONTENT_ICEGROW:
240     {
241       if(player->get_status()->bonus == NO_BONUS) {
242         SpecialRiser* riser = new SpecialRiser(get_pos(), new GrowUp(direction));
243         sector->add_object(riser);
244       } else {
245         SpecialRiser* riser = new SpecialRiser(
246           get_pos(), new Flower(ICE_BONUS));
247         sector->add_object(riser);
248       }
249       sound_manager->play("sounds/upgrade.wav");
250       break;
251     }
252
253     case CONTENT_STAR:
254     {
255       sector->add_object(new Star(get_pos() + Vector(0, -32), direction));
256       sound_manager->play("sounds/upgrade.wav");
257       break;
258     }
259
260     case CONTENT_1UP:
261     {
262       sector->add_object(new OneUp(get_pos(), direction));
263       sound_manager->play("sounds/upgrade.wav");
264       break;
265     }
266
267     case CONTENT_CUSTOM:
268     {
269       SpecialRiser* riser = new SpecialRiser(get_pos(), object);
270       object = 0;
271       sector->add_object(riser);
272       sound_manager->play("sounds/upgrade.wav");
273       break;
274     }
275
276     case CONTENT_SCRIPT:
277     { break; } // because scripts always run, this prevents default contents from being assumed
278
279     case CONTENT_LIGHT:
280     {
281       if(sprite->get_action() == "on")
282         sprite->set_action("off");
283       else
284         sprite->set_action("on");
285       sound_manager->play("sounds/switch.ogg");
286       break;
287     }
288     case CONTENT_TRAMPOLINE:
289     {
290       SpecialRiser* riser = new SpecialRiser(get_pos(), new Trampoline(get_pos(), false));
291       sector->add_object(riser);
292       sound_manager->play("sounds/upgrade.wav");
293       break;
294     }
295     case CONTENT_RAIN:
296     {
297       hit_counter = 1; // multiple hits of coin rain is not allowed
298       Sector::current()->add_object(new CoinRain(get_pos(), true));
299       sound_manager->play("sounds/upgrade.wav");
300       break;
301     }
302     case CONTENT_EXPLODE:
303     {
304       hit_counter = 1; // multiple hits of coin explode is not allowed
305       Sector::current()->add_object(new CoinExplode(get_pos() + Vector (0, -40)));
306       sound_manager->play("sounds/upgrade.wav");
307       break;
308     }
309   }
310
311   if(script != "") { // scripts always run if defined
312     std::istringstream stream(script);
313     Sector::current()->run_script(stream, "powerup-script");
314   }
315
316   start_bounce(player);
317   if(hit_counter <= 0 || contents == CONTENT_LIGHT){ //use 0 to allow infinite hits
318   }else if(hit_counter == 1){
319     sprite->set_action("empty");
320   }else{
321     hit_counter--;
322   }
323 }
324
325 void
326 BonusBlock::try_drop(Player *player)
327 {
328   if(sprite->get_action() == "empty") {
329     sound_manager->play("sounds/brick.wav");
330     return;
331   }
332
333   Sector* sector = Sector::current();
334   assert(sector);
335
336   // First what's below the bonus block, if solid send it up anyway (excepting doll)
337   Rectf dest;
338   dest.p1.x = bbox.get_left() + 1;
339   dest.p1.y = bbox.get_bottom() + 1;
340   dest.p2.x = bbox.get_right() - 1;
341   dest.p2.y = dest.p1.y + 30;
342   if (!Sector::current()->is_free_of_statics(dest, this, true) && !(contents == CONTENT_1UP)) {
343     try_open(player);
344     return;
345   }
346
347   if (player == NULL)
348     player = sector->player;
349
350   if (player == NULL)
351     return;
352
353   Direction direction = (player->get_bbox().get_middle().x > get_bbox().get_middle().x) ? LEFT : RIGHT;
354
355   switch(contents) {
356     case CONTENT_COIN:
357     {
358       try_open(player);
359       break;
360     }
361
362     case CONTENT_FIREGROW:
363     {
364       sector->add_object(new PowerUp(get_pos() + Vector(0, 32), "images/powerups/fireflower/fireflower.sprite"));
365       sound_manager->play("sounds/upgrade.wav");
366       break;
367     }
368
369     case CONTENT_ICEGROW:
370     {
371       sector->add_object(new PowerUp(get_pos() + Vector(0, 32), "images/powerups/iceflower/iceflower.sprite"));
372       sound_manager->play("sounds/upgrade.wav");
373       break;
374     }
375
376     case CONTENT_STAR:
377     {
378       sector->add_object(new Star(get_pos() + Vector(0, 32), direction));
379       sound_manager->play("sounds/upgrade.wav");
380       break;
381     }
382
383     case CONTENT_1UP:
384     {
385       sector->add_object(new OneUp(get_pos(), DOWN));
386       sound_manager->play("sounds/upgrade.wav");
387       break;
388     }
389
390     case CONTENT_CUSTOM:
391     {
392       //TODO: non-portable trampolines could be moved to CONTENT_CUSTOM, but they should not drop
393       object->set_pos(get_pos() +  Vector(0, 32));
394       sector->add_object(object);
395       object = 0;
396       sound_manager->play("sounds/upgrade.wav");
397       break;
398     }
399
400     case CONTENT_SCRIPT:
401     { break; } // because scripts always run, this prevents default contents from being assumed
402
403     case CONTENT_LIGHT:
404     {
405       try_open(player);
406       break;
407     }
408     case CONTENT_TRAMPOLINE:
409     {
410       try_open(player);
411       break;
412     }
413     case CONTENT_RAIN:
414     {
415       try_open(player);
416       break;
417     }
418     case CONTENT_EXPLODE:
419     {
420       hit_counter = 1; // multiple hits of coin explode is not allowed
421       Sector::current()->add_object(new CoinExplode(get_pos() + Vector (0, 40)));
422       sound_manager->play("sounds/upgrade.wav");
423       break;
424     }
425   }
426
427   if(script != "") { // scripts always run if defined
428     std::istringstream stream(script);
429     Sector::current()->run_script(stream, "powerup-script");
430   }
431
432   if(hit_counter <= 0 || contents == CONTENT_LIGHT){ //use 0 to allow infinite hits
433   }else if(hit_counter == 1){
434     sprite->set_action("empty");
435   }else{
436     hit_counter--;
437   }
438 }
439
440 void
441 Block::break_me()
442 {
443   Sector* sector = Sector::current();
444   sector->add_object(
445     new BrokenBrick(sprite->clone(), get_pos(), Vector(-100, -400)));
446   sector->add_object(
447     new BrokenBrick(sprite->clone(), get_pos() + Vector(0, 16),
448                     Vector(-150, -300)));
449   sector->add_object(
450     new BrokenBrick(sprite->clone(), get_pos() + Vector(16, 0),
451                     Vector(100, -400)));
452   sector->add_object(
453     new BrokenBrick(sprite->clone(), get_pos() + Vector(16, 16),
454                     Vector(150, -300)));
455   remove_me();
456 }
457
458 void
459 BonusBlock::draw(DrawingContext& context){
460   // draw regular sprite
461   sprite->draw(context, get_pos(), 10);
462   //Draw light if on.
463   if(sprite->get_action() == "on") {
464     Vector pos = get_pos() + (bbox.get_size() - lightsprite->get_size()) / 2;
465     context.push_target();
466     context.set_target(DrawingContext::LIGHTMAP);
467     context.draw_surface(lightsprite, pos, 10);
468     context.pop_target();
469   }
470 }
471 /* EOF */