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 | |
37 | static const float SQUISH_TIME = 2; |
38 | static const float GEAR_TIME = 2; |
39 | static const float BURN_TIME = 1; |
40 | |
41 | static const float X_OFFSCREEN_DISTANCE = 1280; |
42 | static const float Y_OFFSCREEN_DISTANCE = 800; |
43 | |
44 | BadGuy::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 | |
50 | BadGuy::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 | |
83 | BadGuy::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 | |
123 | void |
124 | BadGuy::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 | |
151 | void |
152 | BadGuy::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 | |
257 | Direction |
258 | BadGuy::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 | |
272 | void |
273 | BadGuy::initialize() |
274 | { |
275 | } |
276 | |
277 | void |
278 | BadGuy::activate() |
279 | { |
280 | } |
281 | |
282 | void |
283 | BadGuy::deactivate() |
284 | { |
285 | } |
286 | |
287 | void |
288 | BadGuy::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 | |
295 | void |
296 | BadGuy::inactive_update(float ) |
297 | { |
298 | } |
299 | |
300 | void |
301 | BadGuy::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 | |
329 | HitResponse |
330 | BadGuy::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 | |
379 | void |
380 | BadGuy::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 | |
387 | HitResponse |
388 | BadGuy::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 | |
416 | HitResponse |
417 | BadGuy::collision_badguy(BadGuy& , const CollisionHit& ) |
418 | { |
419 | return FORCE_MOVE; |
420 | } |
421 | |
422 | bool |
423 | BadGuy::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 | |
438 | HitResponse |
439 | BadGuy::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 | |
484 | void |
485 | BadGuy::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 | |
504 | void |
505 | BadGuy::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 | |
543 | void |
544 | BadGuy::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 | |
562 | void |
563 | BadGuy::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 | |
599 | bool |
600 | BadGuy::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 | |
621 | void |
622 | BadGuy::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 | |
649 | bool |
650 | BadGuy::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 | |
669 | Player* |
670 | BadGuy::get_nearest_player() const |
671 | { |
672 | return Sector::get().get_nearest_player(m_col.m_bbox); |
673 | } |
674 | |
675 | void |
676 | BadGuy::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 | |
684 | bool |
685 | BadGuy::on_ground() const |
686 | { |
687 | return m_on_ground_flag; |
688 | } |
689 | |
690 | bool |
691 | BadGuy::is_active() const |
692 | { |
693 | return m_is_active_flag; |
694 | } |
695 | |
696 | Vector |
697 | BadGuy::get_floor_normal() const |
698 | { |
699 | return m_floor_normal; |
700 | } |
701 | |
702 | void |
703 | BadGuy::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 | |
724 | void |
725 | BadGuy::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 | |
738 | bool |
739 | BadGuy::is_freezable() const |
740 | { |
741 | return false; |
742 | } |
743 | |
744 | bool |
745 | BadGuy::is_frozen() const |
746 | { |
747 | return m_frozen; |
748 | } |
749 | |
750 | bool |
751 | BadGuy::is_in_water() const |
752 | { |
753 | return m_in_water; |
754 | } |
755 | |
756 | void |
757 | BadGuy::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 | |
804 | void |
805 | BadGuy::extinguish() |
806 | { |
807 | } |
808 | |
809 | bool |
810 | BadGuy::is_flammable() const |
811 | { |
812 | return true; |
813 | } |
814 | |
815 | bool |
816 | BadGuy::is_ignited() const |
817 | { |
818 | return m_ignited; |
819 | } |
820 | |
821 | void |
822 | BadGuy::set_colgroup_active(CollisionGroup group_) |
823 | { |
824 | m_colgroup_active = group_; |
825 | if (m_state == STATE_ACTIVE) set_group(group_); |
826 | } |
827 | |
828 | ObjectSettings |
829 | BadGuy::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 | |
841 | void |
842 | BadGuy::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 | |