bonus_block.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777
  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. #include "object/bonus_block.hpp"
  17. #include <memory>
  18. #include "audio/sound_manager.hpp"
  19. #include "badguy/badguy.hpp"
  20. #include "badguy/crusher.hpp"
  21. #include "editor/editor.hpp"
  22. #include "math/vector.hpp"
  23. #include "object/bouncy_coin.hpp"
  24. #include "object/coin_explode.hpp"
  25. #include "object/coin_rain.hpp"
  26. #include "object/flower.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/rock.hpp"
  33. #include "object/specialriser.hpp"
  34. #include "object/star.hpp"
  35. #include "object/trampoline.hpp"
  36. #include "supertux/constants.hpp"
  37. #include "supertux/game_object_factory.hpp"
  38. #include "supertux/level.hpp"
  39. #include "supertux/sector.hpp"
  40. #include "util/reader_collection.hpp"
  41. #include "util/reader_mapping.hpp"
  42. #include "util/writer.hpp"
  43. #include "video/drawing_context.hpp"
  44. #include "video/surface.hpp"
  45. namespace {
  46. std::unique_ptr<MovingObject> to_moving_object(std::unique_ptr<GameObject> object)
  47. {
  48. if (dynamic_cast<MovingObject*>(object.get()) != nullptr) {
  49. return std::unique_ptr<MovingObject>(static_cast<MovingObject*>(object.release()));
  50. } else {
  51. return std::unique_ptr<MovingObject>();
  52. }
  53. }
  54. const float UPGRADE_SOUND_GAIN = 0.3f;
  55. } // namespace
  56. BonusBlock::BonusBlock(const Vector& pos, int tile_data) :
  57. Block(pos, "images/objects/bonus_block/bonusblock.sprite"),
  58. m_contents(),
  59. m_objects(),
  60. m_object(),
  61. m_hit_counter(1),
  62. m_script(),
  63. m_lightsprite(),
  64. m_coin_sprite(get_default_coin_sprite())
  65. {
  66. set_action("normal");
  67. m_contents = get_content_by_data(tile_data);
  68. preload_contents(tile_data);
  69. }
  70. BonusBlock::BonusBlock(const ReaderMapping& mapping) :
  71. Block(mapping, "images/objects/bonus_block/bonusblock.sprite"),
  72. m_contents(Content::COIN),
  73. m_objects(),
  74. m_object(),
  75. m_hit_counter(1),
  76. m_script(),
  77. m_lightsprite(),
  78. m_coin_sprite(get_default_coin_sprite())
  79. {
  80. parse_type(mapping);
  81. mapping.get("count", m_hit_counter);
  82. mapping.get("script", m_script);
  83. mapping.get("coin-sprite", m_coin_sprite);
  84. int data = 0;
  85. if (mapping.get("data", data))
  86. {
  87. m_contents = get_content_by_data(data);
  88. preload_contents(data);
  89. }
  90. std::string content;
  91. if (mapping.get("contents", content))
  92. {
  93. m_contents = get_content_from_string(content);
  94. if (m_contents == Content::CUSTOM)
  95. {
  96. std::optional<ReaderCollection> content_collection;
  97. if (mapping.get("custom-contents", content_collection))
  98. {
  99. const auto& object_specs = content_collection->get_objects();
  100. if (!object_specs.empty())
  101. {
  102. if (object_specs.size() > 1)
  103. log_warning << "Only one custom object allowed inside bonus blocks, ignoring the rest." << std::endl;
  104. const auto& spec = object_specs.at(0);
  105. auto game_object = GameObjectFactory::instance().create(spec.get_name(), spec.get_mapping());
  106. if (dynamic_cast<MovingObject*>(game_object.get()))
  107. set_object(std::move(game_object));
  108. else
  109. log_warning << "Only `MovingObject`s are allowed inside bonus blocks." << std::endl;
  110. }
  111. else
  112. {
  113. log_warning << "Custom object not set." << std::endl;
  114. }
  115. }
  116. else
  117. {
  118. log_warning << "Custom object not set." << std::endl;
  119. }
  120. }
  121. }
  122. if (!Editor::is_active() && m_contents == Content::CUSTOM && !m_object)
  123. throw std::runtime_error("Need to specify content object for custom block");
  124. if (m_contents == Content::LIGHT || m_contents == Content::LIGHT_ON)
  125. {
  126. SoundManager::current()->preload("sounds/switch.ogg");
  127. m_lightsprite = Surface::from_file("/images/objects/lightmap_light/bonusblock_light.png");
  128. if (m_contents == Content::LIGHT_ON)
  129. set_action("on");
  130. else
  131. set_action("off");
  132. }
  133. }
  134. void
  135. BonusBlock::add_object(std::unique_ptr<GameObject> object)
  136. {
  137. if (!m_objects.empty())
  138. throw std::runtime_error(_("Only one custom object is allowed inside bonus blocks."));
  139. set_object(std::move(object));
  140. }
  141. void
  142. BonusBlock::set_object(std::unique_ptr<GameObject> object)
  143. {
  144. m_object = object.get();
  145. m_objects.clear();
  146. m_objects.push_back(std::move(object));
  147. }
  148. GameObjectTypes
  149. BonusBlock::get_types() const
  150. {
  151. return {
  152. { "blue", _("Blue") },
  153. { "orange", _("Orange") },
  154. { "purple", _("Purple") },
  155. { "retro", _("Retro") }
  156. };
  157. }
  158. std::string
  159. BonusBlock::get_default_sprite_name() const
  160. {
  161. switch (m_type)
  162. {
  163. case ORANGE:
  164. return "images/objects/bonus_block/orangeblock.sprite";
  165. case PURPLE:
  166. return "images/objects/bonus_block/purpleblock.sprite";
  167. case RETRO:
  168. return "images/objects/bonus_block/retroblock.sprite";
  169. default:
  170. return m_default_sprite_name;
  171. }
  172. }
  173. void
  174. BonusBlock::on_type_change(int old_type)
  175. {
  176. Block::on_type_change(old_type);
  177. m_hit_counter = get_default_hit_counter();
  178. m_coin_sprite = get_default_coin_sprite();
  179. }
  180. int
  181. BonusBlock::get_default_hit_counter() const
  182. {
  183. switch (m_type)
  184. {
  185. case ORANGE:
  186. return 3;
  187. case PURPLE:
  188. return 5;
  189. default:
  190. return 1;
  191. }
  192. }
  193. std::string
  194. BonusBlock::get_default_coin_sprite() const
  195. {
  196. switch (m_type)
  197. {
  198. case RETRO:
  199. return "images/objects/coin/retro_coin.sprite";
  200. default:
  201. return "images/objects/coin/coin.sprite";
  202. }
  203. }
  204. BonusBlock::Content
  205. BonusBlock::get_content_by_data(int tile_data) const
  206. {
  207. // Warning: 'tile_data' can't be cast to 'Content', this manual
  208. // conversion is necessary.
  209. switch (tile_data) {
  210. case 1: return Content::COIN;
  211. case 2: return Content::FIREGROW;
  212. case 3: return Content::STAR;
  213. case 4: return Content::ONEUP;
  214. case 5: return Content::ICEGROW;
  215. case 6: return Content::LIGHT;
  216. case 7: return Content::TRAMPOLINE;
  217. case 8: return Content::PORTABLE_TRAMPOLINE; // Trampoline.
  218. case 9: return Content::ROCK; // Rock.
  219. case 10: return Content::RAIN;
  220. case 11: return Content::EXPLODE;
  221. case 12: return Content::POTION; // Red potion.
  222. case 13: return Content::AIRGROW;
  223. case 14: return Content::EARTHGROW;
  224. case 15: return Content::LIGHT_ON;
  225. case 16: return Content::RETROGROW;
  226. case 17: return Content::RETROSTAR;
  227. default:
  228. log_warning << "Invalid box contents" << std::endl;
  229. return Content::COIN;
  230. }
  231. }
  232. ObjectSettings
  233. BonusBlock::get_settings()
  234. {
  235. ObjectSettings result = Block::get_settings();
  236. result.add_script(_("Script"), &m_script, "script");
  237. result.add_int(_("Count"), &m_hit_counter, "count", get_default_hit_counter());
  238. result.add_enum(_("Content"), reinterpret_cast<int*>(&m_contents),
  239. { _("Coin"), _("Growth (fire flower)"), _("Growth (ice flower)"), _("Growth (air flower)"),
  240. _("Growth (earth flower)"), _("Growth (retro)"), _("Star"), _("Star (retro)"), _("Tux doll"), _("Custom"), _("Script"), _("Light"), _("Light (On)"),
  241. _("Trampoline"), _("Portable trampoline"), _("Coin rain"), _("Coin explosion"), _("Rock"), _("Potion") },
  242. { "coin", "firegrow", "icegrow", "airgrow", "earthgrow", "retrogrow", "star", "retrostar", "1up", "custom", "script", "light", "light-on",
  243. "trampoline", "portabletrampoline", "rain", "explode", "rock", "potion" },
  244. static_cast<int>(Content::COIN), "contents");
  245. if (m_contents == Content::CUSTOM)
  246. result.add_objects(_("Custom Content"), &m_objects, ObjectFactory::RegisteredObjectParam::OBJ_PARAM_DISPENSABLE,
  247. [this](auto obj) { add_object(std::move(obj)); }, "custom-contents");
  248. else if (m_contents == Content::COIN || m_contents == Content::RAIN || m_contents == Content::EXPLODE)
  249. result.add_sprite(_("Coin sprite"), &m_coin_sprite, "coin-sprite", get_default_coin_sprite());
  250. result.reorder({"script", "count", "contents", "custom-content", "coin-sprite", "sprite", "x", "y"});
  251. return result;
  252. }
  253. void
  254. BonusBlock::hit(Player& player)
  255. {
  256. try_open(&player);
  257. }
  258. HitResponse
  259. BonusBlock::collision(GameObject& other, const CollisionHit& hit_)
  260. {
  261. auto player = dynamic_cast<Player*> (&other);
  262. if (player) {
  263. if (player->m_does_buttjump ||
  264. (player->is_swimboosting() && player->get_bbox().get_bottom() < m_col.m_bbox.get_top() + SHIFT_DELTA))
  265. {
  266. try_drop(player);
  267. }
  268. }
  269. auto badguy = dynamic_cast<BadGuy*> (&other);
  270. if (badguy) {
  271. // Hit contains no information for collisions with blocks.
  272. // Badguy's bottom has to be below the top of the block
  273. // SHIFT_DELTA is required to slide over one tile gaps.
  274. if ( badguy->can_break() && ( badguy->get_bbox().get_bottom() > m_col.m_bbox.get_top() + SHIFT_DELTA ) ) {
  275. try_open(player);
  276. }
  277. }
  278. auto crusher = dynamic_cast<Crusher*> (&other);
  279. if (crusher)
  280. {
  281. try_open(player);
  282. }
  283. auto portable = dynamic_cast<Portable*> (&other);
  284. if (portable && !badguy) {
  285. auto moving = dynamic_cast<MovingObject*> (&other);
  286. if (moving->get_bbox().get_top() > m_col.m_bbox.get_bottom() - SHIFT_DELTA) {
  287. try_open(player);
  288. }
  289. }
  290. return Block::collision(other, hit_);
  291. }
  292. void
  293. BonusBlock::try_open(Player* player)
  294. {
  295. SoundManager::current()->play("sounds/brick.wav", get_pos());
  296. if (m_sprite->get_action() == "empty")
  297. return;
  298. if (player == nullptr)
  299. player = Sector::get().get_nearest_player(m_col.m_bbox);
  300. if (player == nullptr)
  301. return;
  302. Direction direction = (player->get_bbox().get_middle().x > m_col.m_bbox.get_middle().x) ? Direction::LEFT : Direction::RIGHT;
  303. bool play_upgrade_sound = false;
  304. switch (m_contents) {
  305. case Content::COIN:
  306. {
  307. Sector::get().add<BouncyCoin>(get_pos(), true, m_coin_sprite);
  308. SoundManager::current()->play("sounds/coin.wav", get_pos());
  309. player->get_status().add_coins(1, false);
  310. if (m_hit_counter != 0 && !m_parent_dispenser)
  311. Sector::get().get_level().m_stats.increment_coins();
  312. break;
  313. }
  314. case Content::FIREGROW:
  315. {
  316. raise_growup_bonus(player, FIRE_BONUS, direction);
  317. break;
  318. }
  319. case Content::ICEGROW:
  320. {
  321. raise_growup_bonus(player, ICE_BONUS, direction);
  322. break;
  323. }
  324. case Content::AIRGROW:
  325. {
  326. raise_growup_bonus(player, AIR_BONUS, direction);
  327. break;
  328. }
  329. case Content::EARTHGROW:
  330. {
  331. raise_growup_bonus(player, EARTH_BONUS, direction);
  332. break;
  333. }
  334. case Content::RETROGROW:
  335. {
  336. raise_growup_bonus(player, FIRE_BONUS, direction,
  337. "images/powerups/retro/mints.png", "images/powerups/retro/coffee.png");
  338. break;
  339. }
  340. case Content::STAR:
  341. {
  342. Sector::get().add<Star>(get_pos() + Vector(0, -32), direction);
  343. play_upgrade_sound = true;
  344. break;
  345. }
  346. case Content::RETROSTAR:
  347. {
  348. Sector::get().add<Star>(get_pos() + Vector(0, -32), direction,
  349. "images/powerups/retro/golden_herring.png");
  350. play_upgrade_sound = true;
  351. break;
  352. }
  353. case Content::ONEUP:
  354. {
  355. Sector::get().add<OneUp>(get_pos(), direction);
  356. play_upgrade_sound = true;
  357. break;
  358. }
  359. case Content::CUSTOM:
  360. {
  361. auto moving_obj_copy = to_moving_object(GameObjectFactory::instance().create(m_object->get_class_name(), get_pos() + Vector(0, -32),
  362. direction, m_object->save()));
  363. Sector::get().add<SpecialRiser>(get_pos(), std::move(moving_obj_copy), true);
  364. play_upgrade_sound = true;
  365. break;
  366. }
  367. case Content::SCRIPT:
  368. { break; } // This prevents default contents from being assumed because scripts always run.
  369. case Content::LIGHT:
  370. case Content::LIGHT_ON:
  371. {
  372. if (m_sprite->get_action() == "on")
  373. set_action("off");
  374. else
  375. set_action("on");
  376. SoundManager::current()->play("sounds/switch.ogg", get_pos());
  377. break;
  378. }
  379. case Content::TRAMPOLINE:
  380. {
  381. Sector::get().add<SpecialRiser>(get_pos(), std::make_unique<Trampoline>(get_pos(), Trampoline::STATIONARY), true);
  382. play_upgrade_sound = true;
  383. break;
  384. }
  385. case Content::PORTABLE_TRAMPOLINE:
  386. {
  387. Sector::get().add<SpecialRiser>(get_pos(), std::make_unique<Trampoline>(get_pos(), Trampoline::PORTABLE), true);
  388. play_upgrade_sound = true;
  389. break;
  390. }
  391. case Content::ROCK:
  392. {
  393. Sector::get().add<SpecialRiser>(get_pos(), std::make_unique<Rock>(get_pos()));
  394. break;
  395. }
  396. case Content::POTION:
  397. {
  398. Sector::get().add<SpecialRiser>(get_pos(), std::make_unique<PowerUp>(get_pos(), PowerUp::FLIP));
  399. break;
  400. }
  401. case Content::RAIN:
  402. {
  403. Sector::get().add<CoinRain>(get_pos(), true, !m_parent_dispenser, m_coin_sprite);
  404. play_upgrade_sound = true;
  405. break;
  406. }
  407. case Content::EXPLODE:
  408. {
  409. Sector::get().add<CoinExplode>(get_pos() + Vector (0, -40), !m_parent_dispenser, m_coin_sprite);
  410. play_upgrade_sound = true;
  411. break;
  412. }
  413. }
  414. if (play_upgrade_sound)
  415. SoundManager::current()->play("sounds/upgrade.wav", get_pos(), UPGRADE_SOUND_GAIN);
  416. if (!m_script.empty()) { // Scripts always run if defined.
  417. Sector::get().run_script(m_script, "BonusBlockScript");
  418. }
  419. start_bounce(player);
  420. if (m_hit_counter <= 0 || m_contents == Content::LIGHT || m_contents == Content::LIGHT_ON) { // Use 0 to allow infinite hits.
  421. } else if (m_hit_counter == 1) {
  422. set_action("empty");
  423. } else {
  424. m_hit_counter--;
  425. }
  426. }
  427. void
  428. BonusBlock::try_drop(Player *player)
  429. {
  430. SoundManager::current()->play("sounds/brick.wav", get_pos());
  431. if (m_sprite->get_action() == "empty")
  432. return;
  433. // Determine the area below the bonus block. If it's solid, send it up regardless (except for dolls).
  434. Rectf dest_;
  435. dest_.set_left(m_col.m_bbox.get_left() + 1);
  436. dest_.set_top(m_col.m_bbox.get_bottom() + 1);
  437. dest_.set_right(m_col.m_bbox.get_right() - 1);
  438. dest_.set_bottom(dest_.get_top() + 30);
  439. if (!Sector::get().is_free_of_statics(dest_, this, true) && !(m_contents == Content::ONEUP))
  440. {
  441. try_open(player);
  442. return;
  443. }
  444. if (player == nullptr)
  445. player = Sector::get().get_nearest_player(m_col.m_bbox);
  446. if (player == nullptr)
  447. return;
  448. Direction direction = (player->get_bbox().get_middle().x > m_col.m_bbox.get_middle().x) ? Direction::LEFT : Direction::RIGHT;
  449. bool countdown = false;
  450. bool play_upgrade_sound = false;
  451. switch (m_contents) {
  452. case Content::COIN:
  453. {
  454. try_open(player);
  455. break;
  456. }
  457. case Content::FIREGROW:
  458. {
  459. drop_growup_bonus(player, PowerUp::FIRE, direction, countdown);
  460. break;
  461. }
  462. case Content::ICEGROW:
  463. {
  464. drop_growup_bonus(player, PowerUp::ICE, direction, countdown);
  465. break;
  466. }
  467. case Content::AIRGROW:
  468. {
  469. drop_growup_bonus(player, PowerUp::AIR, direction, countdown);
  470. break;
  471. }
  472. case Content::EARTHGROW:
  473. {
  474. drop_growup_bonus(player, PowerUp::EARTH, direction, countdown);
  475. break;
  476. }
  477. case Content::RETROGROW:
  478. {
  479. drop_growup_bonus(player, PowerUp::COFFEE, direction, countdown,
  480. "images/powerups/retro/mints.png");
  481. break;
  482. }
  483. case Content::STAR:
  484. {
  485. Sector::get().add<Star>(get_pos() + Vector(0, 32), direction);
  486. play_upgrade_sound = true;
  487. countdown = true;
  488. break;
  489. }
  490. case Content::RETROSTAR:
  491. {
  492. Sector::get().add<Star>(get_pos() + Vector(0, 32), direction,
  493. "images/powerups/retro/golden_herring.png");
  494. play_upgrade_sound = true;
  495. countdown = true;
  496. break;
  497. }
  498. case Content::ONEUP:
  499. {
  500. Sector::get().add<OneUp>(get_pos(), Direction::DOWN);
  501. play_upgrade_sound = true;
  502. countdown = true;
  503. break;
  504. }
  505. case Content::CUSTOM:
  506. {
  507. // NOTE: Non-portable trampolines could be moved to Content::CUSTOM, but they should not drop.
  508. auto obj_copy = GameObjectFactory::instance().create(m_object->get_class_name(), get_pos() + Vector(0, 32),
  509. direction, m_object->save());
  510. Sector::get().add_object(std::move(obj_copy));
  511. play_upgrade_sound = true;
  512. countdown = true;
  513. break;
  514. }
  515. case Content::SCRIPT:
  516. {
  517. countdown = true;
  518. break;
  519. } // This prevents default contents from being assumed because scripts always run.
  520. case Content::LIGHT:
  521. case Content::LIGHT_ON:
  522. case Content::TRAMPOLINE:
  523. case Content::RAIN:
  524. {
  525. try_open(player);
  526. break;
  527. }
  528. case Content::ROCK:
  529. {
  530. Sector::get().add<Rock>(get_pos() + Vector(0, 32), "images/objects/rock/rock.sprite");
  531. countdown = true;
  532. break;
  533. }
  534. case Content::PORTABLE_TRAMPOLINE:
  535. {
  536. Sector::get().add<Trampoline>(get_pos() + Vector(0, 32), true);
  537. countdown = true;
  538. break;
  539. }
  540. case Content::POTION:
  541. {
  542. Sector::get().add<PowerUp>(get_pos() + Vector(0, 32), PowerUp::FLIP);
  543. countdown = true;
  544. break;
  545. }
  546. case Content::EXPLODE:
  547. {
  548. Sector::get().add<CoinExplode>(get_pos() + Vector (0, 40), !m_parent_dispenser, m_coin_sprite);
  549. play_upgrade_sound = true;
  550. countdown = true;
  551. break;
  552. }
  553. }
  554. if (play_upgrade_sound)
  555. SoundManager::current()->play("sounds/upgrade.wav", get_pos(), UPGRADE_SOUND_GAIN);
  556. if (!m_script.empty()) { // Scripts always run if defined.
  557. Sector::get().run_script(m_script, "powerup-script");
  558. }
  559. if (countdown) { // Only decrease the hit counter if try_open was not called.
  560. if (m_hit_counter == 1) {
  561. set_action("empty");
  562. } else {
  563. m_hit_counter--;
  564. }
  565. }
  566. }
  567. void
  568. BonusBlock::raise_growup_bonus(Player* player, const BonusType& bonus, const Direction& dir,
  569. const std::string& growup_sprite, const std::string& flower_sprite)
  570. {
  571. std::unique_ptr<MovingObject> obj;
  572. if (player->get_status().bonus[player->get_id()] == NO_BONUS)
  573. {
  574. obj = std::make_unique<GrowUp>(get_pos(), dir, growup_sprite);
  575. }
  576. else
  577. {
  578. obj = std::make_unique<Flower>(bonus, flower_sprite);
  579. }
  580. Sector::get().add<SpecialRiser>(get_pos(), std::move(obj));
  581. SoundManager::current()->play("sounds/upgrade.wav", get_pos(), UPGRADE_SOUND_GAIN);
  582. }
  583. void
  584. BonusBlock::drop_growup_bonus(Player* player, int type, const Direction& dir, bool& countdown,
  585. const std::string& growup_sprite)
  586. {
  587. if (player->get_status().bonus[player->get_id()] == NO_BONUS)
  588. {
  589. Sector::get().add<GrowUp>(get_pos() + Vector(0, 32), dir, growup_sprite);
  590. }
  591. else
  592. {
  593. Sector::get().add<PowerUp>(get_pos() + Vector(0, 32), type);
  594. }
  595. SoundManager::current()->play("sounds/upgrade.wav", get_pos(), UPGRADE_SOUND_GAIN);
  596. countdown = true;
  597. }
  598. void
  599. BonusBlock::draw(DrawingContext& context)
  600. {
  601. // Perform the regular drawing first.
  602. Block::draw(context);
  603. // Draw the light if the bonus block is in the "on" state.
  604. if (m_sprite->get_action() == "on")
  605. {
  606. Vector pos = get_pos() + (m_col.m_bbox.get_size().as_vector() - Vector(static_cast<float>(m_lightsprite->get_width()),
  607. static_cast<float>(m_lightsprite->get_height()))) / 2.0f;
  608. context.light().draw_surface(m_lightsprite, pos, 10);
  609. }
  610. }
  611. BonusBlock::Content
  612. BonusBlock::get_content_from_string(const std::string& contentstring) const
  613. {
  614. if (contentstring == "coin")
  615. return Content::COIN;
  616. else if (contentstring == "firegrow")
  617. return Content::FIREGROW;
  618. else if (contentstring == "icegrow")
  619. return Content::ICEGROW;
  620. else if (contentstring == "airgrow")
  621. return Content::AIRGROW;
  622. else if (contentstring == "earthgrow")
  623. return Content::EARTHGROW;
  624. else if (contentstring == "retrogrow")
  625. return Content::RETROGROW;
  626. else if (contentstring == "star")
  627. return Content::STAR;
  628. else if (contentstring == "retrostar")
  629. return Content::RETROSTAR;
  630. else if (contentstring == "1up")
  631. return Content::ONEUP;
  632. else if (contentstring == "custom")
  633. return Content::CUSTOM;
  634. else if (contentstring == "script") // Use this when the bonus block is intended to contain ONLY a script.
  635. return Content::SCRIPT;
  636. else if (contentstring == "light")
  637. return Content::LIGHT;
  638. else if (contentstring == "light-on")
  639. return Content::LIGHT_ON;
  640. else if (contentstring == "trampoline")
  641. return Content::TRAMPOLINE;
  642. else if (contentstring == "portabletrampoline")
  643. return Content::PORTABLE_TRAMPOLINE;
  644. else if (contentstring == "potion")
  645. return Content::POTION;
  646. else if (contentstring == "rock")
  647. return Content::ROCK;
  648. else if (contentstring == "rain")
  649. return Content::RAIN;
  650. else if (contentstring == "explode")
  651. return Content::EXPLODE;
  652. else
  653. {
  654. log_warning << "Invalid bonus block contents '" << contentstring << "'" << std::endl;
  655. return Content::COIN;
  656. }
  657. }
  658. void
  659. BonusBlock::preload_contents(int d)
  660. {
  661. switch (d)
  662. {
  663. case 6: // Light.
  664. case 15: // Light (On).
  665. SoundManager::current()->preload("sounds/switch.ogg");
  666. m_lightsprite=Surface::from_file("/images/objects/lightmap_light/bonusblock_light.png");
  667. break;
  668. case 7:
  669. // Uncomment the following line if this is to be moved to the "custom" case.
  670. // object = new Trampoline(get_pos(), false);
  671. break;
  672. case 8: // Trampoline.
  673. set_object(std::make_unique<Trampoline>(get_pos(), true));
  674. break;
  675. case 9: // Rock.
  676. set_object(std::make_unique<Rock>(get_pos(), "images/objects/rock/rock.sprite"));
  677. break;
  678. case 12: // Red potion.
  679. set_object(std::make_unique<PowerUp>(get_pos(), PowerUp::FLIP));
  680. break;
  681. default:
  682. break;
  683. }
  684. }
  685. /* EOF */