Skip to content

Commit

Permalink
Fix max drop height on slopes (#2812)
Browse files Browse the repository at this point in the history
Fixes certain enemies reacting incorrectly by turning around when seeing a slope
  • Loading branch information
MatusGuy authored Sep 9, 2024
1 parent a19de86 commit fd548f2
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 30 deletions.
127 changes: 111 additions & 16 deletions src/badguy/badguy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "audio/sound_manager.hpp"
#include "badguy/dispenser.hpp"
#include "editor/editor.hpp"
#include "math/aatriangle.hpp"
#include "math/random.hpp"
#include "object/bullet.hpp"
#include "object/camera.hpp"
Expand Down Expand Up @@ -69,11 +70,12 @@ BadGuy::BadGuy(const Vector& pos, Direction direction, const std::string& sprite
m_glowing(false),
m_water_affected(true),
m_unfreeze_timer(),
m_floor_normal(0.0f, 0.0f),
m_detected_slope(0),
m_state(STATE_INIT),
m_is_active_flag(),
m_state_timer(),
m_on_ground_flag(false),
m_floor_normal(0.0f, 0.0f),
m_colgroup_active(COLGROUP_MOVING)
{
SoundManager::current()->preload("sounds/squish.wav");
Expand Down Expand Up @@ -112,11 +114,12 @@ BadGuy::BadGuy(const ReaderMapping& reader, const std::string& sprite_name,
m_glowing(false),
m_water_affected(true),
m_unfreeze_timer(),
m_floor_normal(0.0f, 0.0f),
m_detected_slope(0),
m_state(STATE_INIT),
m_is_active_flag(),
m_state_timer(),
m_on_ground_flag(false),
m_floor_normal(0.0f, 0.0f),
m_colgroup_active(COLGROUP_MOVING)
{
std::string dir_str;
Expand All @@ -139,6 +142,25 @@ BadGuy::BadGuy(const ReaderMapping& reader, const std::string& sprite_name,
void
BadGuy::draw(DrawingContext& context)
{
/*
// TODEL: this shows the detection ranges for the might_fall function.
Vector eye(0, get_bbox().get_bottom() + 1.f);
eye.x = (m_dir == Direction::LEFT ? get_bbox().get_left() : get_bbox().get_right());
Vector end(eye.x, eye.y + 256.f);
context.color().draw_line(eye, end, Color::GREEN, LAYER_GUI);
float dirmult = (m_dir == Direction::LEFT ? 1.f : -1.f);
float rearx = (m_dir == Direction::LEFT ? get_bbox().get_right() : get_bbox().get_left());
float soff = (get_width() / 5.f) * dirmult;
Vector seye(rearx - soff, eye.y);
float eoff = soff - (2.f * dirmult);
Vector send(seye.x + eoff, seye.y + 80.f);
context.color().draw_line(seye, send, Color::GREEN, LAYER_GUI);
*/

if (!m_sprite.get()) return;

if (m_state == STATE_INIT || m_state == STATE_INACTIVE)
Expand Down Expand Up @@ -833,25 +855,98 @@ BadGuy::try_activate()
}

bool
BadGuy::might_fall(int height) const
BadGuy::might_fall(int height)
{
// Make sure we check for at least a 1-pixel fall.
using RaycastResult = CollisionSystem::RaycastResult;

assert(height > 0);

float x1;
float x2;
float y1 = m_col.m_bbox.get_bottom() + 1;
float y2 = m_col.m_bbox.get_bottom() + 1 + static_cast<float>(height);
if (m_dir == Direction::LEFT) {
x1 = m_col.m_bbox.get_left() - 1;
x2 = m_col.m_bbox.get_left();
} else {
x1 = m_col.m_bbox.get_right();
x2 = m_col.m_bbox.get_right() + 1;
// Origin in Y coord used for raycasting.
float oy = get_bbox().get_bottom() + 1.f;

if (m_detected_slope == 0)
{
Vector eye(0, oy);
eye.x = (m_dir == Direction::LEFT ? get_bbox().get_left() : get_bbox().get_right());

Vector end(eye.x, eye.y + static_cast<float>(s_normal_max_drop_height));

RaycastResult result = Sector::get().get_first_line_intersection(eye, end, false, nullptr);

if (result.is_valid && result.box.get_top() - eye.y < static_cast<float>(height))
{
// The ground is within max drop height. Continue.
return false;
}

auto tile_p = std::get_if<const Tile*>(&result.hit);
if (tile_p && (*tile_p) && (*tile_p)->is_slope())
{
// Check if we are about to go down a slope.
AATriangle tri((*tile_p)->get_data());
if (tri.is_south() && (m_dir == Direction::LEFT ? tri.is_east() : !tri.is_east()))
{
// Switch to slope mode.
m_detected_slope = tri.dir;
}

// Otherwise, climb the slope like normal,
// by returning false at the end of this function.
}
else
{
// The ground is no longer within reach. Turn around.
return true;
}
}
const Rectf rect = Rectf(x1, y1, x2, y2);

return Sector::get().is_free_of_statics(rect) && Sector::get().is_free_of_specifically_movingstatics(rect);
if (m_detected_slope != 0)
{
float dirmult = (m_dir == Direction::LEFT ? 1.f : -1.f);

// X position of the opposite face of the hitbox relative to m_dir.
float rearx = (m_dir == Direction::LEFT ? get_bbox().get_right() : get_bbox().get_left());

// X Offset from rearx used for determining the start of the raycast.
float startoff = (get_width() / 5.f) * dirmult;
Vector eye(rearx - startoff, oy);

// X Offset from eye's X used for determining the end of the raycast.
float endoff = startoff - (2.f * dirmult);
Vector end(eye.x + endoff, eye.y + 80.f);

// The resulting line segment (eye, end) should result in a downwards facing diagonal direction.

RaycastResult result = Sector::get().get_first_line_intersection(eye, end, false, nullptr);

std::cout << result.is_valid << std::endl;

if (!result.is_valid)
{
// Turn around and climb the slope.
m_detected_slope = 0;
return true;
}

if (result.box.get_top() - eye.y > static_cast<float>(height) + 1.f)
{
// Result is not within reach.
m_detected_slope = 0;
return true;
}

auto tile_p = std::get_if<const Tile*>(&result.hit);
if (tile_p && (*tile_p) && (*tile_p)->is_slope())
{
// Still going down a slope. Continue.
return false;
}

// No longer going down a slope. Switch off slope mode.
m_detected_slope = 0;
}

return false;
}

Player*
Expand Down
16 changes: 12 additions & 4 deletions src/badguy/badguy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ class BadGuy : public MovingSprite,

/** Returns true if we might soon fall at least @c height
pixels. Minimum value for height is 1 pixel */
bool might_fall(int height = 1) const;
bool might_fall(int height = 1);

/** Update on_ground_flag judging by solid collision @c hit. This
gets called from the base implementation of collision_solid, so
Expand Down Expand Up @@ -259,6 +259,9 @@ class BadGuy : public MovingSprite,
private:
void try_activate();

protected:
static const int s_normal_max_drop_height = 600;

protected:
Physic m_physic;

Expand Down Expand Up @@ -294,6 +297,14 @@ class BadGuy : public MovingSprite,

Timer m_unfreeze_timer;

/** floor normal stored the last time when update_on_ground_flag was
called and we touched something solid from above */
Vector m_floor_normal;

/** Used for the might_fall function.
Represents the tile data of the detected slope. */
int m_detected_slope;

private:
State m_state;

Expand All @@ -307,9 +318,6 @@ class BadGuy : public MovingSprite,
update_on_ground_flag was called last frame */
bool m_on_ground_flag;

/** floor normal stored the last time when update_on_ground_flag was
called and we touched something solid from above */
Vector m_floor_normal;

/** CollisionGroup the badguy should be in while active */
CollisionGroup m_colgroup_active;
Expand Down
7 changes: 6 additions & 1 deletion src/badguy/walking_badguy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ void WalkingBadguy::set_ledge_behavior(LedgeBehavior behavior)
break;

case LedgeBehavior::SMART:
max_drop_height = static_cast<int>(get_bbox().get_width()) / 2;
max_drop_height = 16.f;
break;

case LedgeBehavior::NORMAL:
Expand All @@ -123,6 +123,11 @@ WalkingBadguy::active_update(float dt_sec, float dest_x_velocity, float modifier
{
BadGuy::active_update(dt_sec);

// Walk down slopes smoothly.
if (on_ground() && m_floor_normal.y != 0 && (m_floor_normal.x * m_physic.get_velocity_x()) >= 0) {
m_physic.set_velocity_y((std::abs(m_physic.get_velocity_x()) * std::abs(m_floor_normal.x)) + 100.f);
}

float current_x_velocity = m_physic.get_velocity_x ();

if (m_frozen)
Expand Down
3 changes: 0 additions & 3 deletions src/badguy/walking_badguy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,6 @@ class WalkingBadguy : public BadGuy
protected:
void turn_around();

protected:
static const int s_normal_max_drop_height = 600;

protected:
std::string walk_left_action;
std::string walk_right_action;
Expand Down
4 changes: 2 additions & 2 deletions src/collision/collision_system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -708,11 +708,11 @@ CollisionSystem::is_free_of_tiles(const Rectf& rect, const bool ignoreUnisolid,
}

bool
CollisionSystem::is_free_of_statics(const Rectf& rect, const CollisionObject* ignore_object, const bool ignoreUnisolid) const
CollisionSystem::is_free_of_statics(const Rectf& rect, const CollisionObject* ignore_object, const bool ignoreUnisolid, uint32_t tiletype) const
{
using namespace collision;

if (!is_free_of_tiles(rect, ignoreUnisolid)) return false;
if (!is_free_of_tiles(rect, ignoreUnisolid, tiletype)) return false;

for (const auto& object : m_objects) {
if (object == ignore_object) continue;
Expand Down
2 changes: 1 addition & 1 deletion src/collision/collision_system.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class CollisionSystem final
}

bool is_free_of_tiles(const Rectf& rect, const bool ignoreUnisolid = false, uint32_t tiletype = Tile::SOLID) const;
bool is_free_of_statics(const Rectf& rect, const CollisionObject* ignore_object, const bool ignoreUnisolid) const;
bool is_free_of_statics(const Rectf& rect, const CollisionObject* ignore_object, const bool ignoreUnisolid, uint32_t tiletype = Tile::SOLID) const;
bool is_free_of_movingstatics(const Rectf& rect, const CollisionObject* ignore_object) const;
bool is_free_of_specifically_movingstatics(const Rectf& rect, const CollisionObject* ignore_object) const;

Expand Down
11 changes: 11 additions & 0 deletions src/math/aatriangle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ class AATriangle final
{
}

// Meant for checking directions
AATriangle(int newdir) :
bbox(),
dir(newdir)
{
}

inline int get_dir() const { return dir; }
inline bool is_south() const { return (dir & DIRECTION_MASK) == SOUTHWEST || (dir & DIRECTION_MASK) == SOUTHEAST; }
inline bool is_east() const { return (dir & DIRECTION_MASK) == NORTHEAST || (dir & DIRECTION_MASK) == SOUTHEAST; }

public:
Rectf bbox;
int dir;
Expand Down
5 changes: 3 additions & 2 deletions src/supertux/sector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -550,11 +550,12 @@ Sector::is_free_of_solid_tiles(float left, float top, float right, float bottom,
}

bool
Sector::is_free_of_statics(const Rectf& rect, const MovingObject* ignore_object, const bool ignoreUnisolid) const
Sector::is_free_of_statics(const Rectf& rect, const MovingObject* ignore_object, const bool ignoreUnisolid, uint32_t tiletype) const
{
return m_collision_system->is_free_of_statics(rect,
ignore_object ? ignore_object->get_collision_object() : nullptr,
ignoreUnisolid);
ignoreUnisolid,
tiletype);
}

bool
Expand Down
3 changes: 2 additions & 1 deletion src/supertux/sector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ class Sector final : public Base::Sector
1.) solid tiles and
2.) MovingObjects in COLGROUP_STATIC.
Note that this does not include badguys or players. */
bool is_free_of_statics(const Rectf& rect, const MovingObject* ignore_object = nullptr, const bool ignoreUnisolid = false) const;
bool is_free_of_statics(const Rectf& rect, const MovingObject* ignore_object = nullptr,
const bool ignoreUnisolid = false, uint32_t tiletype = Tile::SOLID) const;
/**
* @scripting
* @description Checks if the specified sector-relative rectangle is free of both:
Expand Down

0 comments on commit fd548f2

Please sign in to comment.