Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CMakeSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"configurations": [
{
"name": "x64-Debug",
"generator": "Ninja",
"configurationType": "Debug",
"inheritEnvironments": [ "msvc_x64_x64" ],
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": ""
}
]
}
13 changes: 12 additions & 1 deletion src/object/coin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "supertux/flip_level_transformer.hpp"
#include "supertux/level.hpp"
#include "supertux/sector.hpp"
#include "supertux/tile.hpp"
#include "util/reader_mapping.hpp"
#include "util/writer.hpp"

Expand Down Expand Up @@ -272,7 +273,17 @@ HeavyCoin::HeavyCoin(const ReaderMapping& reader, bool count_stats) :
void
HeavyCoin::update(float dt_sec)
{
// Enable physics.
// Water physics: reduce gravity so the coin sinks slowly, and cap
// downward velocity so coins that enter water at high speed don't
// punch straight to the bottom. Values chosen to visually match
// the floaty feel of BadGuy water physics (see badguy.cpp).
static const float WATER_GRAVITY_MODIFIER = 0.1f;
static const float WATER_MAX_DROP_SPEED = 100.f;

bool in_water = !Sector::get().is_free_of_tiles(get_bbox(), true, Tile::WATER);
m_physic.set_gravity_modifier(in_water ? WATER_GRAVITY_MODIFIER : 1.f);
if (in_water)
m_physic.set_velocity_y(std::min(m_physic.get_velocity_y(), WATER_MAX_DROP_SPEED));
m_col.set_movement(m_physic.get_movement(dt_sec));
}

Expand Down
3 changes: 3 additions & 0 deletions tests/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ make_unit_test(CollisionTest SOURCE collision_test.cpp
EXTERNAL math/rectf.cpp
LIBRARIES SDL2 glm DEFINITIONS GLM_ENABLE_EXPERIMENTAL)

make_unit_test(HeavyCoinWaterPhysicsTest SOURCE heavy_coin_water_test.cpp
LIBRARIES SDL2 glm DEFINITIONS GLM_ENABLE_EXPERIMENTAL)

message("ALL TESTS: ${all_test_targets}")

add_custom_target(tests DEPENDS ${all_test_targets})
Expand Down
100 changes: 100 additions & 0 deletions tests/unit/heavy_coin_water_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// SuperTux
// Copyright (C) 2026 Francisco Gama Franco Soares Martins
//
// 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 <http://www.gnu.org/licenses/>.

#include "st_assert.hpp"
#include "supertux/physic.hpp"

#include <algorithm>

// Provide the Physic constructor so we don't need to compile
// physic.cpp (which drags in Sector and half the engine).
Physic::Physic() :
ax(0), ay(0),
vx(0), vy(0),
gravity_enabled_flag(true),
gravity_modifier(1.0f)
{
}

// Water-physics constants mirrored from HeavyCoin::update().
static const float WATER_GRAVITY_MODIFIER = 0.1f;
static const float WATER_MAX_DROP_SPEED = 100.f;

// Reproduces the water-physics logic from HeavyCoin::update()
// without needing a Sector or tile lookup.
void apply_water_physics(Physic& physic, bool in_water)
{
physic.set_gravity_modifier(in_water ? WATER_GRAVITY_MODIFIER : 1.f);
if (in_water)
physic.set_velocity_y(std::min(physic.get_velocity_y(), WATER_MAX_DROP_SPEED));
}

int main(void)
{
// Gravity should be reduced to 10% when a coin is inside water.(We use this value to ensure the coin sinks slowly)
Physic p1;
apply_water_physics(p1, true);
ST_ASSERT("in water: gravity modifier is reduced",
p1.get_gravity_modifier() == WATER_GRAVITY_MODIFIER);

// Gravity should stay at 100% when a coin is outside water.
Physic p2;
apply_water_physics(p2, false);
ST_ASSERT("out of water: gravity modifier is normal",
p2.get_gravity_modifier() == 1.0f);

// A coin falling fast (500 px/s) should be capped to 100 px/s on water entry.
Physic p3;
p3.set_velocity_y(500.f);
apply_water_physics(p3, true);
ST_ASSERT("in water: fast fall velocity is capped",
p3.get_velocity_y() <= WATER_MAX_DROP_SPEED);

// A coin already falling slowly (50 px/s) should keep its speed in water.
Physic p4;
p4.set_velocity_y(50.f);
apply_water_physics(p4, true);
ST_ASSERT("in water: slow fall velocity unchanged",
p4.get_velocity_y() == 50.f);

// Upward velocity (negative) must not be clamped by the water cap.
Physic p5;
p5.set_velocity_y(-200.f);
apply_water_physics(p5, true);
ST_ASSERT("in water: upward velocity not capped",
p5.get_velocity_y() == -200.f);

// Outside water, velocity should never be capped regardless of speed.
Physic p6;
p6.set_velocity_y(500.f);
apply_water_physics(p6, false);
ST_ASSERT("out of water: velocity not capped",
p6.get_velocity_y() == 500.f);

// Entering water caps velocity; leaving water restores full gravity.
Physic p7;
p7.set_velocity_y(400.f);
apply_water_physics(p7, true);
ST_ASSERT("transition: velocity capped on entry",
p7.get_velocity_y() == WATER_MAX_DROP_SPEED);
apply_water_physics(p7, false);
ST_ASSERT("transition: gravity restored on exit",
p7.get_gravity_modifier() == 1.0f);

return 0;
}

/* EOF */