From fd548f278a26e67399fc95fa3bbcc1407444bfeb Mon Sep 17 00:00:00 2001 From: Marty <85036874+MatusGuy@users.noreply.github.com> Date: Mon, 9 Sep 2024 22:19:15 +0100 Subject: [PATCH] Fix max drop height on slopes (#2812) Fixes certain enemies reacting incorrectly by turning around when seeing a slope --- src/badguy/badguy.cpp | 127 +++++++++++++++++++++++++---- src/badguy/badguy.hpp | 16 +++- src/badguy/walking_badguy.cpp | 7 +- src/badguy/walking_badguy.hpp | 3 - src/collision/collision_system.cpp | 4 +- src/collision/collision_system.hpp | 2 +- src/math/aatriangle.hpp | 11 +++ src/supertux/sector.cpp | 5 +- src/supertux/sector.hpp | 3 +- 9 files changed, 148 insertions(+), 30 deletions(-) diff --git a/src/badguy/badguy.cpp b/src/badguy/badguy.cpp index a3ee560d57c..1768d39962f 100644 --- a/src/badguy/badguy.cpp +++ b/src/badguy/badguy.cpp @@ -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" @@ -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"); @@ -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; @@ -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) @@ -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(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(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(height)) + { + // The ground is within max drop height. Continue. + return false; + } + + auto tile_p = std::get_if(&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(height) + 1.f) + { + // Result is not within reach. + m_detected_slope = 0; + return true; + } + + auto tile_p = std::get_if(&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* diff --git a/src/badguy/badguy.hpp b/src/badguy/badguy.hpp index 9973086c8be..7dae7f435a7 100644 --- a/src/badguy/badguy.hpp +++ b/src/badguy/badguy.hpp @@ -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 @@ -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; @@ -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; @@ -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; diff --git a/src/badguy/walking_badguy.cpp b/src/badguy/walking_badguy.cpp index 51b07ccf3fc..ba96bb2e6a9 100644 --- a/src/badguy/walking_badguy.cpp +++ b/src/badguy/walking_badguy.cpp @@ -99,7 +99,7 @@ void WalkingBadguy::set_ledge_behavior(LedgeBehavior behavior) break; case LedgeBehavior::SMART: - max_drop_height = static_cast(get_bbox().get_width()) / 2; + max_drop_height = 16.f; break; case LedgeBehavior::NORMAL: @@ -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) diff --git a/src/badguy/walking_badguy.hpp b/src/badguy/walking_badguy.hpp index 4e43ed62235..49ab09639dc 100644 --- a/src/badguy/walking_badguy.hpp +++ b/src/badguy/walking_badguy.hpp @@ -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; diff --git a/src/collision/collision_system.cpp b/src/collision/collision_system.cpp index 1d2a4afa5ff..9b1d8658ad7 100644 --- a/src/collision/collision_system.cpp +++ b/src/collision/collision_system.cpp @@ -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; diff --git a/src/collision/collision_system.hpp b/src/collision/collision_system.hpp index 0dab8149991..41df059de2d 100644 --- a/src/collision/collision_system.hpp +++ b/src/collision/collision_system.hpp @@ -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; diff --git a/src/math/aatriangle.hpp b/src/math/aatriangle.hpp index de43eca1acb..97b145f3fb2 100644 --- a/src/math/aatriangle.hpp +++ b/src/math/aatriangle.hpp @@ -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; diff --git a/src/supertux/sector.cpp b/src/supertux/sector.cpp index 62ef6224e5d..eb2d4681eb0 100644 --- a/src/supertux/sector.cpp +++ b/src/supertux/sector.cpp @@ -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 diff --git a/src/supertux/sector.hpp b/src/supertux/sector.hpp index 5ef59840015..b206b3176ca 100644 --- a/src/supertux/sector.hpp +++ b/src/supertux/sector.hpp @@ -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: