Skip to content

Commit b15d254

Browse files
MatusGuytobbiRusty-BoxVankata453
authored
Zeekling behaviour improvements (#2624)
Balance zeekling by reworking it. --------- Co-authored-by: Tobias Markus <[email protected]> Co-authored-by: MatusGuy <[email protected]> Co-authored-by: Carsten Wirtz <[email protected]> Co-authored-by: Vankata453 <[email protected]>
1 parent fd548f2 commit b15d254

File tree

11 files changed

+232
-134
lines changed

11 files changed

+232
-134
lines changed
-2.93 KB
Loading
-3.02 KB
Loading
-2.96 KB
Loading
-2.08 KB
Loading
-2.16 KB
Loading
-1.85 KB
Loading

src/badguy/zeekling.cpp

+187-113
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,43 @@
1717

1818
#include "badguy/zeekling.hpp"
1919

20-
#include <math.h>
21-
20+
#include "math/easing.hpp"
2221
#include "math/random.hpp"
22+
#include "math/util.hpp"
23+
#include "math/vector.hpp"
2324
#include "object/player.hpp"
2425
#include "sprite/sprite.hpp"
26+
#include "supertux/sector.hpp"
27+
28+
static const float FLYING_SPEED = 180.f;
29+
static const float CHARGING_SPEED = 150.f;
30+
static const float DIVING_SPEED = 280.f;
31+
32+
static const float CHARGING_DURATION = 0.3f;
33+
static const float DIVING_DURATION = 1.5f;
34+
static const float RECOVER_DURATION = 2.8f;
35+
36+
static const float MIN_DETECT_RANGE_Y = 32.f * 4.5f;
37+
static const float MAX_DETECT_RANGE_Y = 512.f;
38+
static const float MIN_DETECT_RANGE_X = 10.f;
39+
static const float MAX_DETECT_RANGE_X = 32.f * 15.f;
40+
41+
static const float CATCH_OFFSET = -10.f;
2542

2643
Zeekling::Zeekling(const ReaderMapping& reader) :
2744
BadGuy(reader, "images/creatures/zeekling/zeekling.sprite"),
28-
speed(gameRandom.randf(130.0f, 171.0f)),
29-
diveRecoverTimer(),
30-
state(FLYING),
31-
last_player(nullptr),
32-
last_player_pos(0.0f, 0.0f),
33-
last_self_pos(0.0f, 0.0f)
45+
m_catch_pos(0.f),
46+
m_timer(),
47+
m_state(FLYING)
3448
{
3549
m_physic.enable_gravity(false);
50+
m_physic.set_velocity_x(FLYING_SPEED);
3651
}
3752

3853
void
3954
Zeekling::initialize()
4055
{
41-
m_physic.set_velocity_x(m_dir == Direction::LEFT ? -speed : speed);
56+
m_physic.set_velocity_x(m_physic.get_velocity_x() * (m_dir == Direction::LEFT ? -1 : 1));
4257
set_action(m_dir);
4358
}
4459

@@ -54,149 +69,202 @@ Zeekling::collision_squished(GameObject& object)
5469
}
5570

5671
void
57-
Zeekling::onBumpHorizontal()
58-
{
59-
if (state == FLYING) {
60-
m_dir = (m_dir == Direction::LEFT ? Direction::RIGHT : Direction::LEFT);
61-
set_action(m_dir);
62-
m_physic.set_velocity_x(m_dir == Direction::LEFT ? -speed : speed);
63-
} else
64-
if (state == DIVING) {
65-
m_dir = (m_dir == Direction::LEFT ? Direction::RIGHT : Direction::LEFT);
66-
state = FLYING;
67-
set_action(m_dir);
68-
m_physic.set_velocity(m_dir == Direction::LEFT ? -speed : speed, 0);
69-
} else
70-
if (state == CLIMBING) {
71-
m_dir = (m_dir == Direction::LEFT ? Direction::RIGHT : Direction::LEFT);
72-
set_action(m_dir);
73-
m_physic.set_velocity_x(m_dir == Direction::LEFT ? -speed : speed);
74-
} else {
75-
assert(false);
76-
}
72+
Zeekling::on_bump_horizontal()
73+
{
74+
m_dir = (m_dir == Direction::LEFT ? Direction::RIGHT : Direction::LEFT);
75+
set_action(m_dir);
76+
m_physic.set_velocity_x(std::abs(m_physic.get_velocity_x()) * (m_dir == Direction::LEFT ? -1 : 1));
77+
78+
if (m_state == DIVING)
79+
{
80+
// Diving attempt failed. So sad. Return to base.
81+
recover();
82+
}
7783
}
7884

7985
void
80-
Zeekling::onBumpVertical()
86+
Zeekling::on_bump_vertical()
8187
{
82-
if (BadGuy::get_state() == STATE_BURNING)
83-
{
84-
m_physic.set_velocity(0, 0);
85-
return;
88+
switch (m_state) {
89+
case DIVING:
90+
// Diving attempt failed. So sad. Return to base.
91+
recover();
92+
break;
93+
94+
case RECOVERING:
95+
// I guess this is my new home now.
96+
// Set start position to the current position.
97+
fly();
98+
break;
99+
100+
default:
101+
break;
86102
}
87-
if (state == FLYING) {
88-
m_physic.set_velocity_y(0);
89-
} else
90-
if (state == DIVING) {
91-
state = CLIMBING;
92-
m_physic.set_velocity_y(-speed);
93-
set_action(m_dir);
94-
} else
95-
if (state == CLIMBING) {
96-
state = FLYING;
97-
m_physic.set_velocity_y(0);
98-
}
99103
}
100104

101105
void
102106
Zeekling::collision_solid(const CollisionHit& hit)
103107
{
104108
if (m_frozen)
105-
BadGuy::collision_solid(hit);
106-
else
107109
{
108-
if (m_sprite->get_action() == "squished-left" ||
109-
m_sprite->get_action() == "squished-right")
110-
{
111-
return;
112-
}
110+
BadGuy::collision_solid(hit);
111+
return;
112+
}
113113

114-
if (hit.top || hit.bottom) {
115-
onBumpVertical();
116-
}
117-
else if (hit.left || hit.right) {
118-
onBumpHorizontal();
119-
}
114+
if (BadGuy::get_state() == STATE_SQUISHED ||
115+
BadGuy::get_state() == STATE_BURNING)
116+
{
117+
return;
120118
}
119+
120+
if (hit.top || hit.bottom)
121+
on_bump_vertical();
122+
else if (hit.left || hit.right)
123+
on_bump_horizontal();
121124
}
122125

123-
/** Linear prediction of player and badguy positions to decide if we should enter the DIVING state. */
124126
bool
125127
Zeekling::should_we_dive()
126128
{
129+
using RaycastResult = CollisionSystem::RaycastResult;
130+
127131
if (m_frozen)
128132
return false;
129133

130-
const auto player = get_nearest_player();
131-
if (player && last_player && (player == last_player)) {
134+
Player* player = get_nearest_player();
135+
if (!player)
136+
return false;
132137

133-
// Get positions and calculate movement.
134-
const Vector& player_pos = player->get_pos();
135-
const Vector player_mov = (player_pos - last_player_pos);
136-
const Vector self_pos = m_col.m_bbox.p1();
137-
const Vector self_mov = (self_pos - last_self_pos);
138+
// Left/rightmost point of the hitbox.
139+
Vector eye;
140+
const Rectf& bbox = get_bbox().grown(1.f);
141+
eye = bbox.get_middle();
142+
eye.x = m_dir == Direction::LEFT ? bbox.get_left() : bbox.get_right();
138143

139-
// New vertical speed to test with.
140-
float vy = 2*fabsf(self_mov.x);
144+
const Vector& playermiddle = player->get_bbox().get_middle();
141145

142-
// Do not dive if we are not above the player.
143-
float height = player_pos.y - self_pos.y;
144-
if (height <= 0) return false;
146+
// Do not dive if we are too close to the player.
147+
float height = player->get_bbox().get_top() - get_bbox().get_top();
148+
if (height <= MIN_DETECT_RANGE_Y)
149+
return false;
145150

146-
// Do not dive if we are too far above the player.
147-
if (height > 512) return false;
151+
// Do not dive if we are too far above the player.
152+
if (height > MAX_DETECT_RANGE_Y)
153+
return false;
148154

149-
// Do not dive if we would not descend faster than the player.
150-
float relSpeed = vy - player_mov.y;
151-
if (relSpeed <= 0) return false;
155+
float xdist = std::abs(eye.x - playermiddle.x);
156+
if (!math::in_bounds(xdist, MIN_DETECT_RANGE_X, MAX_DETECT_RANGE_X))
157+
return false;
152158

153-
// Guess number of frames to descend to same height as player.
154-
float estFrames = height / relSpeed;
159+
RaycastResult result = Sector::get().get_first_line_intersection(eye, playermiddle, false, nullptr);
155160

156-
// Guess where the player would be at this time.
157-
float estPx = (player_pos.x + (estFrames * player_mov.x));
161+
auto* resultobj = std::get_if<CollisionObject*>(&result.hit);
162+
163+
if (result.is_valid && resultobj &&
164+
*resultobj == player->get_collision_object())
165+
{
166+
m_target_y = player->get_bbox().get_top() + CATCH_OFFSET;
167+
return true;
168+
}
158169

159-
// Guess where we would be at this time.
160-
float estBx = (self_pos.x + (estFrames * self_mov.x));
170+
return false;
171+
}
161172

162-
// Allow for slight inaccuracies.
163-
if (fabsf(estPx - estBx) < 8) return true;
164-
}
173+
void
174+
Zeekling::set_speed(float speed)
175+
{
176+
m_physic.set_velocity_x(speed * (m_dir == Direction::LEFT ? -1 : 1));
177+
}
165178

166-
// Update the last player tracked, as well as our positions.
167-
last_player = player;
168-
if (player) {
169-
last_player_pos = player->get_pos();
170-
last_self_pos = m_col.m_bbox.p1();
171-
}
179+
void Zeekling::fly()
180+
{
181+
m_state = FLYING;
182+
set_speed(FLYING_SPEED);
183+
m_start_position.y = get_pos().y;
184+
set_action(m_dir);
185+
}
172186

173-
return false;
187+
void Zeekling::charge()
188+
{
189+
m_state = CHARGING;
190+
m_timer.start(CHARGING_DURATION);
191+
set_speed(CHARGING_SPEED);
192+
set_action("charge", m_dir);
193+
}
194+
195+
void Zeekling::dive()
196+
{
197+
m_state = DIVING;
198+
m_timer.start(DIVING_DURATION);
199+
set_speed(DIVING_SPEED);
200+
set_action("dive", m_dir);
201+
}
202+
203+
void Zeekling::recover()
204+
{
205+
m_state = RECOVERING;
206+
m_catch_pos = get_pos().y;
207+
m_timer.start(RECOVER_DURATION);
208+
set_action(m_dir);
174209
}
175210

176211
void
177212
Zeekling::active_update(float dt_sec) {
178-
if (state == FLYING) {
179-
if (should_we_dive()) {
180-
state = DIVING;
181-
m_physic.set_velocity_y(2*fabsf(m_physic.get_velocity_x()));
182-
set_action("dive", m_dir);
213+
switch (m_state) {
214+
case FLYING:
215+
if (!should_we_dive())
216+
break;
217+
218+
charge();
219+
220+
break;
221+
222+
case CHARGING:
223+
if (m_timer.check())
224+
{
225+
dive();
226+
}
227+
break;
228+
229+
case DIVING:
230+
{
231+
if (m_timer.check())
232+
{
233+
recover();
234+
break;
235+
}
236+
237+
const float dist = m_target_y - m_start_position.y;
238+
const double progress = CubicEaseIn(static_cast<double>(1.f - m_timer.get_progress()));
239+
const float value = m_target_y - (static_cast<float>(progress) * dist);
240+
const Vector pos(get_pos().x, value);
241+
set_pos(pos);
242+
243+
break;
183244
}
184-
BadGuy::active_update(dt_sec);
185-
return;
186-
} else if (state == DIVING) {
187-
BadGuy::active_update(dt_sec);
188-
return;
189-
} else if (state == CLIMBING) {
190-
// Stop climbing when we're back at initial height.
191-
if (get_pos().y <= m_start_position.y) {
192-
state = FLYING;
193-
m_physic.set_velocity_y(0);
245+
246+
case RECOVERING:
247+
{
248+
if (m_timer.check())
249+
{
250+
fly();
251+
break;
252+
}
253+
254+
const float dist = m_catch_pos - m_start_position.y;
255+
const double progress = QuadraticEaseInOut(static_cast<double>(m_timer.get_progress()));
256+
const float value = m_catch_pos - (static_cast<float>(progress) * dist);
257+
const Vector pos(get_pos().x, value);
258+
set_pos(pos);
259+
260+
break;
194261
}
195-
BadGuy::active_update(dt_sec);
196-
return;
197-
} else {
198-
assert(false);
262+
263+
default:
264+
break;
199265
}
266+
267+
BadGuy::active_update(dt_sec);
200268
}
201269

202270
void
@@ -211,7 +279,7 @@ Zeekling::unfreeze(bool melt)
211279
{
212280
BadGuy::unfreeze(melt);
213281
m_physic.enable_gravity(false);
214-
state = FLYING;
282+
m_state = FLYING;
215283
initialize();
216284
}
217285

@@ -221,4 +289,10 @@ Zeekling::is_freezable() const
221289
return true;
222290
}
223291

292+
void Zeekling::on_flip(float height)
293+
{
294+
BadGuy::on_flip(height);
295+
m_start_position.y = get_pos().y;
296+
}
297+
224298
/* EOF */

0 commit comments

Comments
 (0)