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

Zeekling behaviour improvements #2624

Merged
merged 53 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
41eff8f
code style fix for bump functions
MatusGuy Sep 5, 2023
1037d4e
code style polish for zeekling
MatusGuy Sep 5, 2023
29fcf4e
swoop up then down (looks rough)
MatusGuy Sep 5, 2023
8e0a31d
zeekling raycast detection
MatusGuy Sep 7, 2023
ae544c8
easing up/down
MatusGuy Sep 7, 2023
35e9477
remove unused fields
MatusGuy Sep 9, 2023
95020a8
forgot to remove them in initializer list
MatusGuy Sep 9, 2023
0b69aa7
swoop up depends on animation progress
MatusGuy Sep 11, 2023
37391e1
Merge branch 'SuperTux:master' into zeekling_behavior
MatusGuy Sep 19, 2023
a25356e
Merge branch 'SuperTux:master' into zeekling_behavior
MatusGuy Sep 20, 2023
4f0d527
Zeek detect player properly
MatusGuy Sep 20, 2023
1713e75
rebounce wip
MatusGuy Sep 20, 2023
a2a1123
Zeekling proper rebound
MatusGuy Nov 9, 2023
434998d
Zeek recover
MatusGuy Nov 10, 2023
0304ee8
Merge branch 'SuperTux:master' into zeekling_behavior
MatusGuy Nov 11, 2023
e59ec8b
fix bugs and tweak
MatusGuy Nov 11, 2023
5e23863
Handle bumping on wall while charging wip
MatusGuy Nov 14, 2023
11312a6
State handling upon bump
MatusGuy Dec 2, 2023
97bb941
You have gyatt to be kidding me
MatusGuy Dec 2, 2023
c6d7055
Merge branch 'zeekling_behavior' of https://github.com/MatusGuy/super…
MatusGuy Dec 3, 2023
9c82746
Zeekling detection line
MatusGuy Dec 3, 2023
62035d0
Rebound detection at edge of hitbox
MatusGuy Dec 3, 2023
0fd1cee
Prevent charging when right next to player
MatusGuy Dec 3, 2023
56a3d9b
You have gyatt to be kidding me... again!
MatusGuy Dec 3, 2023
e88dd1b
OMFG GIT
MatusGuy Dec 13, 2023
b90c9df
tweak
MatusGuy Dec 13, 2023
ebf5feb
Merge branch 'SuperTux:master' into zeekling_behavior
MatusGuy Jan 26, 2024
f54a040
divide player velocity or something like that idk
MatusGuy Jan 27, 2024
1fafb26
casting
MatusGuy Jan 27, 2024
a35d19b
Merge branch 'SuperTux:master' into zeekling_behavior
MatusGuy May 2, 2024
94023dd
math is so hawwd :3 :3 :3 :3 :3 :3 :3
MatusGuy May 9, 2024
6b55ea5
behaviour mostly finished
MatusGuy Jun 2, 2024
b28e5ae
rusty seal of approval
MatusGuy Jun 20, 2024
bd0c031
Merge branch 'SuperTux:master' into zeekling_behavior
MatusGuy Jun 20, 2024
158d0ac
zeekling charge
MatusGuy Jun 22, 2024
3826309
fix zeekling moonwalking
MatusGuy Jun 22, 2024
3f2d596
zeekling min dive height
MatusGuy Jun 22, 2024
00ccad9
Merge branch 'SuperTux:master' into zeekling_behavior
MatusGuy Jul 6, 2024
1b559ed
Fix zeekling flip clip bug and remove debug draw
MatusGuy Jul 11, 2024
f31b140
Merge branch 'zeekling_behavior' of https://github.com/MatusGuy/super…
MatusGuy Jul 11, 2024
aa12984
Minor adjustment to Zeekling "charge" animation [ci skip]
Rusty-Box Jul 11, 2024
3aa39b9
change switch statement
Jul 20, 2024
98124ca
fixes
MatusGuy Jul 20, 2024
a388580
code style [ci skip]
MatusGuy Jul 21, 2024
78c1588
code style [ci skip]
MatusGuy Jul 23, 2024
5842192
code style 3455436 [ci skip]
MatusGuy Jul 26, 2024
ccef82e
Merge branch 'real-master' into zeekling
MatusGuy Jul 26, 2024
c28cb48
Merge branch 'SuperTux:master' into zeekling_behavior
Rusty-Box Aug 7, 2024
f508da0
g
MatusGuy Aug 7, 2024
15f4294
gg
MatusGuy Aug 14, 2024
94dd97d
Merge branch 'real-master' into zeekling
MatusGuy Aug 16, 2024
caef364
bruh
MatusGuy Aug 16, 2024
024469d
Merge branch 'SuperTux:master' into zeekling_behavior
Rusty-Box Aug 29, 2024
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
Binary file modified data/images/creatures/zeekling/charge-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 modified data/images/creatures/zeekling/charge-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 modified data/images/creatures/zeekling/charge-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 modified data/images/creatures/zeekling/charge-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 modified data/images/creatures/zeekling/charge-4.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 modified data/images/creatures/zeekling/dive-0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
306 changes: 191 additions & 115 deletions src/badguy/zeekling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,43 @@

#include "badguy/zeekling.hpp"

#include <math.h>

#include "math/easing.hpp"
#include "math/random.hpp"
#include "math/util.hpp"
#include "math/vector.hpp"
#include "object/player.hpp"
#include "sprite/sprite.hpp"
#include "supertux/sector.hpp"

static const float FLYING_SPEED = 180.f;
static const float CHARGING_SPEED = 150.f;
static const float DIVING_SPEED = 280.f;

static const float CHARGING_DURATION = 0.3f;
static const float DIVING_DURATION = 1.5f;
static const float RECOVER_DURATION = 2.8f;

static const float MIN_DETECT_RANGE_Y = 32.f * 4.5f;
static const float MAX_DETECT_RANGE_Y = 512.f;
static const float MIN_DETECT_RANGE_X = 10.f;
static const float MAX_DETECT_RANGE_X = 32.f * 15.f;

static const float CATCH_OFFSET = -10.f;

Zeekling::Zeekling(const ReaderMapping& reader) :
BadGuy(reader, "images/creatures/zeekling/zeekling.sprite"),
speed(gameRandom.randf(130.0f, 171.0f)),
diveRecoverTimer(),
state(FLYING),
last_player(nullptr),
last_player_pos(0.0f, 0.0f),
last_self_pos(0.0f, 0.0f)
m_catch_pos(0.f),
m_timer(),
m_state(FLYING)
{
m_physic.enable_gravity(false);
m_physic.set_velocity_x(FLYING_SPEED);
}

void
Zeekling::initialize()
{
m_physic.set_velocity_x(m_dir == Direction::LEFT ? -speed : speed);
m_physic.set_velocity_x(m_physic.get_velocity_x() * (m_dir == Direction::LEFT ? -1 : 1));
tobbi marked this conversation as resolved.
Show resolved Hide resolved
set_action(m_dir);
}

Expand All @@ -54,149 +69,204 @@ Zeekling::collision_squished(GameObject& object)
}

void
Zeekling::onBumpHorizontal()
{
if (state == FLYING) {
m_dir = (m_dir == Direction::LEFT ? Direction::RIGHT : Direction::LEFT);
set_action(m_dir);
m_physic.set_velocity_x(m_dir == Direction::LEFT ? -speed : speed);
} else
if (state == DIVING) {
m_dir = (m_dir == Direction::LEFT ? Direction::RIGHT : Direction::LEFT);
state = FLYING;
set_action(m_dir);
m_physic.set_velocity(m_dir == Direction::LEFT ? -speed : speed, 0);
} else
if (state == CLIMBING) {
m_dir = (m_dir == Direction::LEFT ? Direction::RIGHT : Direction::LEFT);
set_action(m_dir);
m_physic.set_velocity_x(m_dir == Direction::LEFT ? -speed : speed);
} else {
assert(false);
}
Zeekling::on_bump_horizontal()
{
m_dir = (m_dir == Direction::LEFT ? Direction::RIGHT : Direction::LEFT);
set_action(m_dir);
m_physic.set_velocity_x(std::abs(m_physic.get_velocity_x()) * (m_dir == Direction::LEFT ? -1 : 1));

if (m_state == DIVING)
{
MatusGuy marked this conversation as resolved.
Show resolved Hide resolved
// Diving attempt failed. So sad. Return to base.
recover();
}
}

void
Zeekling::onBumpVertical()
Zeekling::on_bump_vertical()
{
if (BadGuy::get_state() == STATE_BURNING)
{
m_physic.set_velocity(0, 0);
return;
switch (m_state) {
case DIVING:
// Diving attempt failed. So sad. Return to base.
recover();
break;

case RECOVERING:
// I guess this is my new home now.
MatusGuy marked this conversation as resolved.
Show resolved Hide resolved
// Set start position to the current position.
fly();
break;

default:
break;
}
if (state == FLYING) {
m_physic.set_velocity_y(0);
} else
if (state == DIVING) {
state = CLIMBING;
m_physic.set_velocity_y(-speed);
set_action(m_dir);
} else
if (state == CLIMBING) {
state = FLYING;
m_physic.set_velocity_y(0);
}
}

void
Zeekling::collision_solid(const CollisionHit& hit)
{
if (m_frozen)
{
BadGuy::collision_solid(hit);
else
return;
}

if (BadGuy::get_state() == STATE_SQUISHED ||
BadGuy::get_state() == STATE_BURNING)
{
if (m_sprite->get_action() == "squished-left" ||
m_sprite->get_action() == "squished-right")
{
return;
}

if (hit.top || hit.bottom) {
onBumpVertical();
}
else if (hit.left || hit.right) {
onBumpHorizontal();
}
return;
}

if (hit.top || hit.bottom)
on_bump_vertical();
else if (hit.left || hit.right)
on_bump_horizontal();
}

/** Linear prediction of player and badguy positions to decide if we should enter the DIVING state. */
bool
Zeekling::should_we_dive()
MatusGuy marked this conversation as resolved.
Show resolved Hide resolved
{
using RaycastResult = CollisionSystem::RaycastResult;

if (m_frozen)
return false;

const auto player = get_nearest_player();
if (player && last_player && (player == last_player)) {
Player* player = get_nearest_player();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use auto? It's not that important, but wanted to mention it anyway.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to keep the usage of auto to a bare minimum, unless it's a long type or the same type is referenced twice in the same line.

if (!player)
return false;

// Left/rightmost point of the hitbox.
Vector eye;
const Rectf& bbox = get_bbox().grown(1.f);
eye = bbox.get_middle();
eye.x = m_dir == Direction::LEFT ? bbox.get_left() : bbox.get_right();

// Get positions and calculate movement.
const Vector& player_pos = player->get_pos();
const Vector player_mov = (player_pos - last_player_pos);
const Vector self_pos = m_col.m_bbox.p1();
const Vector self_mov = (self_pos - last_self_pos);
const Vector& playermiddle = player->get_bbox().get_middle();

// New vertical speed to test with.
float vy = 2*fabsf(self_mov.x);
// Do not dive if we are too close to the player.
float height = player->get_bbox().get_top() - get_bbox().get_top();
if (height <= MIN_DETECT_RANGE_Y)
return false;

// Do not dive if we are not above the player.
float height = player_pos.y - self_pos.y;
if (height <= 0) return false;
// Do not dive if we are too far above the player.
if (height > MAX_DETECT_RANGE_Y)
return false;

// Do not dive if we are too far above the player.
if (height > 512) return false;
float xdist = std::abs(eye.x - playermiddle.x);
if (!math::in_bounds(xdist, MIN_DETECT_RANGE_X, MAX_DETECT_RANGE_X))
return false;

// Do not dive if we would not descend faster than the player.
float relSpeed = vy - player_mov.y;
if (relSpeed <= 0) return false;
RaycastResult result = Sector::get().get_first_line_intersection(eye, playermiddle, false, nullptr);

// Guess number of frames to descend to same height as player.
float estFrames = height / relSpeed;
auto* resultobj = std::get_if<CollisionObject*>(&result.hit);

if (result.is_valid && resultobj &&
*resultobj == player->get_collision_object())
{
m_target_y = player->get_bbox().get_top() + CATCH_OFFSET;
return true;
}
else
MatusGuy marked this conversation as resolved.
Show resolved Hide resolved
{
return false;
}
}

// Guess where the player would be at this time.
float estPx = (player_pos.x + (estFrames * player_mov.x));
void
Zeekling::set_speed(float speed)
{
m_physic.set_velocity_x(speed * (m_dir == Direction::LEFT ? -1 : 1));
}

// Guess where we would be at this time.
float estBx = (self_pos.x + (estFrames * self_mov.x));
void Zeekling::fly()
{
m_state = FLYING;
set_speed(FLYING_SPEED);
m_start_position.y = get_pos().y;
set_action(m_dir);
}

// Allow for slight inaccuracies.
if (fabsf(estPx - estBx) < 8) return true;
}
void Zeekling::charge()
{
m_state = CHARGING;
m_timer.start(CHARGING_DURATION);
set_speed(CHARGING_SPEED);
set_action("charge", m_dir);
}

// Update the last player tracked, as well as our positions.
last_player = player;
if (player) {
last_player_pos = player->get_pos();
last_self_pos = m_col.m_bbox.p1();
}
void Zeekling::dive()
{
m_state = DIVING;
m_timer.start(DIVING_DURATION);
set_speed(DIVING_SPEED);
set_action("dive", m_dir);
}

return false;
void Zeekling::recover()
{
m_state = RECOVERING;
m_catch_pos = get_pos().y;
m_timer.start(RECOVER_DURATION);
set_action(m_dir);
}

void
Zeekling::active_update(float dt_sec) {
if (state == FLYING) {
if (should_we_dive()) {
state = DIVING;
m_physic.set_velocity_y(2*fabsf(m_physic.get_velocity_x()));
set_action("dive", m_dir);
}
BadGuy::active_update(dt_sec);
return;
} else if (state == DIVING) {
BadGuy::active_update(dt_sec);
return;
} else if (state == CLIMBING) {
// Stop climbing when we're back at initial height.
if (get_pos().y <= m_start_position.y) {
state = FLYING;
m_physic.set_velocity_y(0);
}
BadGuy::active_update(dt_sec);
return;
} else {
assert(false);
switch (m_state) {
case FLYING:
if (!should_we_dive())
break;

charge();

break;

case CHARGING:
if (m_timer.check())
{
dive();
}
break;

case DIVING:
if (m_timer.check())
{
recover();
}
else
{
const float dist = m_target_y - m_start_position.y;
const double progress = CubicEaseIn(static_cast<double>(1.f - m_timer.get_progress()));
const float value = m_target_y - (static_cast<float>(progress) * dist);
const Vector pos(get_pos().x, value);

set_pos(pos);

MatusGuy marked this conversation as resolved.
Show resolved Hide resolved
}
break;

case RECOVERING:
if (m_timer.check())
{
fly();
}
else
{
MatusGuy marked this conversation as resolved.
Show resolved Hide resolved
const float dist = m_catch_pos - m_start_position.y;
const double progress = QuadraticEaseInOut(static_cast<double>(m_timer.get_progress()));
const float value = m_catch_pos - (static_cast<float>(progress) * dist);
const Vector pos(get_pos().x, value);

set_pos(pos);
}

MatusGuy marked this conversation as resolved.
Show resolved Hide resolved
break;

default:
break;
}

BadGuy::active_update(dt_sec);
}

void
Expand All @@ -211,7 +281,7 @@ Zeekling::unfreeze(bool melt)
{
BadGuy::unfreeze(melt);
m_physic.enable_gravity(false);
state = FLYING;
m_state = FLYING;
initialize();
}

Expand All @@ -221,4 +291,10 @@ Zeekling::is_freezable() const
return true;
}

void Zeekling::on_flip(float height)
{
BadGuy::on_flip(height);
m_start_position.y = get_pos().y;
}

/* EOF */
Loading