diff --git a/data/images/creatures/ghosttree/blue_root-left.png b/data/images/creatures/ghosttree/blue_root-left.png new file mode 100644 index 00000000000..1b1f00b34e1 Binary files /dev/null and b/data/images/creatures/ghosttree/blue_root-left.png differ diff --git a/data/images/creatures/ghosttree/blue_root-up.png b/data/images/creatures/ghosttree/blue_root-up.png new file mode 100644 index 00000000000..9e29207fe16 Binary files /dev/null and b/data/images/creatures/ghosttree/blue_root-up.png differ diff --git a/data/images/creatures/ghosttree/blue_root.sprite b/data/images/creatures/ghosttree/blue_root.sprite new file mode 100644 index 00000000000..600b1d670a9 --- /dev/null +++ b/data/images/creatures/ghosttree/blue_root.sprite @@ -0,0 +1,22 @@ +(supertux-sprite + (action + (name "flying-left") + (hitbox 15 12 70 8) + (images "blue_root-left.png") + ) + (action + (name "flying-up") + (hitbox 12 15 8 70) + (images "blue_root-up.png") + ) + (action + (name "flying-right") + (hitbox 11 12 70 8) + (mirror-action "flying-left") + ) + (action + (name "flying-down") + (hitbox 12 11 8 70) + (flip-action "flying-up") + ) +) diff --git a/data/images/creatures/ghosttree/bust-0.png b/data/images/creatures/ghosttree/bust-0.png new file mode 100644 index 00000000000..22b9ca9b0b8 Binary files /dev/null and b/data/images/creatures/ghosttree/bust-0.png differ diff --git a/data/images/creatures/ghosttree/charge-0.png b/data/images/creatures/ghosttree/charge-0.png new file mode 100644 index 00000000000..3a6736615d9 Binary files /dev/null and b/data/images/creatures/ghosttree/charge-0.png differ diff --git a/data/images/creatures/ghosttree/ghosttree-dying-0.png b/data/images/creatures/ghosttree/ghosttree-dying-0.png deleted file mode 100644 index d6aa453c22e..00000000000 Binary files a/data/images/creatures/ghosttree/ghosttree-dying-0.png and /dev/null differ diff --git a/data/images/creatures/ghosttree/ghosttree-glow-dying-0.png b/data/images/creatures/ghosttree/ghosttree-glow-dying-0.png deleted file mode 100644 index 03672c3abc8..00000000000 Binary files a/data/images/creatures/ghosttree/ghosttree-glow-dying-0.png and /dev/null differ diff --git a/data/images/creatures/ghosttree/ghosttree-glow.png b/data/images/creatures/ghosttree/ghosttree-glow.png deleted file mode 100644 index f54ad3d1e82..00000000000 Binary files a/data/images/creatures/ghosttree/ghosttree-glow.png and /dev/null differ diff --git a/data/images/creatures/ghosttree/ghosttree-glow.sprite b/data/images/creatures/ghosttree/ghosttree-glow.sprite deleted file mode 100644 index c9c80b57db3..00000000000 --- a/data/images/creatures/ghosttree/ghosttree-glow.sprite +++ /dev/null @@ -1,20 +0,0 @@ -(supertux-sprite - (action - (name "default") - (hitbox 230 300 40 60) - (images - "ghosttree-glow.png" - ) - ) - (action - (name "dying") - (hitbox 230 300 40 60) - (fps 4) - (images - "ghosttree-glow-dying-0.png" - "ghosttree-glow-dying-0.png" - "ghosttree-glow-dying-0.png" - "ghosttree-glow-dying-0.png" - ) - ) -) diff --git a/data/images/creatures/ghosttree/ghosttree-swallow-0.png b/data/images/creatures/ghosttree/ghosttree-swallow-0.png deleted file mode 100644 index be9f9f6e8f8..00000000000 Binary files a/data/images/creatures/ghosttree/ghosttree-swallow-0.png and /dev/null differ diff --git a/data/images/creatures/ghosttree/ghosttree-swallow-1.png b/data/images/creatures/ghosttree/ghosttree-swallow-1.png deleted file mode 100644 index 43d5720ab78..00000000000 Binary files a/data/images/creatures/ghosttree/ghosttree-swallow-1.png and /dev/null differ diff --git a/data/images/creatures/ghosttree/ghosttree-swallow-2.png b/data/images/creatures/ghosttree/ghosttree-swallow-2.png deleted file mode 100644 index b0396403766..00000000000 Binary files a/data/images/creatures/ghosttree/ghosttree-swallow-2.png and /dev/null differ diff --git a/data/images/creatures/ghosttree/ghosttree-swallow-3.png b/data/images/creatures/ghosttree/ghosttree-swallow-3.png deleted file mode 100644 index c9a691f672f..00000000000 Binary files a/data/images/creatures/ghosttree/ghosttree-swallow-3.png and /dev/null differ diff --git a/data/images/creatures/ghosttree/ghosttree.png b/data/images/creatures/ghosttree/ghosttree.png deleted file mode 100644 index ee4d4d59e33..00000000000 Binary files a/data/images/creatures/ghosttree/ghosttree.png and /dev/null differ diff --git a/data/images/creatures/ghosttree/ghosttree.sprite b/data/images/creatures/ghosttree/ghosttree.sprite index d1494af7d8d..1faa9b9ed7d 100644 --- a/data/images/creatures/ghosttree/ghosttree.sprite +++ b/data/images/creatures/ghosttree/ghosttree.sprite @@ -1,31 +1,59 @@ (supertux-sprite (action - (name "default") - (hitbox 230 300 40 60) + (name "idle") + (hitbox 526 520 48 48) + (fps 10) (images - "ghosttree.png" + "idle-0.png" ) ) (action - (name "swallow") - (hitbox 230 300 40 60) - (fps 16) + (name "charge") + (hitbox 526 520 48 48) + (fps 10) (images - "ghosttree-swallow-0.png" - "ghosttree-swallow-1.png" - "ghosttree-swallow-2.png" - "ghosttree-swallow-3.png" + "charge-0.png" ) ) (action - (name "dying") - (hitbox 230 300 40 60) - (fps 4) + (name "scream") + (hitbox 526 520 48 48) + (fps 10) (images - "ghosttree-dying-0.png" - "ghosttree-dying-0.png" - "ghosttree-dying-0.png" - "ghosttree-dying-0.png" + "scream-0.png" + ) + ) + + (action + (name "idle-pinch") + (hitbox 296 520 48 48) + (fps 10) + (images + "pinch_idle-0.png" + ) + ) + (action + (name "charge-pinch") + (hitbox 296 520 48 48) + (fps 10) + (images + "pinch_charge-0.png" + ) + ) + (action + (name "scream-pinch") + (hitbox 296 520 48 48) + (fps 10) + (images + "pinch_scream-0.png" + ) + ) + (action + (name "busted") + (hitbox 296 520 48 48) + (fps 10) + (images + "bust-0.png" ) ) ) diff --git a/data/images/creatures/ghosttree/granito_root-0.png b/data/images/creatures/ghosttree/granito_root-0.png new file mode 100644 index 00000000000..79b5e496052 Binary files /dev/null and b/data/images/creatures/ghosttree/granito_root-0.png differ diff --git a/data/images/creatures/ghosttree/granito_root-1.png b/data/images/creatures/ghosttree/granito_root-1.png new file mode 100644 index 00000000000..c6856333802 Binary files /dev/null and b/data/images/creatures/ghosttree/granito_root-1.png differ diff --git a/data/images/creatures/ghosttree/granito_root.sprite b/data/images/creatures/ghosttree/granito_root.sprite new file mode 100644 index 00000000000..32a910bbccb --- /dev/null +++ b/data/images/creatures/ghosttree/granito_root.sprite @@ -0,0 +1,12 @@ +(supertux-sprite + (action + (name "variant1") + (hitbox 37 37 16 188) + (images "granito_root-0.png") + ) + (action + (name "variant2") + (hitbox 37 37 16 188) + (images "granito_root-1.png") + ) +) diff --git a/data/images/creatures/ghosttree/green_root.png b/data/images/creatures/ghosttree/green_root.png new file mode 100644 index 00000000000..fb6b46e0357 Binary files /dev/null and b/data/images/creatures/ghosttree/green_root.png differ diff --git a/data/images/creatures/ghosttree/root.sprite b/data/images/creatures/ghosttree/green_root.sprite similarity index 50% rename from data/images/creatures/ghosttree/root.sprite rename to data/images/creatures/ghosttree/green_root.sprite index 7e54ff1feb7..ce189c26328 100644 --- a/data/images/creatures/ghosttree/root.sprite +++ b/data/images/creatures/ghosttree/green_root.sprite @@ -1,6 +1,7 @@ (supertux-sprite (action (name "default") - (images "root.png") + (hitbox 32 40 32 88) + (images "green_root.png") ) ) diff --git a/data/images/creatures/ghosttree/hudlife.png b/data/images/creatures/ghosttree/hudlife.png index 80d24a63c60..424ac6cdca3 100644 Binary files a/data/images/creatures/ghosttree/hudlife.png and b/data/images/creatures/ghosttree/hudlife.png differ diff --git a/data/images/creatures/ghosttree/idle-0.png b/data/images/creatures/ghosttree/idle-0.png new file mode 100644 index 00000000000..1bef45988d0 Binary files /dev/null and b/data/images/creatures/ghosttree/idle-0.png differ diff --git a/data/images/creatures/ghosttree/main_root.png b/data/images/creatures/ghosttree/main_root.png new file mode 100644 index 00000000000..214aefc11f6 Binary files /dev/null and b/data/images/creatures/ghosttree/main_root.png differ diff --git a/data/images/creatures/ghosttree/main_root.sprite b/data/images/creatures/ghosttree/main_root.sprite new file mode 100644 index 00000000000..bdcecc875d8 --- /dev/null +++ b/data/images/creatures/ghosttree/main_root.sprite @@ -0,0 +1,7 @@ +(supertux-sprite + (action + (name "default") + (hitbox 37 37 16 188) + (images "main_root.png") + ) +) diff --git a/data/images/creatures/ghosttree/pinch_charge-0.png b/data/images/creatures/ghosttree/pinch_charge-0.png new file mode 100644 index 00000000000..8ae5a10c89f Binary files /dev/null and b/data/images/creatures/ghosttree/pinch_charge-0.png differ diff --git a/data/images/creatures/ghosttree/pinch_idle-0.png b/data/images/creatures/ghosttree/pinch_idle-0.png new file mode 100644 index 00000000000..284f9fc3d36 Binary files /dev/null and b/data/images/creatures/ghosttree/pinch_idle-0.png differ diff --git a/data/images/creatures/ghosttree/pinch_root.png b/data/images/creatures/ghosttree/pinch_root.png new file mode 100644 index 00000000000..0169e4ab1af Binary files /dev/null and b/data/images/creatures/ghosttree/pinch_root.png differ diff --git a/data/images/creatures/ghosttree/pinch_root.sprite b/data/images/creatures/ghosttree/pinch_root.sprite new file mode 100644 index 00000000000..3114159405c --- /dev/null +++ b/data/images/creatures/ghosttree/pinch_root.sprite @@ -0,0 +1,7 @@ +(supertux-sprite + (action + (name "default") + (hitbox 37 37 16 188) + (images "pinch_root.png") + ) +) diff --git a/data/images/creatures/ghosttree/pinch_scream-0.png b/data/images/creatures/ghosttree/pinch_scream-0.png new file mode 100644 index 00000000000..307dd31172b Binary files /dev/null and b/data/images/creatures/ghosttree/pinch_scream-0.png differ diff --git a/data/images/creatures/ghosttree/red_root-0.png b/data/images/creatures/ghosttree/red_root-0.png new file mode 100644 index 00000000000..e50e12a77cd Binary files /dev/null and b/data/images/creatures/ghosttree/red_root-0.png differ diff --git a/data/images/creatures/ghosttree/red_root-1.png b/data/images/creatures/ghosttree/red_root-1.png new file mode 100644 index 00000000000..1a28134b3d9 Binary files /dev/null and b/data/images/creatures/ghosttree/red_root-1.png differ diff --git a/data/images/creatures/ghosttree/red_root-2.png b/data/images/creatures/ghosttree/red_root-2.png new file mode 100644 index 00000000000..6b065f79bd2 Binary files /dev/null and b/data/images/creatures/ghosttree/red_root-2.png differ diff --git a/data/images/creatures/ghosttree/red_root.sprite b/data/images/creatures/ghosttree/red_root.sprite new file mode 100644 index 00000000000..48ec0a97976 --- /dev/null +++ b/data/images/creatures/ghosttree/red_root.sprite @@ -0,0 +1,17 @@ +(supertux-sprite + (action + (name "variant1") + (hitbox 12 32 8 70) + (images "red_root-0.png") + ) + (action + (name "variant2") + (hitbox 12 32 8 70) + (images "red_root-1.png") + ) + (action + (name "variant3") + (hitbox 12 32 8 70) + (images "red_root-2.png") + ) +) diff --git a/data/images/creatures/ghosttree/root-base-0.png b/data/images/creatures/ghosttree/root-base-0.png deleted file mode 100644 index a674613cd55..00000000000 Binary files a/data/images/creatures/ghosttree/root-base-0.png and /dev/null differ diff --git a/data/images/creatures/ghosttree/root-base-1.png b/data/images/creatures/ghosttree/root-base-1.png deleted file mode 100644 index 2195d12e979..00000000000 Binary files a/data/images/creatures/ghosttree/root-base-1.png and /dev/null differ diff --git a/data/images/creatures/ghosttree/root-base-2.png b/data/images/creatures/ghosttree/root-base-2.png deleted file mode 100644 index e2c540ff824..00000000000 Binary files a/data/images/creatures/ghosttree/root-base-2.png and /dev/null differ diff --git a/data/images/creatures/ghosttree/root-base-3.png b/data/images/creatures/ghosttree/root-base-3.png deleted file mode 100644 index 87f5fad4bbf..00000000000 Binary files a/data/images/creatures/ghosttree/root-base-3.png and /dev/null differ diff --git a/data/images/creatures/ghosttree/root-base.sprite b/data/images/creatures/ghosttree/root-base.sprite deleted file mode 100644 index edfa363a273..00000000000 --- a/data/images/creatures/ghosttree/root-base.sprite +++ /dev/null @@ -1,22 +0,0 @@ -(supertux-sprite - (action - (name "appearing") - (fps 16) - (images - "root-base-0.png" - "root-base-1.png" - "root-base-2.png" - "root-base-3.png" - ) - ) - (action - (name "vanishing") - (fps 16) - (images - "root-base-3.png" - "root-base-2.png" - "root-base-1.png" - "root-base-0.png" - ) - ) -) diff --git a/data/images/creatures/ghosttree/root.png b/data/images/creatures/ghosttree/root.png deleted file mode 100644 index da5aee936ab..00000000000 Binary files a/data/images/creatures/ghosttree/root.png and /dev/null differ diff --git a/data/images/creatures/ghosttree/scream-0.png b/data/images/creatures/ghosttree/scream-0.png new file mode 100644 index 00000000000..744c1e3453e Binary files /dev/null and b/data/images/creatures/ghosttree/scream-0.png differ diff --git a/data/images/engine/editor/objects.stoi b/data/images/engine/editor/objects.stoi index 99088e407b0..2ddd70bde00 100644 --- a/data/images/engine/editor/objects.stoi +++ b/data/images/engine/editor/objects.stoi @@ -166,7 +166,7 @@ (icon "images/creatures/yeti/hudlife.png")) (object (class "ghosttree") - (icon "images/creatures/ghosttree/ghosttree.png")) + (icon "images/creatures/ghosttree/idle-0.png")) ) (objectgroup diff --git a/data/sounds/tree_howling.ogg b/data/sounds/tree_howling.ogg index 5654a432492..ef55919bb49 100644 Binary files a/data/sounds/tree_howling.ogg and b/data/sounds/tree_howling.ogg differ diff --git a/data/sounds/tree_pinch.ogg b/data/sounds/tree_pinch.ogg new file mode 100644 index 00000000000..fef3307d011 Binary files /dev/null and b/data/sounds/tree_pinch.ogg differ diff --git a/data/sounds/tree_suck.ogg b/data/sounds/tree_suck.ogg index efffca445ee..06dcdd090f1 100644 Binary files a/data/sounds/tree_suck.ogg and b/data/sounds/tree_suck.ogg differ diff --git a/src/badguy/dart.cpp b/src/badguy/dart.cpp index 66d66a0e353..78383ba085d 100644 --- a/src/badguy/dart.cpp +++ b/src/badguy/dart.cpp @@ -57,6 +57,11 @@ Dart::Dart(const Vector& pos, Direction d, const BadGuy* parent_, const std::str set_action("flying", m_dir); } +Dart::~Dart () +{ + +} + bool Dart::updatePointers(const GameObject* from_object, GameObject* to_object) { diff --git a/src/badguy/dart.hpp b/src/badguy/dart.hpp index 262d22fcf2a..7d4e8b43391 100644 --- a/src/badguy/dart.hpp +++ b/src/badguy/dart.hpp @@ -26,6 +26,7 @@ class Dart final : public BadGuy public: Dart(const ReaderMapping& reader); Dart(const Vector& pos, Direction d, const BadGuy* parent, const std::string& sprite = "images/creatures/darttrap/granito/root_dart.sprite"); + ~Dart(); // An empty destructor is required to deallocate the sound_source properly. virtual void initialize() override; virtual void activate() override; diff --git a/src/badguy/ghosttree.cpp b/src/badguy/ghosttree.cpp index 12e60154646..b21e1223c2a 100644 --- a/src/badguy/ghosttree.cpp +++ b/src/badguy/ghosttree.cpp @@ -20,6 +20,7 @@ #include #include "audio/sound_manager.hpp" +#include "badguy/ghosttree_attack.hpp" #include "badguy/root.hpp" #include "badguy/treewillowisp.hpp" #include "math/random.hpp" @@ -39,21 +40,16 @@ static const float SUCK_TARGET_SPREAD = 8; GhostTree::GhostTree(const ReaderMapping& mapping) : Boss(mapping, "images/creatures/ghosttree/ghosttree.sprite", LAYER_OBJECTS - 10), - mystate(STATE_IDLE), - willowisp_timer(), - willo_spawn_y(0), - willo_radius(200), - willo_speed(1.8f), - willo_color(0), - glow_sprite(SpriteManager::current()->create("images/creatures/ghosttree/ghosttree-glow.sprite")), - colorchange_timer(), - suck_timer(), - root_timer(), - treecolor(0), - suck_lantern_color(), - m_taking_life(), - suck_lantern(nullptr), - willowisps() + m_state(STATE_INIT), + m_attack(ATTACK_RED), + m_state_timer(), + m_willo_spawn_y(0), + m_willo_radius(200), + m_willo_speed(1.8f), + m_willo_to_spawn(9), + m_next_willo(ATTACK_RED), + m_willowisps(), + m_root_attack() { mapping.get("hud-icon", m_hud_icon, "images/creatures/ghosttree/hudlife.png"); m_hud_head = Surface::from_file(m_hud_icon); @@ -61,223 +57,291 @@ GhostTree::GhostTree(const ReaderMapping& mapping) : set_colgroup_active(COLGROUP_TOUCHABLE); SoundManager::current()->preload("sounds/tree_howling.ogg"); SoundManager::current()->preload("sounds/tree_suck.ogg"); + SoundManager::current()->preload("sounds/tree_pinch.ogg"); + SoundManager::current()->preload("sounds/gulp.wav"); + SoundManager::current()->preload("sounds/explosion.wav"); // for green and pinch root + SoundManager::current()->preload("sounds/dartfire.wav"); // for blue and pinch root + + set_state(STATE_INIT); } -void -GhostTree::die() +GhostTree::~GhostTree() { - for (const auto& willo : willowisps) { - willo->vanish(); - } - - if (m_lives <= 0) { - mystate = STATE_DYING; - set_action("dying", 1); - glow_sprite->set_action("dying", 1); - run_dead_script(); - } } void GhostTree::activate() { - willowisp_timer.start(1.0f, true); - colorchange_timer.start(13, true); - root_timer.start(5, true); +} + +Vector +GhostTree::get_player_pos() +{ + auto player = get_nearest_player(); + if (player) { + return player->get_pos(); + } else { + return m_col.m_bbox.get_middle(); + } } void GhostTree::active_update(float dt_sec) { Boss::boss_update(dt_sec); - - if (mystate == STATE_IDLE) { - m_taking_life = false; - if (colorchange_timer.check()) { - SoundManager::current()->play("sounds/tree_howling.ogg", get_pos()); - suck_timer.start(3); - treecolor = (treecolor + 1) % 3; - - Color col; - switch (treecolor) { - case 0: col = Color(1, 0, 0); break; - case 1: col = Color(0, 1, 0); break; - case 2: col = Color(0, 0, 1); break; - case 3: col = Color(1, 1, 0); break; - case 4: col = Color(1, 0, 1); break; - case 5: col = Color(0, 1, 1); break; - default: assert(false); - } - glow_sprite->set_color(col); - } - - if (suck_timer.check()) { - Color col = glow_sprite->get_color(); - SoundManager::current()->play("sounds/tree_suck.ogg", get_pos()); - for (const auto& willo : willowisps) { - if (willo->get_color() == col) { - willo->start_sucking( - m_col.m_bbox.get_middle() + SUCK_TARGET_OFFSET - + Vector(gameRandom.randf(-SUCK_TARGET_SPREAD, SUCK_TARGET_SPREAD), - gameRandom.randf(-SUCK_TARGET_SPREAD, SUCK_TARGET_SPREAD))); + switch (m_state) { + case STATE_INIT: + if (m_state_timer.check()) { + spawn_willowisp(m_next_willo); + rotate_willo_color(); + m_state_timer.start(0.1); + --m_willo_to_spawn; + if (m_willo_to_spawn <= 0) { + set_state(STATE_SCREAM); } } - mystate = STATE_SUCKING; - } - - if (willowisp_timer.check()) { - if (willowisps.size() < WILLOWISP_COUNT) { - Vector pos(m_col.m_bbox.get_width() / 2, - m_col.m_bbox.get_height() / 2 + (m_flip == NO_FLIP ? (willo_spawn_y + WILLOWISP_TOP_OFFSET) : - -(willo_spawn_y + WILLOWISP_TOP_OFFSET + 32.0f))); - auto& willowisp = Sector::get().add(this, pos, 200 + willo_radius, willo_speed); - willowisps.push_back(&willowisp); - - willo_spawn_y -= 40; - if (willo_spawn_y < -160) - willo_spawn_y = 0; - - willo_radius += 20; - if (willo_radius > 120) - willo_radius = 0; - - if (willo_speed == 1.8f) { - willo_speed = 1.5f; - } else { - willo_speed = 1.8f; - } - - do { - willo_color = (willo_color + 1) % 3; - } while(willo_color == treecolor); - - switch (willo_color) { - case 0: willowisp.set_color(Color(1, 0, 0)); break; - case 1: willowisp.set_color(Color(0, 1, 0)); break; - case 2: willowisp.set_color(Color(0, 0, 1)); break; - case 3: willowisp.set_color(Color(1, 1, 0)); break; - case 4: willowisp.set_color(Color(1, 0, 1)); break; - case 5: willowisp.set_color(Color(0, 1, 1)); break; - default: assert(false); - } + break; + case STATE_SCREAM: + if (m_state_timer.check()) { + set_state(STATE_IDLE); } - } - - // TODO: Add support for the new root implementation - /* - if (root_timer.check()) { - //TODO: indicate root with an animation. - auto player = get_nearest_player(); - if (player) - Sector::get().add(Vector(player->get_bbox().get_left(), (m_flip == NO_FLIP ? (m_col.m_bbox.get_bottom() + ROOT_TOP_OFFSET) : (m_col.m_bbox.get_top() - ROOT_TOP_OFFSET - ROOT_HEIGHT))), m_flip); - } - */ - } else if (mystate == STATE_SWALLOWING) { - if (suck_lantern) { - // Suck in the lantern. - assert (suck_lantern); - Vector pos = suck_lantern->get_pos(); - Vector delta = m_col.m_bbox.get_middle() + SUCK_TARGET_OFFSET - pos; - if (glm::length(delta) < 1) { - suck_lantern->ungrab(*this, Direction::RIGHT); - suck_lantern->remove_me(); - suck_lantern = nullptr; - set_action("swallow", 1); - } else { - pos += glm::normalize(delta); - suck_lantern->grab(*this, pos, Direction::RIGHT); + break; + case STATE_IDLE: + m_root_attack->active_update(dt_sec); + if (m_root_attack->is_done()) { + set_state(STATE_SUCKING); } - } else { - // Wait until the lantern is swallowed completely. - if (m_sprite->animation_done()) { - if (is_color_deadly(suck_lantern_color)) { - if (!m_taking_life) { - m_lives--; - m_taking_life = true; - } - die(); + break; + case STATE_SUCKING: + break; + case STATE_ATTACKING: + m_root_attack->active_update(dt_sec); + if (m_root_attack->is_done()) { + set_state(STATE_RECHARGING); + } + break; + case STATE_RECHARGING: + if (m_state_timer.check()) { + if (m_attack == ATTACK_PINCH) { + spawn_willowisp(m_next_willo); + rotate_willo_color(); + m_state_timer.start(0.3); + } else { + spawn_willowisp(m_attack); + m_state_timer.start(0.9); } - if (m_lives > 0) { - set_action("default"); - mystate = STATE_IDLE; - spawn_lantern(); + --m_willo_to_spawn; + if (m_willo_to_spawn <= 0) { + set_state(STATE_IDLE); } } - } + break; + case STATE_DEAD: + break; + default: + break; } } bool -GhostTree::is_color_deadly(Color color) const -{ - if (color == Color(0,0,0)) return false; - Color my_color = glow_sprite->get_color(); - return ((my_color.red != color.red) || (my_color.green != color.green) || (my_color.blue != color.blue)); +GhostTree::suck_now(const Color& color) const { + switch (m_attack) { + case ATTACK_RED: + return color.red == 1.0; + case ATTACK_GREEN: + return color.green == 1.0; + case ATTACK_BLUE: + return color.blue == 1.0; + case ATTACK_PINCH: + return true; + default: + return false; + } } void -GhostTree::willowisp_died(TreeWillOWisp* willowisp) -{ - if ((mystate == STATE_SUCKING) && (willowisp->was_sucked)) { - mystate = STATE_IDLE; +GhostTree::rotate_willo_color() { + m_next_willo = static_cast((static_cast(m_next_willo) + 1) % 3); +} + +void +GhostTree::spawn_willowisp(AttackType color) { + Vector pos(m_col.m_bbox.get_width() / 2, + m_col.m_bbox.get_height() / 2 + (m_flip == NO_FLIP ? (m_willo_spawn_y + WILLOWISP_TOP_OFFSET) : + -(m_willo_spawn_y + WILLOWISP_TOP_OFFSET + 32.0f))); + auto& willowisp = Sector::get().add(this, pos, 200 + m_willo_radius, m_willo_speed); + m_willowisps.push_back(&willowisp); + + m_willo_spawn_y -= 40; + if (m_willo_spawn_y < -160) + m_willo_spawn_y = 0; + + m_willo_radius += 20; + if (m_willo_radius > 120) + m_willo_radius = 0; + + if (m_willo_speed == 1.8f) { + m_willo_speed = 1.5f; + } else { + m_willo_speed = 1.8f; + } + + switch (color) { + case ATTACK_RED: + willowisp.set_color(Color::RED); + break; + case ATTACK_GREEN: + willowisp.set_color(Color::GREEN); + break; + case ATTACK_BLUE: + willowisp.set_color(Color::BLUE); + break; + case ATTACK_PINCH: + break; + default: + break; } - willowisps.erase(std::find_if(willowisps.begin(), willowisps.end(), - [willowisp](const GameObject* lhs) - { - return lhs == willowisp; - })); } void -GhostTree::draw(DrawingContext& context) +GhostTree::set_state(MyState new_state) { + switch (new_state) { + case STATE_INIT: + set_action("idle"); + m_state_timer.start(0.1); + break; + case STATE_SCREAM: + set_action("scream"); + SoundManager::current()->play("sounds/tree_howling.ogg", get_pos()); + m_state_timer.start(2); + break; + case STATE_IDLE: + set_action(m_attack == ATTACK_PINCH ? "idle-pinch" : "idle"); + start_attack(true); + break; + case STATE_SUCKING: + SoundManager::current()->play("sounds/tree_suck.ogg", get_pos()); + for (const auto& willo : m_willowisps) { + if (suck_now(willo->get_color())) { + willo->start_sucking( + m_col.m_bbox.get_middle() + SUCK_TARGET_OFFSET + + Vector(gameRandom.randf(-SUCK_TARGET_SPREAD, SUCK_TARGET_SPREAD), + gameRandom.randf(-SUCK_TARGET_SPREAD, SUCK_TARGET_SPREAD))); + } + } + break; + case STATE_ATTACKING: + set_action(m_attack == ATTACK_PINCH ? "scream-pinch" : "scream"); + SoundManager::current()->play("sounds/tree_howling.ogg", get_pos()); + start_attack(false); + break; + case STATE_RECHARGING: + set_action(m_attack == ATTACK_PINCH ? "charge-pinch" : "charge"); + m_state_timer.start(1); + m_willo_to_spawn = m_attack == ATTACK_PINCH ? 9 : 3; + break; + case STATE_DEAD: + SoundManager::current()->play("sounds/tree_howling.ogg", get_pos()); + set_action("busted"); + run_dead_script(); + break; + default: + break; + } + m_state = new_state; +} + +void +GhostTree::start_attack(bool main_root) { - Boss::draw(context); + const float middle = m_col.m_bbox.get_middle().x; + const float base = m_col.m_bbox.get_bottom() + 96; + if (main_root) { + m_root_attack.reset(new GhostTreeAttackMain(Vector(middle, base))); + return; + } - context.push_transform(); - if (mystate == STATE_SUCKING) { - context.set_alpha(0.5f + fmodf(g_game_time, 0.5f)); - } else { - context.set_alpha(0.5f); + Vector player_pos = get_player_pos(); + switch (m_attack) { + case ATTACK_RED: + if (player_pos.x > middle) { + m_root_attack.reset(new GhostTreeAttackRed(base, middle - 512, middle + 512)); + } else { + m_root_attack.reset(new GhostTreeAttackRed(base, middle + 512, middle - 512)); + } + break; + case ATTACK_GREEN: + m_root_attack.reset(new GhostTreeAttackGreen(Vector(player_pos.x, base))); + break; + case ATTACK_BLUE: + m_root_attack.reset(new GhostTreeAttackBlue(Vector(player_pos.x, base))); + break; + case ATTACK_PINCH: + default: + m_root_attack.reset(new GhostTreeAttackPinch(Vector(player_pos.x, base), middle - 512, middle + 512)); + break; } - glow_sprite->draw(context.light(), get_pos(), m_layer); - context.pop_transform(); +} + +void +GhostTree::willowisp_died(TreeWillOWisp* willowisp) +{ + if ((m_state == STATE_SUCKING) && (willowisp->was_sucked)) { + set_state(STATE_ATTACKING); + } + m_willowisps.erase(std::find_if(m_willowisps.begin(), m_willowisps.end(), + [willowisp](const GameObject* lhs) + { + return lhs == willowisp; + })); } bool GhostTree::collides(MovingObject& other, const CollisionHit& ) const { - if (mystate != STATE_SUCKING) return false; - if (dynamic_cast(&other)) return true; + if (m_state != STATE_RECHARGING) return false; if (dynamic_cast(&other)) return true; return false; } HitResponse -GhostTree::collision(MovingObject& other, const CollisionHit& ) +GhostTree::collision(MovingObject& other, const CollisionHit& hit) { - if (mystate != STATE_SUCKING) return ABORT_MOVE; + if (m_state != STATE_RECHARGING) return ABORT_MOVE; + return BadGuy::collision(other, hit); +} - auto player = dynamic_cast(&other); +bool +GhostTree::collision_squished(MovingObject& object) +{ + auto player = dynamic_cast(&object); if (player) { - player->kill(false); + player->bounce(*this); } + + SoundManager::current()->play(m_attack == ATTACK_BLUE ? "sounds/tree_pinch.ogg" : "sounds/gulp.wav", get_pos()); - Lantern* lantern = dynamic_cast(&other); - if (lantern) { - suck_lantern = lantern; - suck_lantern->grab(*this, suck_lantern->get_pos(), Direction::RIGHT); - suck_lantern_color = lantern->get_color(); - mystate = STATE_SWALLOWING; + --m_lives; + if (m_lives <= 0) { + set_state(STATE_DEAD); + return true; } - - return ABORT_MOVE; -} - -void -GhostTree::spawn_lantern() -{ - Sector::get().add(m_col.m_bbox.get_middle() + SUCK_TARGET_OFFSET); + + if (m_attack == ATTACK_PINCH) { + while (m_willo_to_spawn--) { + spawn_willowisp(m_next_willo); + rotate_willo_color(); + } + } else { + while (m_willo_to_spawn--) { + spawn_willowisp(m_attack); + } + m_attack = static_cast(static_cast(m_attack) + 1); + } + + set_state(STATE_IDLE); + return true; } std::vector diff --git a/src/badguy/ghosttree.hpp b/src/badguy/ghosttree.hpp index 6816c57376b..6d43937f1d5 100644 --- a/src/badguy/ghosttree.hpp +++ b/src/badguy/ghosttree.hpp @@ -18,19 +18,19 @@ #include "badguy/boss.hpp" +class GhostTreeAttack; class TreeWillOWisp; -class Lantern; class GhostTree final : public Boss { public: GhostTree(const ReaderMapping& mapping); + ~GhostTree(); virtual void kill_fall() override { } virtual void activate() override; virtual void active_update(float dt_sec) override; - virtual void draw(DrawingContext& context) override; virtual bool collides(MovingObject& other, const CollisionHit& hit) const override; virtual HitResponse collision(MovingObject& other, const CollisionHit& hit) override; @@ -44,40 +44,49 @@ class GhostTree final : public Boss virtual void on_flip(float height) override; void willowisp_died(TreeWillOWisp* willowisp); - void die(); protected: virtual std::vector get_allowed_directions() const override; + virtual bool collision_squished(MovingObject& object) override; private: enum MyState { - STATE_IDLE, STATE_SUCKING, STATE_SWALLOWING, STATE_DYING + STATE_INIT, + STATE_SCREAM, + STATE_IDLE, + STATE_SUCKING, + STATE_ATTACKING, + STATE_RECHARGING, + STATE_DEAD + }; + + enum AttackType { + ATTACK_RED = 0, + ATTACK_GREEN, + ATTACK_BLUE, + ATTACK_PINCH }; private: - bool is_color_deadly(Color color) const; - void spawn_lantern(); + void set_state(MyState new_state); + bool suck_now(const Color& color) const; private: - MyState mystate; - Timer willowisp_timer; - float willo_spawn_y; - float willo_radius; - float willo_speed; - int willo_color; - - SpritePtr glow_sprite; - Timer colorchange_timer; - Timer suck_timer; - Timer root_timer; - int treecolor; - Color suck_lantern_color; - - bool m_taking_life; - - Lantern* suck_lantern; /**< Lantern that is currently being sucked in */ - - std::vector willowisps; + MyState m_state; + AttackType m_attack; + Timer m_state_timer; + float m_willo_spawn_y; + float m_willo_radius; + float m_willo_speed; + int m_willo_to_spawn; + AttackType m_next_willo; + + std::vector m_willowisps; + std::unique_ptr m_root_attack; + void spawn_willowisp(AttackType color); + void rotate_willo_color(); + void start_attack(bool main_root); + Vector get_player_pos(); private: GhostTree(const GhostTree&) = delete; diff --git a/src/badguy/ghosttree_attack.cpp b/src/badguy/ghosttree_attack.cpp new file mode 100644 index 00000000000..8f93049f67b --- /dev/null +++ b/src/badguy/ghosttree_attack.cpp @@ -0,0 +1,492 @@ +// SuperTux - Ghost Tree Attacks +// Copyright (C) 2025 Hypernoot +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "badguy/ghosttree_attack.hpp" + +#include "audio/sound_manager.hpp" +#include "badguy/dart.hpp" +#include "object/explosion.hpp" +#include "object/player.hpp" +#include "supertux/sector.hpp" + +static const float MAIN_ROOT_THREAT = 16; +static const float MAIN_ROOT_THREAT_SPEED = 32; +static const float MAIN_ROOT_SPEED = 128; +static const float MAIN_ROOT_DELAY = 2; + +static const float RED_ROOT_SPEED = 128; +static const float RED_ROOT_DELAY = 0.2; +static const float RED_ROOT_SPAN = 32; + +static const float GREEN_ROOT_SPEED = 64; + +static const float BLUE_ROOT_SPEED = 64; +static const float BLUE_ROOT_FIRE_DELAY = 1.0; +static const float BLUE_ROOT_FALL_DELAY = 1.0; +static const std::string BLUE_ROOT_PROJECTILE = "images/creatures/ghosttree/blue_root.sprite"; + +static const float PINCH_ROOT_SPEED = 64; +static const float PINCH_ROOT_FIRE_DELAY = 1.0; +static const float PINCH_ROOT_EXPLOSION_DELAY = 1.0; +static const float PINCH_ROOT_SPAN = 64; + +// PART 1: Abstract Classes +// ---------------------------------------------------------------------------- + +GhostTreeAttack::GhostTreeAttack() +{ +} + +GhostTreeAttack::~GhostTreeAttack() +{ +} + +GhostTreeRoot::GhostTreeRoot(const Vector& pos, Direction dir, const std::string& sprite) : + BadGuy(pos, dir, sprite, LAYER_OBJECTS) +{ + set_colgroup_active(COLGROUP_TOUCHABLE); + m_physic.enable_gravity(false); +} + +GhostTreeRoot::~GhostTreeRoot() +{ +} + +HitResponse +GhostTreeRoot::collision_badguy(BadGuy& other, const CollisionHit& hit) +{ + if (other.get_group() == COLGROUP_MOVING && other.is_snipable()) + { + other.kill_fall(); + return ABORT_MOVE; + } + + return BadGuy::collision_badguy(other, hit); +} + +void +GhostTreeRoot::kill_fall() +{ +} + +// PART 2: Roots +// ---------------------------------------------------------------------------- + +GhostTreeRootMain::GhostTreeRootMain(const Vector& pos, GhostTreeAttack* parent) : + GhostTreeRoot(pos + Vector(0, MAIN_ROOT_THREAT), Direction::AUTO, "images/creatures/ghosttree/main_root.sprite"), + m_level_bottom(pos.y + MAIN_ROOT_THREAT), + m_level_middle(pos.y), + m_level_top(pos.y), + m_state(STATE_THREATENING), + m_parent(parent) +{ + m_physic.set_velocity_y(-MAIN_ROOT_THREAT_SPEED); + m_level_top -= m_col.m_bbox.get_height(); +} + +GhostTreeRootMain::~GhostTreeRootMain() +{ +} + +void +GhostTreeRootMain::active_update(float dt_sec) +{ + BadGuy::active_update(dt_sec); + switch (m_state) { + case STATE_THREATENING: + if (get_pos().y <= m_level_middle) { + m_state = STATE_RISING; + m_physic.set_velocity_y(-MAIN_ROOT_SPEED); + } + break; + case STATE_RISING: + if (get_pos().y <= m_level_top) { + m_state = STATE_FALLING; + m_physic.set_velocity_y(MAIN_ROOT_SPEED); + } + break; + case STATE_FALLING: + if (get_pos().y >= m_level_bottom) { + m_parent->root_died(); + remove_me(); + } + break; + default: + break; + } +} + +GhostTreeRootRed::GhostTreeRootRed(const Vector& pos, GhostTreeAttack* parent) : + GhostTreeRoot(pos, Direction::AUTO, "images/creatures/ghosttree/red_root.sprite"), + m_level_bottom(pos.y), + m_level_top(pos.y), + m_state(STATE_RISING), + m_parent(parent) +{ + m_physic.set_velocity_y(-RED_ROOT_SPEED); + const int variant = abs(static_cast(pos.x)) % 3 + 1; + set_action("variant" + std::to_string(variant)); + m_level_top -= m_col.m_bbox.get_height(); +} + +GhostTreeRootRed::~GhostTreeRootRed() +{ +} + +void +GhostTreeRootRed::active_update(float dt_sec) +{ + BadGuy::active_update(dt_sec); + switch (m_state) { + case STATE_RISING: + if (get_pos().y <= m_level_top) { + m_state = STATE_FALLING; + m_physic.set_velocity_y(RED_ROOT_SPEED); + } + break; + case STATE_FALLING: + if (get_pos().y >= m_level_bottom) { + m_parent->root_died(); + remove_me(); + } + break; + default: + break; + } +} + +GhostTreeRootGreen::GhostTreeRootGreen(const Vector& pos, GhostTreeAttack* parent) : + GhostTreeRoot(pos, Direction::AUTO, "images/creatures/ghosttree/green_root.sprite"), + m_level_top(pos.y), + m_parent(parent) +{ + m_physic.set_velocity_y(-GREEN_ROOT_SPEED); + m_level_top -= m_col.m_bbox.get_height(); +} + +GhostTreeRootGreen::~GhostTreeRootGreen() +{ +} + +void +GhostTreeRootGreen::active_update(float dt_sec) +{ + BadGuy::active_update(dt_sec); + if (get_pos().y > m_level_top) { + return; + } + Sector::get().add(m_col.m_bbox.get_middle(), EXPLOSION_STRENGTH_DEFAULT); + m_parent->root_died(); + remove_me(); +} + +GhostTreeRootBlue::GhostTreeRootBlue(const Vector& pos, GhostTreeAttack* parent) : + GhostTreeRoot(pos, Direction::AUTO, "images/creatures/ghosttree/granito_root.sprite"), + m_level_bottom(pos.y), + m_level_top(pos.y), + m_state(STATE_RISING), + m_state_timer(), + m_variant(abs(static_cast(pos.x)) % 2 + 1), + m_parent(parent) +{ + m_physic.set_velocity_y(-BLUE_ROOT_SPEED); + set_action("variant" + std::to_string(m_variant)); + m_level_top -= m_col.m_bbox.get_height(); +} + +GhostTreeRootBlue::~GhostTreeRootBlue() +{ +} + +void +GhostTreeRootBlue::active_update(float dt_sec) +{ + BadGuy::active_update(dt_sec); + switch (m_state) { + case STATE_RISING: + if (get_pos().y <= m_level_top) { + m_state = STATE_FIRE_DELAY; + m_physic.set_velocity_y(0.0); + m_state_timer.start(BLUE_ROOT_FIRE_DELAY); + } + break; + case STATE_FIRE_DELAY: + if (m_state_timer.check()) { + fire(); + m_state = STATE_FALL_DELAY; + m_state_timer.start(BLUE_ROOT_FALL_DELAY); + } + break; + case STATE_FALL_DELAY: + if (m_state_timer.check()) { + m_state = STATE_FALLING; + m_physic.set_velocity_y(BLUE_ROOT_SPEED); + } + break; + case STATE_FALLING: + if (get_pos().y >= m_level_bottom) { + m_parent->root_died(); + remove_me(); + } + break; + default: + break; + } +} + +void +GhostTreeRootBlue::fire() +{ + SoundManager::current()->play("sounds/dartfire.wav", get_pos()); + if (m_variant == 1) { + Sector::get().add(get_pos() + Vector(-70.0f, 124.0f), Direction::LEFT, this, BLUE_ROOT_PROJECTILE); + Sector::get().add(get_pos() + Vector( 16.0f, 57.0f), Direction::RIGHT, this, BLUE_ROOT_PROJECTILE); + } else { + Sector::get().add(get_pos() + Vector(-70.0f, 82.0f), Direction::LEFT, this, BLUE_ROOT_PROJECTILE); + Sector::get().add(get_pos() + Vector( 16.0f, 124.0f), Direction::RIGHT, this, BLUE_ROOT_PROJECTILE); + } +} + +GhostTreeRootPinch::GhostTreeRootPinch(const Vector& pos, GhostTreeAttack* parent) : + GhostTreeRoot(pos, Direction::AUTO, "images/creatures/ghosttree/pinch_root.sprite"), + m_level_top(pos.y), + m_state(STATE_RISING), + m_state_timer(), + m_parent(parent) +{ + m_physic.set_velocity_y(-PINCH_ROOT_SPEED); + m_level_top -= m_col.m_bbox.get_height(); +} + +GhostTreeRootPinch::~GhostTreeRootPinch() +{ +} + +void +GhostTreeRootPinch::active_update(float dt_sec) +{ + BadGuy::active_update(dt_sec); + switch (m_state) { + case STATE_RISING: + if (get_pos().y <= m_level_top) { + m_state = STATE_FIRE_DELAY; + m_physic.set_velocity_y(0.0); + m_state_timer.start(PINCH_ROOT_FIRE_DELAY); + } + break; + case STATE_FIRE_DELAY: + if (m_state_timer.check()) { + fire(); + m_state = STATE_EXPLOSION_DELAY; + m_state_timer.start(PINCH_ROOT_EXPLOSION_DELAY); + } + break; + case STATE_EXPLOSION_DELAY: + if (m_state_timer.check()) { + Sector::get().add(m_col.m_bbox.get_middle(), EXPLOSION_STRENGTH_DEFAULT); + m_parent->root_died(); + remove_me(); + } + break; + default: + break; + } +} + +void +GhostTreeRootPinch::fire() +{ + SoundManager::current()->play("sounds/dartfire.wav", get_pos()); + Sector::get().add(get_pos() + Vector(-70.0f, 126.0f), Direction::LEFT, this, BLUE_ROOT_PROJECTILE); + Sector::get().add(get_pos() + Vector( 16.0f, 127.0f), Direction::RIGHT, this, BLUE_ROOT_PROJECTILE); +} + +// PART 3: Root Attacks +// ---------------------------------------------------------------------------- + +GhostTreeAttackMain::GhostTreeAttackMain(Vector pos) : +m_spawn_timer(), +m_pos(pos), +m_remaining_roots(2) +{ + m_spawn_timer.start(0.2); +} + +GhostTreeAttackMain::~GhostTreeAttackMain() +{ +} + +void +GhostTreeAttackMain::active_update(float dtime) +{ + if (m_remaining_roots <= 0 || !m_spawn_timer.check()) { + return; + } + + Player* p = Sector::get().get_nearest_player(m_pos); + Sector::get().add(Vector(p ? p->get_pos().x : m_pos.x, m_pos.y), this); +} + +bool +GhostTreeAttackMain::is_done() const +{ + return m_remaining_roots <= 0; +} + +void +GhostTreeAttackMain::root_died() +{ + --m_remaining_roots; + if (m_remaining_roots >= 0) { + m_spawn_timer.start(MAIN_ROOT_DELAY); + } +} + +GhostTreeAttackRed::GhostTreeAttackRed(float y, float x_start, float x_end) : +m_spawn_timer(), +m_pos_y(y), +m_start_x(x_start), +m_end_x(x_end), +m_current_x(x_start), +m_remaining_roots(0), +m_ended(false) +{ + m_spawn_timer.start(0.2); +} + +GhostTreeAttackRed::~GhostTreeAttackRed() +{ +} + +void +GhostTreeAttackRed::active_update(float dtime) +{ + if (m_ended || !m_spawn_timer.check()) { + return; + } + + auto& root = Sector::get().add(Vector(m_current_x, m_pos_y), this); + m_spawn_timer.start(RED_ROOT_DELAY); + ++m_remaining_roots; + + if (m_start_x < m_end_x) { + m_current_x += RED_ROOT_SPAN; + m_ended = m_current_x >= m_end_x; + } else { + m_current_x -= RED_ROOT_SPAN; + m_ended = m_current_x < m_end_x; + } +} + +bool +GhostTreeAttackRed::is_done() const +{ + return m_ended && m_remaining_roots <= 0; +} + +void +GhostTreeAttackRed::root_died() +{ + --m_remaining_roots; +} + +GhostTreeAttackGreen::GhostTreeAttackGreen(const Vector& pos) : +m_ended(false) +{ + Sector::get().add(pos, this); +} + +GhostTreeAttackGreen::~GhostTreeAttackGreen() +{ +} + +void +GhostTreeAttackGreen::active_update(float dtime) +{ + // The root updates on its own, it just notifies us when it's done. +} + +bool +GhostTreeAttackGreen::is_done() const +{ + return m_ended; +} + +void +GhostTreeAttackGreen::root_died() +{ + m_ended = true; +} + +GhostTreeAttackBlue::GhostTreeAttackBlue(const Vector& pos) : +m_ended(false) +{ + Sector::get().add(pos, this); +} + +GhostTreeAttackBlue::~GhostTreeAttackBlue() +{ +} + +void +GhostTreeAttackBlue::active_update(float dtime) +{ + // The root updates on its own, it just notifies us when it's done. +} + +bool +GhostTreeAttackBlue::is_done() const +{ + return m_ended; +} + +void +GhostTreeAttackBlue::root_died() +{ + m_ended = true; +} + +GhostTreeAttackPinch::GhostTreeAttackPinch(const Vector& pos, float x_left, float x_right) : +m_root_ended(false), +m_left_trail(pos.y, pos.x - RED_ROOT_SPAN, x_left), +m_right_trail(pos.y, pos.x + PINCH_ROOT_SPAN, x_right) +{ + Sector::get().add(pos, this); +} + +GhostTreeAttackPinch::~GhostTreeAttackPinch() +{ +} + +void +GhostTreeAttackPinch::active_update(float dtime) +{ + m_left_trail.active_update(dtime); + m_right_trail.active_update(dtime); +} + +bool +GhostTreeAttackPinch::is_done() const +{ + return m_root_ended && m_left_trail.is_done() && m_right_trail.is_done(); +} + +void +GhostTreeAttackPinch::root_died() +{ + m_root_ended = true; +} + diff --git a/src/badguy/ghosttree_attack.hpp b/src/badguy/ghosttree_attack.hpp new file mode 100644 index 00000000000..c54c6c6fe04 --- /dev/null +++ b/src/badguy/ghosttree_attack.hpp @@ -0,0 +1,243 @@ +// SuperTux - Ghost Tree Attacks +// Copyright (C) 2025 Hypernoot +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "badguy/badguy.hpp" + +#include "supertux/timer.hpp" + +// This implementation is very Java but it is the only way how not to get +// mad of it. I could split it into more files but that would be unnecessary. + +// PART 1: Abstract Classes +// ---------------------------------------------------------------------------- + +class GhostTreeAttack +{ +public: + GhostTreeAttack(); + ~GhostTreeAttack(); + + virtual void active_update(float dtime) = 0; + virtual bool is_done() const = 0; + virtual void root_died() { }; +}; + +class GhostTreeRoot : public BadGuy +{ +public: + GhostTreeRoot(const Vector& pos, Direction dir, const std::string& sprite); + ~GhostTreeRoot(); + + virtual HitResponse collision_badguy(BadGuy& other, const CollisionHit& hit) override; + virtual void kill_fall() override; + + virtual bool is_flammable() const override { return false; } + virtual bool is_freezable() const override { return false; } + virtual bool is_snipable() const override { return false; } +}; + +// PART 2: Roots +// ---------------------------------------------------------------------------- + +class GhostTreeRootMain final : public GhostTreeRoot +{ +public: + GhostTreeRootMain(const Vector& pos, GhostTreeAttack* parent); + ~GhostTreeRootMain(); + + virtual void active_update(float dt_sec) override; + +private: + enum State { + STATE_THREATENING, + STATE_RISING, + STATE_FALLING, + }; + + float m_level_bottom; + float m_level_middle; + float m_level_top; + State m_state; + GhostTreeAttack* m_parent; +}; + +class GhostTreeRootRed final : public GhostTreeRoot +{ +public: + GhostTreeRootRed(const Vector& pos, GhostTreeAttack* parent); + ~GhostTreeRootRed(); + + virtual void active_update(float dt_sec) override; + +private: + enum State { + STATE_RISING, + STATE_FALLING + }; + + float m_level_bottom; + float m_level_top; + State m_state; + GhostTreeAttack* m_parent; +}; + +class GhostTreeRootGreen final : public GhostTreeRoot +{ +public: + GhostTreeRootGreen(const Vector& pos, GhostTreeAttack* parent); + ~GhostTreeRootGreen(); + + virtual void active_update(float dt_sec) override; + +private: + float m_level_top; + GhostTreeAttack* m_parent; +}; + +class GhostTreeRootBlue final : public GhostTreeRoot +{ +public: + GhostTreeRootBlue(const Vector& pos, GhostTreeAttack* parent); + ~GhostTreeRootBlue(); + + virtual void active_update(float dt_sec) override; + +private: + enum State { + STATE_RISING, + STATE_FIRE_DELAY, + STATE_FALL_DELAY, + STATE_FALLING + }; + + float m_level_bottom; + float m_level_top; + State m_state; + Timer m_state_timer; + int m_variant; + GhostTreeAttack* m_parent; + + void fire(); +}; + +class GhostTreeRootPinch final : public GhostTreeRoot +{ +public: + GhostTreeRootPinch(const Vector& pos, GhostTreeAttack* parent); + ~GhostTreeRootPinch(); + + virtual void active_update(float dt_sec) override; + +private: + enum State { + STATE_RISING, + STATE_FIRE_DELAY, + STATE_EXPLOSION_DELAY + }; + + float m_level_top; + State m_state; + Timer m_state_timer; + GhostTreeAttack* m_parent; + + void fire(); +}; + +// PART 3: Root Attacks +// ---------------------------------------------------------------------------- + +class GhostTreeAttackMain final : public GhostTreeAttack +{ +public: + GhostTreeAttackMain(Vector pos); + ~GhostTreeAttackMain(); + + virtual void active_update(float dtime) override; + virtual bool is_done() const override; + virtual void root_died() override; + +private: + Timer m_spawn_timer; + Vector m_pos; + int m_remaining_roots; +}; + +class GhostTreeAttackRed final : public GhostTreeAttack +{ +public: + GhostTreeAttackRed(float y, float x_start, float x_end); + ~GhostTreeAttackRed(); + + virtual void active_update(float dtime) override; + virtual bool is_done() const override; + virtual void root_died() override; + +private: + Timer m_spawn_timer; + float m_pos_y; + float m_start_x; + float m_end_x; + float m_current_x; + int m_remaining_roots; + bool m_ended; +}; + +class GhostTreeAttackGreen final : public GhostTreeAttack +{ +public: + GhostTreeAttackGreen(const Vector& pos); + ~GhostTreeAttackGreen(); + + virtual void active_update(float dtime) override; + virtual bool is_done() const override; + virtual void root_died() override; + +private: + bool m_ended; +}; + +class GhostTreeAttackBlue final : public GhostTreeAttack +{ +public: + GhostTreeAttackBlue(const Vector& pos); + ~GhostTreeAttackBlue(); + + virtual void active_update(float dtime) override; + virtual bool is_done() const override; + virtual void root_died() override; + +private: + bool m_ended; +}; + +class GhostTreeAttackPinch final : public GhostTreeAttack +{ +public: + GhostTreeAttackPinch(const Vector& pos, float x_left, float x_right); + ~GhostTreeAttackPinch(); + + virtual void active_update(float dtime) override; + virtual bool is_done() const override; + virtual void root_died() override; + +private: + bool m_root_ended; + GhostTreeAttackRed m_left_trail; + GhostTreeAttackRed m_right_trail; +}; + diff --git a/src/badguy/treewillowisp.cpp b/src/badguy/treewillowisp.cpp index 3e4a033e5fb..863781393ee 100644 --- a/src/badguy/treewillowisp.cpp +++ b/src/badguy/treewillowisp.cpp @@ -83,12 +83,12 @@ TreeWillOWisp::start_sucking(const Vector& suck_target_) was_sucked = true; } -HitResponse +/*HitResponse TreeWillOWisp::collision_player(Player& player, const CollisionHit& hit) { // TODO: This function is essentially a no-op. Remove if it doesn't change the behavior. return BadGuy::collision_player(player, hit); -} +}*/ bool TreeWillOWisp::collides(MovingObject& other, const CollisionHit& ) const @@ -96,8 +96,8 @@ TreeWillOWisp::collides(MovingObject& other, const CollisionHit& ) const auto lantern = dynamic_cast(&other); if (lantern && lantern->is_open()) return true; - if (dynamic_cast(&other)) - return true; + /*if (dynamic_cast(&other)) + return true;*/ return false; } diff --git a/src/badguy/treewillowisp.hpp b/src/badguy/treewillowisp.hpp index ded5775e8aa..e8867314fbf 100644 --- a/src/badguy/treewillowisp.hpp +++ b/src/badguy/treewillowisp.hpp @@ -50,7 +50,7 @@ class TreeWillOWisp final : public BadGuy protected: virtual bool collides(MovingObject& other, const CollisionHit& hit) const override; - virtual HitResponse collision_player(Player& player, const CollisionHit& hit) override; + //virtual HitResponse collision_player(Player& player, const CollisionHit& hit) override; private: enum MyState {