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

Fix max drop height on slopes (part 2) #3060

Draft
wants to merge 24 commits into
base: master
Choose a base branch
from
Draft
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
105 changes: 88 additions & 17 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 @@ -70,11 +71,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 @@ -113,11 +115,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 Down Expand Up @@ -288,7 +291,7 @@ BadGuy::update(float dt_sec)
m_freezesprite->set_action(get_overlay_size(), 1);
else
m_freezesprite->set_action("default", 1);

active_update(dt_sec);
break;

Expand Down Expand Up @@ -837,25 +840,93 @@ 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;

float fh = static_cast<float>(height);

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

Vector end(eye.x, eye.y + fh + 2.f);

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

if (!result.is_valid)
{
// The ground is deeper than max drop height. Turn around.
return true;
}

auto tile_p = std::get_if<const Tile*>(&result.hit);
if (tile_p && (*tile_p) && (*tile_p)->is_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.
}
}

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, &m_col);

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

if (result.box.get_top() - oy > fh + 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;
}
const Rectf rect = Rectf(x1, y1, x2, y2);

return Sector::get().is_free_of_statics(rect) && Sector::get().is_free_of_specifically_movingstatics(rect);
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
97 changes: 58 additions & 39 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 Expand Up @@ -764,46 +764,49 @@ CollisionSystem::is_free_of_specifically_movingstatics(const Rectf& rect, const
CollisionSystem::RaycastResult
CollisionSystem::get_first_line_intersection(const Vector& line_start,
const Vector& line_end,
bool ignore_objects,
RaycastIgnore ignore,
const CollisionObject* ignore_object) const
{
using namespace collision;
RaycastResult result{};

// Check if no tile is in the way.
const float lsx = std::min(line_start.x, line_end.x);
const float lex = std::max(line_start.x, line_end.x);
const float lsy = std::min(line_start.y, line_end.y);
const float ley = std::max(line_start.y, line_end.y);

for (float test_x = lsx; test_x <= lex; test_x += 16) { // NOLINT.
for (float test_y = lsy; test_y <= ley; test_y += 16) { // NOLINT.
for (const auto& solids : m_sector.get_solid_tilemaps()) {
const auto& test_vector = Vector(test_x, test_y);
if(solids->is_outside_bounds(test_vector))
{
continue;
}
RaycastResult tileresult;

if (ignore != IGNORE_TILES)
{
// Check if no tile is in the way.
const float lsx = std::min(line_start.x, line_end.x);
const float lex = std::max(line_start.x, line_end.x);
const float lsy = std::min(line_start.y, line_end.y);
const float ley = std::max(line_start.y, line_end.y);

for (float test_x = lsx; test_x <= lex; test_x += 16) { // NOLINT.
for (float test_y = lsy; test_y <= ley; test_y += 16) { // NOLINT.
for (const auto& solids : m_sector.get_solid_tilemaps()) {
const auto& test_vector = Vector(test_x, test_y);
if(solids->is_outside_bounds(test_vector))
{
continue;
}

const Tile* tile = &solids->get_tile_at(test_vector);
const Tile* tile = &solids->get_tile_at(test_vector);

// FIXME: check collision with slope tiles
if ((tile->get_attributes() & Tile::SOLID))
{
result.is_valid = true;
result.hit = tile;
result.box = solids->get_tile_bbox(static_cast<int>(test_vector.x / 32.f), static_cast<int>(test_vector.y / 32.f));
return result;
// FIXME: check collision with slope tiles
if ((tile->get_attributes() & Tile::SOLID))
{
tileresult.is_valid = true;
tileresult.hit = tile;
tileresult.box = solids->get_tile_bbox(static_cast<int>(test_vector.x / 32.f), static_cast<int>(test_vector.y / 32.f));
goto finish_tiles;
}
}
}
}
}

if (ignore_objects)
{
result.is_valid = false;
return result;
}
finish_tiles:
if (ignore == IGNORE_OBJECTS)
return tileresult;

RaycastResult objresult;

// Check if no object is in the way.
for (const auto& object : m_objects) {
Expand All @@ -815,22 +818,38 @@ CollisionSystem::get_first_line_intersection(const Vector& line_start,
{
if (intersects_line(object->get_bbox(), line_start, line_end))
{
result.is_valid = true;
result.hit = object;
result.box = object->get_bbox();
return result;
objresult.is_valid = true;
objresult.hit = object;
objresult.box = object->get_bbox();
break;
}
}
}

result.is_valid = false;
return result;
if (ignore == IGNORE_TILES)
return objresult;

if (tileresult.is_valid && objresult.is_valid)
{
float tiledist = glm::distance(tileresult.box.get_middle(), line_start);
float objdist = glm::distance(objresult.box.get_middle(), line_start);
return tiledist < objdist ? tileresult : objresult;
}
else if (tileresult.is_valid)
return tileresult;
else if (objresult.is_valid)
return objresult;
else
{
return RaycastResult();
}
}

bool
CollisionSystem::free_line_of_sight(const Vector& line_start, const Vector& line_end, bool ignore_objects, const CollisionObject* ignore_object) const
{
return !get_first_line_intersection(line_start, line_end, ignore_objects, ignore_object).is_valid;
auto ignore = (ignore_objects ? IGNORE_OBJECTS : IGNORE_NONE);
return !get_first_line_intersection(line_start, line_end, ignore, ignore_object).is_valid;
}

std::vector<CollisionObject*>
Expand Down
Loading
Loading