Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tux emits bubbles while being underwater #3014

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Binary file added data/images/particles/air_bubble-0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added data/images/particles/air_bubble-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added data/images/particles/air_bubble-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added data/images/particles/air_bubble-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added data/images/particles/air_bubble-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions data/images/particles/air_bubble.sprite
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
(supertux-sprite
(action
(name "normal")
(fps 15)
(loops 1)
(images "air_bubble-0.png"
"air_bubble-1.png"
"air_bubble-2.png"
"air_bubble-3.png"
"air_bubble-4.png"
)
)
(action
(name "small")
(fps 15)
(loops 1)
(images "air_bubble-0.png"
"air_bubble-1.png"
"air_bubble-2.png"
"air_bubble-3.png"
)
)
)
90 changes: 89 additions & 1 deletion src/object/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ Player::Player(PlayerStatus& player_status, const std::string& name_, int player
m_swimming_accel_modifier(100.f),
m_water_jump(false),
m_airarrow(Surface::from_file("images/engine/hud/airarrow.png")),
m_bubbles_sprite(SpriteManager::current()->create("images/particles/air_bubble.sprite")),
m_floor_normal(0.0f, 0.0f),
m_ghost_mode(false),
m_unduck_hurt_timer(),
Expand Down Expand Up @@ -254,6 +255,8 @@ Player::Player(PlayerStatus& player_status, const std::string& name_, int player
SoundManager::current()->preload("sounds/invincible_start.ogg");
SoundManager::current()->preload("sounds/splash.wav");
SoundManager::current()->preload("sounds/grow.wav");
m_bubble_timer.start(3.0f + graphicsRandom.randf(2));

m_col.set_size(TUX_WIDTH, is_big() ? BIG_TUX_HEIGHT : SMALL_TUX_HEIGHT);

m_sprite->set_angle(0.0f);
Expand Down Expand Up @@ -412,6 +415,22 @@ Player::update(float dt_sec)
}
}

if (!m_active_bubbles.empty())
{
for (auto& bubble : m_active_bubbles)
{
bubble.second.y -= dt_sec * 40.0f;
bubble.second.x += std::sin(bubble.second.y * 0.1f) * dt_sec * 5.0f;
}

m_active_bubbles.remove_if([&](const std::pair<SurfacePtr, Vector>& bubble)
{
Rectf bubble_box(bubble.second.x, bubble.second.y, bubble.second.x + 16.f, bubble.second.y + 16.f);
bool is_out_of_water = Sector::get().is_free_of_tiles(bubble_box, true, Tile::WATER);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure the bubble pops when hitting solid tiles.

return is_out_of_water;
});
}

// Skip if in multiplayer respawn
if (is_dead() && m_target && Sector::get().get_object_count<Player>([this](const Player& p) { return !p.is_dead() && !p.is_dying() && !p.is_winning() && &p != this; }))
{
Expand Down Expand Up @@ -514,6 +533,72 @@ Player::update(float dt_sec)
if (m_physic.get_velocity_y() > -350.f && m_controller->hold(Control::UP))
m_physic.set_velocity_y(-350.f);
}

if (m_bubble_timer.check())
{
Vector beak_local_offset(30.f, 0.0f);
float big_offset_x = is_big() ? 4.0f : 0.0f;

// Calculate the offsets based on the sprite angle
float offset_x = std::cos(m_swimming_angle) * 10.0f;
float offset_y = std::sin(m_swimming_angle) * 10.0f;

// Rotate the beak offset based on the sprite's angle
float rotated_beak_offset_x = beak_local_offset.x * std::cos(m_swimming_angle) - beak_local_offset.y * std::sin(m_swimming_angle);
float rotated_beak_offset_y = beak_local_offset.x * std::sin(m_swimming_angle) + beak_local_offset.y * std::cos(m_swimming_angle);

Vector player_center = m_col.m_bbox.get_middle();
Vector beak_position;

// Determine direction based on the radians
if (std::abs(m_swimming_angle) > static_cast<float>(math::PI_2)) // Facing left
{
beak_position = player_center + Vector(rotated_beak_offset_x - big_offset_x * 2, rotated_beak_offset_y);
}
else // Facing right (including straight up or down)
{
beak_position = player_center + Vector(rotated_beak_offset_x - 4.0f + big_offset_x, rotated_beak_offset_y);
}

int num_bubbles = graphicsRandom.rand(1, 3);

std::optional<std::vector<SurfacePtr>> bubble_surfaces = m_bubbles_sprite->get_action_surfaces("normal");

if (bubble_surfaces)
{
const std::vector<SurfacePtr>& surfaces = bubble_surfaces.value();
int surfaces_size = static_cast<int>(surfaces.size());

for (int i = 0; i < num_bubbles; ++i)
{
int random_index = graphicsRandom.rand(1, surfaces_size - 1);
SurfacePtr bubble_surface = surfaces.at(random_index);

Vector bubble_pos(0.f, 0.f);
if (std::abs(m_swimming_angle) > static_cast<float>(math::PI_2)) // Facing left
{
bubble_pos = beak_position + Vector(offset_x - big_offset_x * 2, offset_y);
}
else // Facing right (including straight up or down)
{
bubble_pos = beak_position + Vector(offset_x - 4.0f + big_offset_x, offset_y);
}

if (num_bubbles > 1)
{
float burst_offset_x = graphicsRandom.randf(-5.0f, 5.0f);
float burst_offset_y = graphicsRandom.randf(-5.0f, 5.0f);
bubble_pos.x += burst_offset_x;
bubble_pos.y += burst_offset_y;
}

m_active_bubbles.push_back({ bubble_surface, bubble_pos });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bubble should not try to spawn if it's not submerged, or else it will spawn the bubble for one frame and then pop in the next frame. To reproduce this bug, get in this position.
image

}
}

// Restart the timer for the next wave of bubbles
m_bubble_timer.start(0.8f + graphicsRandom.randf(0.0f, 0.6f));
}
}
else
{
Expand Down Expand Up @@ -2245,10 +2330,13 @@ Player::draw(DrawingContext& context)
get_bonus() == EARTH_BONUS ? Color(1.f, 0.9f, 0.6f) :
Color(1.f, 1.f, 1.f));

for (auto bubble_sprite : m_active_bubbles)
{
context.color().draw_surface(bubble_sprite.first, bubble_sprite.second, LAYER_TILES - 5);
}
m_sprite->set_color(m_stone ? Color(1.f, 1.f, 1.f) : power_color);
}


void
Player::collision_tile(uint32_t tile_attributes)
{
Expand Down
6 changes: 6 additions & 0 deletions src/object/player.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
#include "video/layer.hpp"
#include "video/surface_ptr.hpp"

#include <list>

class BadGuy;
class Climbable;
class Controller;
Expand Down Expand Up @@ -552,6 +554,10 @@ class Player final : public MovingObject

SurfacePtr m_airarrow; /**< arrow indicating Tux' position when he's above the camera */

SpritePtr m_bubbles_sprite; /**< bubble particles sprite for swimming */
Timer m_bubble_timer; /**< timer for spawning bubble particles */
std::list<std::pair<SurfacePtr, Vector>> m_active_bubbles; /**< active bubble particles */

Vector m_floor_normal;

bool m_ghost_mode; /**< indicates if Tux should float around and through solid objects */
Expand Down
Loading