diff --git a/src/object/coin.cpp b/src/object/coin.cpp index 287c5dec55..23248b44de 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 7756ca2167..0eddc02a88 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 515505d539..93d3f2bcee 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 e7a316d4e7..bad56fd26b 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 b296f6a6e5..771c5f06e5 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 df5c8ea3e5..33bb26fe50 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 d625fe7b49..2a7dd4a1ba 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);