1// SuperTux
2// Copyright (C) 2006 Matthias Braun <matze@braunis.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 "badguy/badguy.hpp"
18
19#include "audio/sound_manager.hpp"
20#include "badguy/dispenser.hpp"
21#include "editor/editor.hpp"
22#include "math/random.hpp"
23#include "object/bullet.hpp"
24#include "object/camera.hpp"
25#include "object/player.hpp"
26#include "object/portable.hpp"
27#include "object/sprite_particle.hpp"
28#include "object/water_drop.hpp"
29#include "sprite/sprite.hpp"
30#include "sprite/sprite_manager.hpp"
31#include "supertux/level.hpp"
32#include "supertux/sector.hpp"
33#include "supertux/tile.hpp"
34#include "util/reader_mapping.hpp"
35#include "util/writer.hpp"
36
37static const float SQUISH_TIME = 2;
38static const float GEAR_TIME = 2;
39static const float BURN_TIME = 1;
40
41static const float X_OFFSCREEN_DISTANCE = 1280;
42static const float Y_OFFSCREEN_DISTANCE = 800;
43
44BadGuy::BadGuy(const Vector& pos, const std::string& sprite_name_, int layer_,
45 const std::string& light_sprite_name) :
46 BadGuy(pos, Direction::LEFT, sprite_name_, layer_, light_sprite_name)
47{
48}
49
50BadGuy::BadGuy(const Vector& pos, Direction direction, const std::string& sprite_name_, int layer_,
51 const std::string& light_sprite_name) :
52 MovingSprite(pos, sprite_name_, layer_, COLGROUP_DISABLED),
53 m_physic(),
54 m_countMe(true),
55 m_is_initialized(false),
56 m_start_position(m_col.m_bbox.p1()),
57 m_dir(direction),
58 m_start_dir(direction),
59 m_frozen(false),
60 m_ignited(false),
61 m_in_water(false),
62 m_dead_script(),
63 m_melting_time(0),
64 m_lightsprite(SpriteManager::current()->create(light_sprite_name)),
65 m_glowing(false),
66 m_state(STATE_INIT),
67 m_is_active_flag(),
68 m_state_timer(),
69 m_on_ground_flag(false),
70 m_floor_normal(),
71 m_colgroup_active(COLGROUP_MOVING),
72 m_parent_dispenser()
73{
74 SoundManager::current()->preload("sounds/squish.wav");
75 SoundManager::current()->preload("sounds/fall.wav");
76 SoundManager::current()->preload("sounds/splash.ogg");
77 SoundManager::current()->preload("sounds/fire.ogg");
78
79 m_dir = (m_start_dir == Direction::AUTO) ? Direction::LEFT : m_start_dir;
80 m_lightsprite->set_blend(Blend::ADD);
81}
82
83BadGuy::BadGuy(const ReaderMapping& reader, const std::string& sprite_name_, int layer_,
84 const std::string& light_sprite_name) :
85 MovingSprite(reader, sprite_name_, layer_, COLGROUP_DISABLED),
86 m_physic(),
87 m_countMe(true),
88 m_is_initialized(false),
89 m_start_position(m_col.m_bbox.p1()),
90 m_dir(Direction::LEFT),
91 m_start_dir(Direction::AUTO),
92 m_frozen(false),
93 m_ignited(false),
94 m_in_water(false),
95 m_dead_script(),
96 m_melting_time(0),
97 m_lightsprite(SpriteManager::current()->create(light_sprite_name)),
98 m_glowing(false),
99 m_state(STATE_INIT),
100 m_is_active_flag(),
101 m_state_timer(),
102 m_on_ground_flag(false),
103 m_floor_normal(),
104 m_colgroup_active(COLGROUP_MOVING),
105 m_parent_dispenser()
106{
107 std::string dir_str = "auto";
108 reader.get("direction", dir_str);
109 m_start_dir = str2dir( dir_str );
110 m_dir = m_start_dir;
111
112 reader.get("dead-script", m_dead_script);
113
114 SoundManager::current()->preload("sounds/squish.wav");
115 SoundManager::current()->preload("sounds/fall.wav");
116 SoundManager::current()->preload("sounds/splash.ogg");
117 SoundManager::current()->preload("sounds/fire.ogg");
118
119 m_dir = (m_start_dir == Direction::AUTO) ? Direction::LEFT : m_start_dir;
120 m_lightsprite->set_blend(Blend::ADD);
121}
122
123void
124BadGuy::draw(DrawingContext& context)
125{
126 if (!m_sprite.get()) return;
127
128 if (m_state == STATE_INIT || m_state == STATE_INACTIVE)
129 {
130 if (Editor::is_active()) {
131 m_sprite->draw(context.color(), get_pos(), m_layer);
132 }
133 }
134 else
135 {
136 if (m_state == STATE_FALLING) {
137 context.push_transform();
138 context.set_flip(context.get_flip() ^ VERTICAL_FLIP);
139 m_sprite->draw(context.color(), get_pos(), m_layer);
140 context.pop_transform();
141 } else {
142 m_sprite->draw(context.color(), get_pos(), m_layer);
143 }
144
145 if (m_glowing) {
146 m_lightsprite->draw(context.light(), m_col.m_bbox.get_middle(), 0);
147 }
148 }
149}
150
151void
152BadGuy::update(float dt_sec)
153{
154 if (!Sector::get().inside(m_col.m_bbox)) {
155 auto this_portable = dynamic_cast<Portable*> (this);
156 if (!this_portable || !this_portable->is_grabbed())
157 {
158 run_dead_script();
159 m_is_active_flag = false;
160 remove_me();
161 // This was removed due to fixing a bug. If is it needed somewhere, then I'm sorry. --Hume2
162 /**if(countMe) {
163 // get badguy name from sprite_name ignoring path and extension
164 std::string badguy = sprite_name.substr(0, sprite_name.length() - 7);
165 int path_chars = badguy.rfind("/",badguy.length());
166 badguy = badguy.substr(path_chars + 1, badguy.length() - path_chars);
167 // log warning since badguys_killed can no longer reach total_badguys
168 std::string current_level = "[" + Sector::get().get_level()->filename + "] ";
169 log_warning << current_level << "Counted badguy " << badguy << " starting at " << start_position << " has left the sector" <<std::endl;;
170 }*/
171 return;
172 }
173 }
174 if ((m_state != STATE_INACTIVE) && is_offscreen()) {
175 if (m_state == STATE_ACTIVE) deactivate();
176 set_state(STATE_INACTIVE);
177 }
178
179 switch (m_state) {
180 case STATE_ACTIVE:
181 m_is_active_flag = true;
182 if (Editor::is_active()) {
183 break;
184 }
185 active_update(dt_sec);
186 break;
187
188 case STATE_INIT:
189 case STATE_INACTIVE:
190 m_is_active_flag = false;
191 inactive_update(dt_sec);
192 try_activate();
193 break;
194
195 case STATE_BURNING: {
196 m_is_active_flag = false;
197 m_col.m_movement = m_physic.get_movement(dt_sec);
198 if ( m_sprite->animation_done() ) {
199 remove_me();
200 }
201 } break;
202
203 case STATE_GEAR:
204 case STATE_SQUISHED:
205 m_is_active_flag = false;
206 if (m_state_timer.check()) {
207 remove_me();
208 break;
209 }
210 m_col.m_movement = m_physic.get_movement(dt_sec);
211 break;
212
213 case STATE_MELTING: {
214 m_is_active_flag = false;
215 m_col.m_movement = m_physic.get_movement(dt_sec);
216 if ( m_sprite->animation_done() || on_ground() ) {
217 Sector::get().add<WaterDrop>(m_col.m_bbox.p1(), get_water_sprite(), m_physic.get_velocity());
218 remove_me();
219 break;
220 }
221 } break;
222
223 case STATE_GROUND_MELTING:
224 m_is_active_flag = false;
225 m_col.m_movement = m_physic.get_movement(dt_sec);
226 if ( m_sprite->animation_done() ) {
227 remove_me();
228 }
229 break;
230
231 case STATE_INSIDE_MELTING: {
232 m_is_active_flag = false;
233 m_col.m_movement = m_physic.get_movement(dt_sec);
234 if ( on_ground() && m_sprite->animation_done() ) {
235 m_sprite->set_action(m_dir == Direction::LEFT ? "gear-left" : "gear-right", 1);
236 set_state(STATE_GEAR);
237 }
238 int pa = graphicsRandom.rand(0,3);
239 float px = graphicsRandom.randf(m_col.m_bbox.get_left(), m_col.m_bbox.get_right());
240 float py = graphicsRandom.randf(m_col.m_bbox.get_top(), m_col.m_bbox.get_bottom());
241 Vector ppos = Vector(px, py);
242 Sector::get().add<SpriteParticle>(get_water_sprite(), "particle_" + std::to_string(pa),
243 ppos, ANCHOR_MIDDLE,
244 Vector(0, 0), Vector(0, 100 * Sector::get().get_gravity()),
245 LAYER_OBJECTS-1);
246 } break;
247
248 case STATE_FALLING:
249 m_is_active_flag = false;
250 m_col.m_movement = m_physic.get_movement(dt_sec);
251 break;
252 }
253
254 m_on_ground_flag = false;
255}
256
257Direction
258BadGuy::str2dir(const std::string& dir_str) const
259{
260 if ( dir_str == "auto" )
261 return Direction::AUTO;
262 if ( dir_str == "left" )
263 return Direction::LEFT;
264 if ( dir_str == "right" )
265 return Direction::RIGHT;
266
267 //default to "auto"
268 log_warning << "Badguy::str2dir: unknown direction \"" << dir_str << "\"" << std::endl;;
269 return Direction::AUTO;
270}
271
272void
273BadGuy::initialize()
274{
275}
276
277void
278BadGuy::activate()
279{
280}
281
282void
283BadGuy::deactivate()
284{
285}
286
287void
288BadGuy::active_update(float dt_sec)
289{
290 m_col.m_movement = m_physic.get_movement(dt_sec);
291 if (m_frozen)
292 m_sprite->stop_animation();
293}
294
295void
296BadGuy::inactive_update(float )
297{
298}
299
300void
301BadGuy::collision_tile(uint32_t tile_attributes)
302{
303 // Don't kill badguys that have already been killed
304 if (!is_active()) return;
305
306 if (tile_attributes & Tile::WATER && !is_in_water())
307 {
308 m_in_water = true;
309 SoundManager::current()->play("sounds/splash.ogg", get_pos());
310 }
311 if (!(tile_attributes & Tile::WATER) && is_in_water())
312 {
313 m_in_water = false;
314 }
315
316 if (tile_attributes & Tile::HURTS && is_hurtable()) {
317 if (tile_attributes & Tile::FIRE) {
318 if (is_flammable()) ignite();
319 }
320 else if (tile_attributes & Tile::ICE) {
321 if (is_freezable()) freeze();
322 }
323 else {
324 kill_fall();
325 }
326 }
327}
328
329HitResponse
330BadGuy::collision(GameObject& other, const CollisionHit& hit)
331{
332 if (!is_active()) return ABORT_MOVE;
333
334 auto badguy = dynamic_cast<BadGuy*> (&other);
335 if (badguy && badguy->is_active() && badguy->m_col.get_group() == COLGROUP_MOVING) {
336
337 /* Badguys don't let badguys squish other badguys. It's bad. */
338#if 0
339 // hit from above?
340 if (badguy->get_bbox().get_bottom() < (bbox.get_top() + 16)) {
341 if (collision_squished(*badguy)) {
342 return ABORT_MOVE;
343 }
344 }
345#endif
346
347 return collision_badguy(*badguy, hit);
348 }
349
350 auto player = dynamic_cast<Player*> (&other);
351 if (player) {
352
353 // hit from above?
354 if (player->get_bbox().get_bottom() < (m_col.m_bbox.get_top() + 16)) {
355 if (player->is_stone()) {
356 kill_fall();
357 return FORCE_MOVE;
358 }
359 if (collision_squished(*player)) {
360 return FORCE_MOVE;
361 }
362 }
363
364 if (player->is_stone()) {
365 collision_solid(hit);
366 return FORCE_MOVE;
367 }
368
369 return collision_player(*player, hit);
370 }
371
372 auto bullet = dynamic_cast<Bullet*> (&other);
373 if (bullet)
374 return collision_bullet(*bullet, hit);
375
376 return FORCE_MOVE;
377}
378
379void
380BadGuy::collision_solid(const CollisionHit& hit)
381{
382 m_physic.set_velocity_x(0);
383 m_physic.set_velocity_y(0);
384 update_on_ground_flag(hit);
385}
386
387HitResponse
388BadGuy::collision_player(Player& player, const CollisionHit& )
389{
390 if (player.is_invincible()) {
391 kill_fall();
392 return ABORT_MOVE;
393 }
394 if(player.get_grabbed_object() != nullptr)
395 {
396 auto badguy = dynamic_cast<BadGuy*>(player.get_grabbed_object());
397 if(badguy != nullptr)
398 {
399 player.get_grabbed_object()->ungrab(player, player.m_dir);
400 player.stop_grabbing();
401 badguy->kill_fall();
402 kill_fall();
403 return ABORT_MOVE;
404 }
405 }
406
407 //TODO: unfreeze timer
408 if (m_frozen)
409 //unfreeze();
410 return FORCE_MOVE;
411
412 player.kill(false);
413 return FORCE_MOVE;
414}
415
416HitResponse
417BadGuy::collision_badguy(BadGuy& , const CollisionHit& )
418{
419 return FORCE_MOVE;
420}
421
422bool
423BadGuy::collision_squished(GameObject& object)
424{
425 // frozen badguys can be killed with butt-jump
426 if (m_frozen)
427 {
428 auto player = dynamic_cast<Player*>(&object);
429 if (player && (player->m_does_buttjump)) {
430 player->bounce(*this);
431 kill_fall();
432 return true;
433 }
434 }
435 return false;
436}
437
438HitResponse
439BadGuy::collision_bullet(Bullet& bullet, const CollisionHit& hit)
440{
441 if (is_frozen()) {
442 if (bullet.get_type() == FIRE_BONUS) {
443 // fire bullet thaws frozen badguys
444 unfreeze();
445 bullet.remove_me();
446 return ABORT_MOVE;
447 } else {
448 // other bullets ricochet
449 bullet.ricochet(*this, hit);
450 return FORCE_MOVE;
451 }
452 }
453 else if (is_ignited()) {
454 if (bullet.get_type() == ICE_BONUS) {
455 // ice bullets extinguish ignited badguys
456 extinguish();
457 bullet.remove_me();
458 return ABORT_MOVE;
459 } else {
460 // other bullets are absorbed by ignited badguys
461 bullet.remove_me();
462 return FORCE_MOVE;
463 }
464 }
465 else if (bullet.get_type() == FIRE_BONUS && is_flammable()) {
466 // fire bullets ignite flammable badguys
467 ignite();
468 bullet.remove_me();
469 return ABORT_MOVE;
470 }
471 else if (bullet.get_type() == ICE_BONUS && is_freezable()) {
472 // ice bullets freeze freezable badguys
473 freeze();
474 bullet.remove_me();
475 return ABORT_MOVE;
476 }
477 else {
478 // in all other cases, bullets ricochet
479 bullet.ricochet(*this, hit);
480 return FORCE_MOVE;
481 }
482}
483
484void
485BadGuy::kill_squished(GameObject& object)
486{
487 if (!is_active()) return;
488
489 SoundManager::current()->play("sounds/squish.wav", get_pos());
490 m_physic.enable_gravity(true);
491 m_physic.set_velocity_x(0);
492 m_physic.set_velocity_y(0);
493 set_state(STATE_SQUISHED);
494 set_group(COLGROUP_MOVING_ONLY_STATIC);
495 auto player = dynamic_cast<Player*>(&object);
496 if (player) {
497 player->bounce(*this);
498 }
499
500 // start dead-script
501 run_dead_script();
502}
503
504void
505BadGuy::kill_fall()
506{
507 if (!is_active()) return;
508
509 if (m_frozen) {
510 SoundManager::current()->play("sounds/brick.wav");
511 Vector pr_pos;
512 float cx = m_col.m_bbox.get_width() / 2;
513 float cy = m_col.m_bbox.get_height() / 2;
514 for (pr_pos.x = 0; pr_pos.x < m_col.m_bbox.get_width(); pr_pos.x += 16) {
515 for (pr_pos.y = 0; pr_pos.y < m_col.m_bbox.get_height(); pr_pos.y += 16) {
516 Vector speed = Vector((pr_pos.x - cx) * 8, (pr_pos.y - cy) * 8 + 100);
517 Sector::get().add<SpriteParticle>(
518 "images/particles/ice_piece1.sprite", "default",
519 m_col.m_bbox.p1() + pr_pos, ANCHOR_MIDDLE,
520 speed,
521 Vector(0, Sector::get().get_gravity() * 100.0f));
522 }
523 }
524 // start dead-script
525 run_dead_script();
526 remove_me();
527 } else {
528 SoundManager::current()->play("sounds/fall.wav", get_pos());
529 m_physic.set_velocity_y(0);
530 m_physic.set_acceleration_y(0);
531 m_physic.enable_gravity(true);
532 set_state(STATE_FALLING);
533
534 // Set the badguy layer to be the foremost, so that
535 // this does not reveal secret tilemaps:
536 m_layer = Sector::get().get_foremost_layer() + 1;
537 // start dead-script
538 run_dead_script();
539 }
540
541}
542
543void
544BadGuy::run_dead_script()
545{
546 if (m_countMe)
547 Sector::get().get_level().m_stats.m_badguys++;
548
549 m_countMe = false;
550
551 if (m_parent_dispenser != nullptr)
552 {
553 m_parent_dispenser->notify_dead();
554 }
555
556 // start dead-script
557 if (!m_dead_script.empty()) {
558 Sector::get().run_script(m_dead_script, "dead-script");
559 }
560}
561
562void
563BadGuy::set_state(State state_)
564{
565 if (m_state == state_)
566 return;
567
568 State laststate = m_state;
569 m_state = state_;
570 switch (state_) {
571 case STATE_BURNING:
572 m_state_timer.start(BURN_TIME);
573 break;
574 case STATE_SQUISHED:
575 m_state_timer.start(SQUISH_TIME);
576 break;
577 case STATE_GEAR:
578 m_state_timer.start(GEAR_TIME);
579 break;
580 case STATE_ACTIVE:
581 set_group(m_colgroup_active);
582 //bbox.set_pos(start_position);
583 break;
584 case STATE_INACTIVE:
585 // was the badguy dead anyway?
586 if (laststate == STATE_SQUISHED || laststate == STATE_FALLING) {
587 remove_me();
588 }
589 set_group(COLGROUP_DISABLED);
590 break;
591 case STATE_FALLING:
592 set_group(COLGROUP_DISABLED);
593 break;
594 default:
595 break;
596 }
597}
598
599bool
600BadGuy::is_offscreen() const
601{
602 Vector dist;
603 if (Editor::is_active()) {
604 Camera& cam = Sector::get().get_camera();
605 dist = cam.get_center() - m_col.m_bbox.get_middle();
606 }
607 auto player = get_nearest_player();
608 if (!player)
609 return false;
610 if (!Editor::is_active()) {
611 dist = player->get_bbox().get_middle() - m_col.m_bbox.get_middle();
612 }
613 // In SuperTux 0.1.x, Badguys were activated when Tux<->Badguy center distance was approx. <= ~668px
614 // This doesn't work for wide-screen monitors which give us a virt. res. of approx. 1066px x 600px
615 if ((fabsf(dist.x) <= X_OFFSCREEN_DISTANCE) && (fabsf(dist.y) <= Y_OFFSCREEN_DISTANCE)) {
616 return false;
617 }
618 return true;
619}
620
621void
622BadGuy::try_activate()
623{
624 // Don't activate if player is dying
625 auto player = get_nearest_player();
626 if (!player) return;
627
628 if (!is_offscreen()) {
629 set_state(STATE_ACTIVE);
630 if (!m_is_initialized) {
631
632 // if starting direction was set to AUTO, this is our chance to re-orient the badguy
633 if (m_start_dir == Direction::AUTO) {
634 auto player_ = get_nearest_player();
635 if (player_ && (player_->get_bbox().get_left() > m_col.m_bbox.get_right())) {
636 m_dir = Direction::RIGHT;
637 } else {
638 m_dir = Direction::LEFT;
639 }
640 }
641
642 initialize();
643 m_is_initialized = true;
644 }
645 activate();
646 }
647}
648
649bool
650BadGuy::might_fall(int height) const
651{
652 // make sure we check for at least a 1-pixel fall
653 assert(height > 0);
654
655 float x1;
656 float x2;
657 float y1 = m_col.m_bbox.get_bottom() + 1;
658 float y2 = m_col.m_bbox.get_bottom() + 1 + static_cast<float>(height);
659 if (m_dir == Direction::LEFT) {
660 x1 = m_col.m_bbox.get_left() - 1;
661 x2 = m_col.m_bbox.get_left();
662 } else {
663 x1 = m_col.m_bbox.get_right();
664 x2 = m_col.m_bbox.get_right() + 1;
665 }
666 return Sector::get().is_free_of_statics(Rectf(x1, y1, x2, y2));
667}
668
669Player*
670BadGuy::get_nearest_player() const
671{
672 return Sector::get().get_nearest_player(m_col.m_bbox);
673}
674
675void
676BadGuy::update_on_ground_flag(const CollisionHit& hit)
677{
678 if (hit.bottom) {
679 m_on_ground_flag = true;
680 m_floor_normal = hit.slope_normal;
681 }
682}
683
684bool
685BadGuy::on_ground() const
686{
687 return m_on_ground_flag;
688}
689
690bool
691BadGuy::is_active() const
692{
693 return m_is_active_flag;
694}
695
696Vector
697BadGuy::get_floor_normal() const
698{
699 return m_floor_normal;
700}
701
702void
703BadGuy::freeze()
704{
705 set_group(COLGROUP_MOVING_STATIC);
706 m_frozen = true;
707
708 if (m_sprite->has_action("iced-left"))
709 m_sprite->set_action(m_dir == Direction::LEFT ? "iced-left" : "iced-right", 1);
710 // when the sprite doesn't have separate actions for left and right, it tries to use an universal one.
711 else
712 {
713 if (m_sprite->has_action("iced"))
714 m_sprite->set_action("iced", 1);
715 // when no iced action exists, default to shading badguy blue
716 else
717 {
718 m_sprite->set_color(Color(0.60f, 0.72f, 0.88f));
719 m_sprite->stop_animation();
720 }
721 }
722}
723
724void
725BadGuy::unfreeze()
726{
727 set_group(m_colgroup_active);
728 m_frozen = false;
729
730 // restore original color if needed
731 if ((!m_sprite->has_action("iced-left")) && (!m_sprite->has_action("iced")) )
732 {
733 m_sprite->set_color(Color(1.f, 1.f, 1.f));
734 m_sprite->set_animation_loops();
735 }
736}
737
738bool
739BadGuy::is_freezable() const
740{
741 return false;
742}
743
744bool
745BadGuy::is_frozen() const
746{
747 return m_frozen;
748}
749
750bool
751BadGuy::is_in_water() const
752{
753 return m_in_water;
754}
755
756void
757BadGuy::ignite()
758{
759 if (!is_flammable() || m_ignited) {
760 return;
761 }
762
763 m_physic.enable_gravity(true);
764 m_physic.set_velocity_x(0);
765 m_physic.set_velocity_y(0);
766 set_group(COLGROUP_MOVING_ONLY_STATIC);
767 m_sprite->stop_animation();
768 m_ignited = true;
769
770 if (m_sprite->has_action("melting-left")) {
771
772 // melt it!
773 if (m_sprite->has_action("ground-melting-left") && on_ground()) {
774 m_sprite->set_action(m_dir == Direction::LEFT ? "ground-melting-left" : "ground-melting-right", 1);
775 SoundManager::current()->play("sounds/splash.ogg", get_pos());
776 set_state(STATE_GROUND_MELTING);
777 } else {
778 m_sprite->set_action(m_dir == Direction::LEFT ? "melting-left" : "melting-right", 1);
779 SoundManager::current()->play("sounds/sizzle.ogg", get_pos());
780 set_state(STATE_MELTING);
781 }
782
783 run_dead_script();
784
785 } else if (m_sprite->has_action("burning-left")) {
786 // burn it!
787 m_glowing = true;
788 SoundManager::current()->play("sounds/fire.ogg", get_pos());
789 m_sprite->set_action(m_dir == Direction::LEFT ? "burning-left" : "burning-right", 1);
790 set_state(STATE_BURNING);
791 run_dead_script();
792 } else if (m_sprite->has_action("inside-melting-left")) {
793 // melt it inside!
794 SoundManager::current()->play("sounds/splash.ogg", get_pos());
795 m_sprite->set_action(m_dir == Direction::LEFT ? "inside-melting-left" : "inside-melting-right", 1);
796 set_state(STATE_INSIDE_MELTING);
797 run_dead_script();
798 } else {
799 // Let it fall off the screen then.
800 kill_fall();
801 }
802}
803
804void
805BadGuy::extinguish()
806{
807}
808
809bool
810BadGuy::is_flammable() const
811{
812 return true;
813}
814
815bool
816BadGuy::is_ignited() const
817{
818 return m_ignited;
819}
820
821void
822BadGuy::set_colgroup_active(CollisionGroup group_)
823{
824 m_colgroup_active = group_;
825 if (m_state == STATE_ACTIVE) set_group(group_);
826}
827
828ObjectSettings
829BadGuy::get_settings()
830{
831 ObjectSettings result = MovingSprite::get_settings();
832
833 result.add_direction(_("Direction"), &m_start_dir, Direction::AUTO, "direction");
834 result.add_script(_("Death script"), &m_dead_script, "dead-script");
835
836 result.reorder({"direction", "sprite", "x", "y"});
837
838 return result;
839}
840
841void
842BadGuy::after_editor_set()
843{
844 if (m_dir == Direction::AUTO)
845 {
846 if (m_sprite->has_action("editor-left")) {
847 m_sprite->set_action("editor-left");
848 } else if (m_sprite->has_action("editor-right")) {
849 m_sprite->set_action("editor-right");
850 } else if (m_sprite->has_action("left")) {
851 m_sprite->set_action("left");
852 } else if (m_sprite->has_action("normal")) {
853 m_sprite->set_action("normal");
854 } else if (m_sprite->has_action("idle")) {
855 m_sprite->set_action("idle");
856 } else if (m_sprite->has_action("idle-left")) {
857 m_sprite->set_action("idle-left");
858 } else if (m_sprite->has_action("flying-left")) {
859 m_sprite->set_action("flying-left");
860 } else if (m_sprite->has_action("walking-left")) {
861 m_sprite->set_action("walking-left");
862 } else if (m_sprite->has_action("flying")) {
863 m_sprite->set_action("flying");
864 } else if (m_sprite->has_action("standing-left")) {
865 m_sprite->set_action("standing-left");
866 } else {
867 log_warning << "couldn't find editor sprite for badguy direction='auto': " << get_class() << std::endl;
868 }
869 }
870 else
871 {
872 std::string action_str = dir_to_string(m_dir);
873
874 if (m_sprite->has_action("editor-" + action_str)) {
875 m_sprite->set_action("editor-" + action_str);
876 } else if (m_sprite->has_action(action_str)) {
877 m_sprite->set_action(action_str);
878 } else if (m_sprite->has_action("idle-" + action_str)) {
879 m_sprite->set_action("idle-" + action_str);
880 } else if (m_sprite->has_action("flying-" + action_str)) {
881 m_sprite->set_action("flying-" + action_str);
882 } else if (m_sprite->has_action("standing-" + action_str)) {
883 m_sprite->set_action("standing-" + action_str);
884 } else if (m_sprite->has_action("walking-" + action_str)) {
885 m_sprite->set_action("walking-" + action_str);
886 } else if (m_sprite->has_action("left")) {
887 m_sprite->set_action("left");
888 } else if (m_sprite->has_action("normal")) {
889 m_sprite->set_action("normal");
890 } else if (m_sprite->has_action("idle")) {
891 m_sprite->set_action("idle");
892 } else if (m_sprite->has_action("flying")) {
893 m_sprite->set_action("flying");
894 } else {
895 log_warning << "couldn't find editor sprite for badguy direction='" << action_str << "': "
896 << get_class() << std::endl;
897 }
898 }
899}
900
901/* EOF */
902