From 31f6ec5ffc3308ff328653f86551cac789ebb20d Mon Sep 17 00:00:00 2001 From: Sofia Piteira Date: Tue, 3 Jun 2025 13:32:33 +0100 Subject: [PATCH] Fixes #3199 - Coins carry support for Owl enemy Added the possibility for the owl to carry three new types of objects, coin, coin rain and coin explosion. Since the owl cannot visually carry a coin rain or a coin explosion, then visually the owl for these types carries a single coin, but then when the owl ungrabs the coin, the behavior is of a coin rain or a coin explosion respectively. The coin drop is activated when the tux approaches. Closes #3199 Co-authored-by: Filipe Oleacu --- src/object/coin.cpp | 137 ++++++++++++++++++++++++++- src/object/coin.hpp | 18 +++- src/object/coin_explode.cpp | 51 +++++++++- src/object/coin_explode.hpp | 16 +++- src/object/coin_rain.cpp | 45 +++++++++ src/object/coin_rain.hpp | 17 +++- src/supertux/game_object_factory.cpp | 6 +- 7 files changed, 281 insertions(+), 9 deletions(-) diff --git a/src/object/coin.cpp b/src/object/coin.cpp index 287c5dec55e..23248b44de3 100644 --- a/src/object/coin.cpp +++ b/src/object/coin.cpp @@ -22,12 +22,17 @@ #include "object/bouncy_coin.hpp" #include "object/player.hpp" #include "object/tilemap.hpp" +#include "supertux/constants.hpp" #include "supertux/flip_level_transformer.hpp" #include "supertux/level.hpp" #include "supertux/sector.hpp" #include "util/reader_mapping.hpp" #include "util/writer.hpp" +namespace { + const float GROUND_FRICTION = 0.1f; +} // namespace + Coin::Coin(const Vector& pos, bool count_stats, const std::string& sprite_path) : MovingSprite(pos, sprite_path, LAYER_OBJECTS - 1, COLGROUP_TOUCHABLE), PathObject(), @@ -37,7 +42,16 @@ Coin::Coin(const Vector& pos, bool count_stats, const std::string& sprite_path) m_physic(), m_collect_script(), m_starting_node(0), - m_count_stats(count_stats) + m_count_stats(count_stats), + m_on_ground(false), + m_on_ice(false), + m_at_ceiling(false), + m_last_movement(0.0f, 0.0f), + m_on_grab_script(), + m_on_ungrab_script(), + m_running_grab_script(false), + m_running_ungrab_script(false), + m_last_sector_gravity(10.0f) { SoundManager::current()->preload("sounds/coin.wav"); } @@ -51,10 +65,21 @@ Coin::Coin(const ReaderMapping& reader, bool count_stats) : m_physic(), m_collect_script(), m_starting_node(0), - m_count_stats(count_stats) + m_count_stats(count_stats), + m_on_ground(false), + m_on_ice(false), + m_at_ceiling(false), + m_last_movement(0.0f, 0.0f), + m_on_grab_script(), + m_on_ungrab_script(), + m_running_grab_script(false), + m_running_ungrab_script(false), + m_last_sector_gravity(10.0f) { reader.get("starting-node", m_starting_node, 0); reader.get("collect-script", m_collect_script, ""); + reader.get("on-grab-script", m_on_grab_script, ""); + reader.get("on-ungrab-script", m_on_ungrab_script, ""); parse_type(reader); init_path(reader, true); @@ -101,6 +126,40 @@ Coin::finish_construction() void Coin::update(float dt_sec) { + if (!is_grabbed()) + { + if (get_bbox().get_top() > Sector::get().get_height()) + { + remove_me(); + return; + } + + // Check for ice + Rectf icebox = get_bbox().grown(-1.f); + icebox.set_bottom(get_bbox().get_bottom() + 8.f); + m_on_ice = !Sector::get().is_free_of_tiles(icebox, true, Tile::ICE); + + // Water physics + bool in_water = !Sector::get().is_free_of_tiles(get_bbox(), true, Tile::WATER); + m_physic.set_gravity_modifier(in_water ? 0.2f : 1.f); + + m_col.set_movement(m_physic.get_movement(dt_sec) * + Vector(in_water ? 0.4f : 1.f, in_water ? 0.6f : 1.f)); + + // Handle gravity direction changes + const float sector_gravity = Sector::get().get_gravity(); + if (m_last_sector_gravity != sector_gravity) + { + if ((sector_gravity < 0.0f && m_last_sector_gravity >= 0.0f) || + (sector_gravity >= 0.0f && m_last_sector_gravity < 0.0f)) + { + m_on_ground = false; + m_at_ceiling = false; + } + m_last_sector_gravity = sector_gravity; + } + } + // If we have a path to follow, follow it. if (get_walker()) { Vector v(0.0f, 0.0f); @@ -281,7 +340,10 @@ HeavyCoin::collision_solid(const CollisionHit& hit) { float clink_threshold = 100.0f; // Sets the minimum speed needed to result in collision noise. // TODO: Colliding with HeavyCoins should have their own unique sound. - + if (is_grabbed()) + // Don't do anything while being carried + return; + if (hit.bottom) { if (m_physic.get_velocity_y() > clink_threshold && !m_last_hit.bottom) SoundManager::current()->play("sounds/coin2.ogg", get_pos()); @@ -303,6 +365,10 @@ HeavyCoin::collision_solid(const CollisionHit& hit) SoundManager::current()->play("sounds/coin2.ogg", get_pos()); m_physic.set_velocity_y(-m_physic.get_velocity_y()); } + if (m_on_ground || (hit.bottom && m_on_ice)) + { + m_physic.set_velocity_x(m_physic.get_velocity_x() * (1.f - (GROUND_FRICTION * (m_on_ice ? 0.5f : 1.f)))); + } // Only make a sound if the coin wasn't hittin anything last frame (A coin // stuck in solid matter would flood the sound manager - see #1555 on GitHub). @@ -392,4 +458,69 @@ HeavyCoin::on_flip(float height) MovingSprite::on_flip(height); } +void +Coin::grab(MovingObject& object, const Vector& pos, Direction dir) +{ + Portable::grab(object, pos, dir); + + // Update position and movement + Vector movement = pos - get_pos(); + m_col.set_movement(movement); + m_last_movement = movement; + + // Update collision group + set_group(COLGROUP_TOUCHABLE); + + // Reset ground/ceiling state + m_on_ground = false; + m_at_ceiling = false; + + // Handle grab script + m_running_ungrab_script = false; + if (!m_on_grab_script.empty() && !m_running_grab_script) + { + m_running_grab_script = true; + Sector::get().run_script(m_on_grab_script, "Coin::on_grab"); + } +} + +void +Coin::ungrab(MovingObject& object, Direction dir) +{ + auto player = dynamic_cast(&object); + + // Reset collision group and states + set_group(COLGROUP_MOVING_STATIC); + m_on_ground = false; + m_at_ceiling = false; + + if (player) + { + // Handle swimming physics + if (player->is_swimming() || player->is_water_jumping()) + { + float swimangle = player->get_swimming_angle(); + m_physic.set_velocity(player->get_velocity() + Vector(std::cos(swimangle), std::sin(swimangle))); + } + else + { + // Normal throwing physics + m_physic.set_velocity_x(fabsf(player->get_physic().get_velocity_x()) < 1.f ? 0.f : + player->m_dir == Direction::LEFT ? -200.f : 200.f); + m_physic.set_velocity_y((dir == Direction::UP) ? -500.f : (dir == Direction::DOWN) ? 500.f : + (glm::length(m_last_movement) > 1) ? -200.f : 0.f); + } + } + + // Handle ungrab script + m_running_grab_script = false; + if (!m_on_ungrab_script.empty() && !m_running_ungrab_script) + { + m_running_ungrab_script = true; + Sector::get().run_script(m_on_ungrab_script, "Coin::on_ungrab"); + } + + Portable::ungrab(object, dir); +} + /* EOF */ diff --git a/src/object/coin.hpp b/src/object/coin.hpp index 7756ca21678..0eddc02a881 100644 --- a/src/object/coin.hpp +++ b/src/object/coin.hpp @@ -20,13 +20,15 @@ #include "object/path_object.hpp" #include "object/moving_sprite.hpp" #include "supertux/physic.hpp" +#include "object/portable.hpp" class Path; class PathWalker; class TileMap; class Coin : public MovingSprite, - public PathObject + public PathObject, + public Portable { friend class HeavyCoin; @@ -45,6 +47,9 @@ class Coin : public MovingSprite, virtual std::string get_display_name() const override { return display_name(); } virtual GameObjectClasses get_class_types() const override { return MovingSprite::get_class_types().add(typeid(PathObject)).add(typeid(Coin)); } + virtual void grab(MovingObject& object, const Vector& pos, Direction dir) override; + virtual void ungrab(MovingObject& object, Direction dir) override; + virtual ObjectSettings get_settings() override; GameObjectTypes get_types() const override; std::string get_default_sprite_name() const override; @@ -63,6 +68,17 @@ class Coin : public MovingSprite, void collect(); +protected: + bool m_on_ground; + bool m_on_ice; + bool m_at_ceiling; + Vector m_last_movement; + std::string m_on_grab_script; + std::string m_on_ungrab_script; + bool m_running_grab_script; + bool m_running_ungrab_script; + float m_last_sector_gravity; + private: enum Type { NORMAL, diff --git a/src/object/coin_explode.cpp b/src/object/coin_explode.cpp index 515505d5397..93d3f2bceeb 100644 --- a/src/object/coin_explode.cpp +++ b/src/object/coin_explode.cpp @@ -18,19 +18,52 @@ #include "math/random.hpp" #include "object/coin.hpp" +#include "sprite/sprite.hpp" +#include "sprite/sprite_manager.hpp" #include "supertux/sector.hpp" #include CoinExplode::CoinExplode(const Vector& pos, bool count_stats, const std::string& sprite) : + GameObject("coin-explode"), m_sprite(sprite), position(pos), m_count_stats(count_stats) { } +CoinExplode::CoinExplode(const ReaderMapping& reader) : + GameObject(reader), + m_sprite("images/objects/coin/coin.sprite"), + position(), + m_count_stats(true) +{ + reader.get("x", position.x); + reader.get("y", position.y); + bool emerge = false; +} + +void +CoinExplode::update(float dt_sec) +{ + if (is_grabbed()) + // Don't do anything while being carried + return; +} + +void +CoinExplode::draw(DrawingContext& context) +{ + // Draw the coin sprite so it's visible + auto sprite = SpriteManager::current()->create(m_sprite); + if (sprite) + { + sprite->draw(context.color(), position, LAYER_OBJECTS); + } +} + void -CoinExplode::update(float ) +CoinExplode::explode() { float mag = 100.0f; // Magnitude at which coins are thrown. float rand = 30.0f; // Max variation to be subtracted from the magnitude. @@ -53,8 +86,22 @@ CoinExplode::update(float ) } void -CoinExplode::draw(DrawingContext &) +CoinExplode::grab(MovingObject& object, const Vector& pos, Direction dir) { + position = pos; + Portable::grab(object, pos, dir); } +void +CoinExplode::ungrab(MovingObject& object, Direction dir) +{ + position = object.get_pos(); + + // Explode immediately when ungrabbed + explode(); + + Portable::ungrab(object, dir); +} + + /* EOF */ diff --git a/src/object/coin_explode.hpp b/src/object/coin_explode.hpp index e7a316d4e77..bad56fd26b6 100644 --- a/src/object/coin_explode.hpp +++ b/src/object/coin_explode.hpp @@ -19,23 +19,37 @@ #include "math/vector.hpp" #include "supertux/game_object.hpp" +#include "object/portable.hpp" +#include "util/reader_mapping.hpp" -class CoinExplode final : public GameObject +class CoinExplode final : public GameObject, + public Portable { public: CoinExplode(const Vector& pos, bool count_stats = true, const std::string& sprite_path = "images/objects/coin/coin.sprite"); + CoinExplode(const ReaderMapping& reader); virtual GameObjectClasses get_class_types() const override { return GameObject::get_class_types().add(typeid(CoinExplode)); } + static std::string class_name() { return "coin_explode"; } + virtual std::string get_class_name() const override { return class_name(); } + static std::string display_name() { return _("Coin Explode"); } + virtual std::string get_display_name() const override { return display_name(); } + virtual bool is_portable() const override { return true; } + virtual void update(float dt_sec) override; virtual void draw(DrawingContext& context) override; virtual bool is_saveable() const override { return false; } + + virtual void grab(MovingObject& object, const Vector& pos, Direction dir) override; + virtual void ungrab(MovingObject& object, Direction dir) override; private: std::string m_sprite; Vector position; const bool m_count_stats; + void explode(); }; #endif diff --git a/src/object/coin_rain.cpp b/src/object/coin_rain.cpp index b296f6a6e55..771c5f06e56 100644 --- a/src/object/coin_rain.cpp +++ b/src/object/coin_rain.cpp @@ -39,9 +39,35 @@ CoinRain::CoinRain(const Vector& pos, bool emerge, bool count_stats, const std:: } } +CoinRain::CoinRain(const ReaderMapping& reader) : + GameObject(reader), + sprite(SpriteManager::current()->create("images/objects/coin/coin.sprite")), + m_sprite_path("images/objects/coin/coin.sprite"), + position(), + emerge_distance(0), + timer(), + counter(0), + drop(0), + m_count_stats(true) +{ + reader.get("x", position.x); + reader.get("y", position.y); + bool emerge = false; + reader.get("emerge", emerge); + + if (emerge) + { + emerge_distance = static_cast(sprite->get_height()); + } +} + void CoinRain::update(float dt_sec) { + if (is_grabbed()) + // Don't do anything while being carried + return; + // First a single (untouchable) coin flies up above the sector. if (position.y > -32){ float dist = -500 * dt_sec; @@ -81,4 +107,23 @@ CoinRain::draw(DrawingContext& context) sprite->draw(context.color(), position, layer); } +void +CoinRain::grab(MovingObject& object, const Vector& pos, Direction dir) +{ + position = pos; + Portable::grab(object, pos, dir); +} + +void +CoinRain::ungrab(MovingObject& object, Direction dir) +{ + // Reset counter to start coin rain effect + counter = 0; + drop = 0; + timer.stop(); + + position = object.get_pos(); + Portable::ungrab(object, dir); +} + /* EOF */ diff --git a/src/object/coin_rain.hpp b/src/object/coin_rain.hpp index df5c8ea3e5e..33bb26fe50a 100644 --- a/src/object/coin_rain.hpp +++ b/src/object/coin_rain.hpp @@ -20,20 +20,35 @@ #include "math/vector.hpp" #include "sprite/sprite_ptr.hpp" #include "supertux/game_object.hpp" +#include "object/portable.hpp" #include "supertux/timer.hpp" +#include "util/reader_mapping.hpp" -class CoinRain final : public GameObject +class CoinRain final : public GameObject, + public Portable { public: CoinRain(const Vector& pos, bool emerge=false, bool count_stats = true, const std::string& sprite_path = "images/objects/coin/coin.sprite"); + CoinRain(const ReaderMapping& reader); virtual GameObjectClasses get_class_types() const override { return GameObject::get_class_types().add(typeid(CoinRain)); } + + static std::string class_name() { return "coin_rain"; } + virtual std::string get_class_name() const override { return class_name(); } + static std::string display_name() { return _("Coin Rain"); } + virtual std::string get_display_name() const override { return display_name(); } + virtual void update(float dt_sec) override; virtual void draw(DrawingContext& context) override; virtual bool is_saveable() const override { return false; } + virtual bool is_portable() const override { return true; } + + virtual void grab(MovingObject& object, const Vector& pos, Direction dir) override; + virtual void ungrab(MovingObject& object, Direction dir) override; + private: SpritePtr sprite; std::string m_sprite_path; diff --git a/src/supertux/game_object_factory.cpp b/src/supertux/game_object_factory.cpp index d625fe7b49c..2a7dd4a1bad 100644 --- a/src/supertux/game_object_factory.cpp +++ b/src/supertux/game_object_factory.cpp @@ -96,6 +96,8 @@ #include "object/custom_particle_system.hpp" #include "object/custom_particle_system_file.hpp" #include "object/coin.hpp" +#include "object/coin_explode.hpp" +#include "object/coin_rain.hpp" #include "object/conveyor_belt.hpp" #include "object/decal.hpp" #include "object/display_effect.hpp" @@ -266,7 +268,9 @@ GameObjectFactory::init_factories() add_factory("conveyor-belt"); add_factory("particles-custom"); add_factory("particles-custom-file"); - add_factory("coin", OBJ_PARAM_DISPENSABLE); + add_factory("coin", OBJ_PARAM_PORTABLE | OBJ_PARAM_DISPENSABLE); + add_factory("coin_rain", OBJ_PARAM_PORTABLE | OBJ_PARAM_DISPENSABLE); + add_factory("coin_explode", OBJ_PARAM_PORTABLE | OBJ_PARAM_DISPENSABLE); add_factory("decal", OBJ_PARAM_WORLDMAP); add_factory("explosion", OBJ_PARAM_DISPENSABLE); add_factory("fallblock", OBJ_PARAM_DISPENSABLE);