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