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

Merged
merged 13 commits into from
Oct 20, 2024
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);
bruhmoent marked this conversation as resolved.
Show resolved Hide resolved
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 });
bruhmoent marked this conversation as resolved.
Show resolved Hide resolved
}
}

// 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
7 changes: 7 additions & 0 deletions src/object/player.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
#include "video/layer.hpp"
#include "video/surface_ptr.hpp"

#include <array>
bruhmoent marked this conversation as resolved.
Show resolved Hide resolved
#include <list>

class BadGuy;
class Climbable;
class Controller;
Expand Down Expand Up @@ -552,6 +555,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