feat(player): direcció preferida al HURT segons l'offset d'impacte
This commit is contained in:
1 parent
4667e0abf9
commit
c38c80753a
2 files changed
+108
-64
No files matched your search
@@ -584,74 +584,81 @@ void Player::startJump() {
|
||||
// tocaran fins a aterrar.
|
||||
}
|
||||
|
||||
void Player::enterHurt() {
|
||||
auto Player::deltasFor(Cardinal c) -> std::pair<float, float> {
|
||||
switch (c) {
|
||||
case Cardinal::EAST:
|
||||
return {1.0F, 0.0F};
|
||||
case Cardinal::WEST:
|
||||
return {-1.0F, 0.0F};
|
||||
case Cardinal::NORTH:
|
||||
return {0.0F, -1.0F};
|
||||
case Cardinal::SOUTH:
|
||||
return {0.0F, 1.0F};
|
||||
case Cardinal::NONE:
|
||||
break;
|
||||
}
|
||||
return {0.0F, 0.0F};
|
||||
}
|
||||
|
||||
auto Player::reachInDirection(float dx, float dy, int max_px) const -> int {
|
||||
const auto BASE = getBaseBox();
|
||||
for (int i = 1; i <= max_px; ++i) {
|
||||
SDL_FRect probe = BASE;
|
||||
probe.x += static_cast<float>(i) * dx;
|
||||
probe.y += static_cast<float>(i) * dy;
|
||||
if (map_.collides(probe) || !map_.inside_playfield(probe)) { return i - 1; }
|
||||
}
|
||||
return max_px;
|
||||
}
|
||||
|
||||
auto Player::chooseHurtDirection(Cardinal preferred) const -> Cardinal {
|
||||
constexpr int MAX_PX = 32; // 2 tiles, prou per a la cerca; la inèrcia limita el recorregut real.
|
||||
|
||||
// Recorregut natural del HURT (distància que recórreria sota
|
||||
// decaiment lineal pur). Si la direcció preferida cabe sense topar
|
||||
// abans d'aquesta distància, l'usem; d'altra manera, busquem la
|
||||
// millor.
|
||||
const auto REQUIRED = static_cast<int>(
|
||||
0.5F * hurt_config.initial_speed_px_per_s * (static_cast<float>(hurt_config.brake_time_ms) / 1000.0F));
|
||||
|
||||
if (preferred != Cardinal::NONE) {
|
||||
const auto [DX, DY] = deltasFor(preferred);
|
||||
if (reachInDirection(DX, DY, MAX_PX) >= REQUIRED) { return preferred; }
|
||||
}
|
||||
|
||||
constexpr std::array<Cardinal, 4> ALL{Cardinal::EAST, Cardinal::WEST, Cardinal::NORTH, Cardinal::SOUTH};
|
||||
int best_reach = -1;
|
||||
Cardinal best_dir = Cardinal::EAST;
|
||||
for (Cardinal c : ALL) {
|
||||
const auto [DX, DY] = deltasFor(c);
|
||||
const int R = reachInDirection(DX, DY, MAX_PX);
|
||||
if (R > best_reach) {
|
||||
best_reach = R;
|
||||
best_dir = c;
|
||||
}
|
||||
}
|
||||
return best_dir;
|
||||
}
|
||||
|
||||
void Player::enterHurt(Cardinal preferred) {
|
||||
// Si la mecànica no està configurada (speed = 0), no fem res — el
|
||||
// comportament queda equivalent al d'abans, sense empenta.
|
||||
if (hurt_config.initial_speed_px_per_s <= 0.0F || hurt_config.brake_time_ms <= 0) { return; }
|
||||
|
||||
// Triem la direcció amb més recorregut lliure: provem els 4
|
||||
// cardinals i ens quedem el de major distància contra muros/sarcs.
|
||||
// El test no comprova jugadors (durant HURT el jugador és
|
||||
// intangible al PvP), només geometria del mapa.
|
||||
constexpr int MAX_PX = 32; // 2 tiles, prou per a la cerca; la inèrcia limita el recorregut real.
|
||||
struct Candidate {
|
||||
Cardinal dir;
|
||||
float dx;
|
||||
float dy;
|
||||
};
|
||||
constexpr std::array<Candidate, 4> ALL{{
|
||||
{.dir = Cardinal::EAST, .dx = 1.0F, .dy = 0.0F},
|
||||
{.dir = Cardinal::WEST, .dx = -1.0F, .dy = 0.0F},
|
||||
{.dir = Cardinal::NORTH, .dx = 0.0F, .dy = -1.0F},
|
||||
{.dir = Cardinal::SOUTH, .dx = 0.0F, .dy = 1.0F},
|
||||
}};
|
||||
const auto BASE = getBaseBox();
|
||||
int best_reach = -1;
|
||||
Cardinal best_dir = Cardinal::EAST;
|
||||
for (const auto& c : ALL) {
|
||||
int reach = MAX_PX;
|
||||
for (int i = 1; i <= MAX_PX; ++i) {
|
||||
SDL_FRect probe = BASE;
|
||||
probe.x += static_cast<float>(i) * c.dx;
|
||||
probe.y += static_cast<float>(i) * c.dy;
|
||||
if (map_.collides(probe) || !map_.inside_playfield(probe)) {
|
||||
reach = i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (reach > best_reach) {
|
||||
best_reach = reach;
|
||||
best_dir = c.dir;
|
||||
}
|
||||
}
|
||||
const Cardinal CHOSEN = chooseHurtDirection(preferred);
|
||||
|
||||
// Activem l'estat i fixem la velocitat segons la direcció triada.
|
||||
state_ = State::HURT;
|
||||
applyCardinal(best_dir);
|
||||
last_input_cardinal_ = best_dir;
|
||||
applyCardinal(CHOSEN);
|
||||
last_input_cardinal_ = CHOSEN;
|
||||
turning_remaining_ms_ = 0.0F;
|
||||
pending_cardinal_ = Cardinal::NONE;
|
||||
intermediate_cardinal_ = Cardinal::NONE;
|
||||
being_pushed_ = false;
|
||||
const float V = hurt_config.initial_speed_px_per_s;
|
||||
intended_vx_ = 0.0F;
|
||||
intended_vy_ = 0.0F;
|
||||
switch (best_dir) {
|
||||
case Cardinal::EAST:
|
||||
intended_vx_ = V;
|
||||
break;
|
||||
case Cardinal::WEST:
|
||||
intended_vx_ = -V;
|
||||
break;
|
||||
case Cardinal::NORTH:
|
||||
intended_vy_ = -V;
|
||||
break;
|
||||
case Cardinal::SOUTH:
|
||||
intended_vy_ = V;
|
||||
break;
|
||||
case Cardinal::NONE:
|
||||
break;
|
||||
}
|
||||
const auto [DX, DY] = deltasFor(CHOSEN);
|
||||
intended_vx_ = DX * V;
|
||||
intended_vy_ = DY * V;
|
||||
input_vx_ = 0.0F;
|
||||
input_vy_ = 0.0F;
|
||||
motion_time_ms_ = 0.0F;
|
||||
@@ -692,7 +699,27 @@ void Player::resolveLandingOverlap() {
|
||||
const SDL_FRect MY_HIT = getHitBox();
|
||||
const SDL_FRect OT_HIT = other->getHitBox();
|
||||
if (!SDL_HasRectIntersectionFloat(&MY_HIT, &OT_HIT)) { continue; }
|
||||
other->enterHurt();
|
||||
|
||||
// Direcció preferida del HURT: cap on l'altre està respecte a
|
||||
// nosaltres (cardinal dominant del vector centre→centre this →
|
||||
// other). Si caem a la dreta del seu cos, ell està a l'esquerra
|
||||
// nostra → preferred = WEST → surt cap a l'esquerra. Si caem
|
||||
// exactament al damunt (centres alineats), preferred queda
|
||||
// NONE i `enterHurt` busca la millor direcció lliure.
|
||||
const float MCX = MY_HIT.x + (MY_HIT.w * 0.5F);
|
||||
const float MCY = MY_HIT.y + (MY_HIT.h * 0.5F);
|
||||
const float OCX = OT_HIT.x + (OT_HIT.w * 0.5F);
|
||||
const float OCY = OT_HIT.y + (OT_HIT.h * 0.5F);
|
||||
const float DX = OCX - MCX;
|
||||
const float DY = OCY - MCY;
|
||||
Cardinal preferred = Cardinal::NONE;
|
||||
if (std::fabs(DX) >= std::fabs(DY) && DX != 0.0F) {
|
||||
preferred = (DX > 0.0F) ? Cardinal::EAST : Cardinal::WEST;
|
||||
} else if (DY != 0.0F) {
|
||||
preferred = (DY > 0.0F) ? Cardinal::SOUTH : Cardinal::NORTH;
|
||||
}
|
||||
|
||||
other->enterHurt(preferred);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -411,13 +411,30 @@ class Player : public AnimatedSprite {
|
||||
static void readHurtBlock(const fkyaml::node& yaml);
|
||||
|
||||
// Activa l'estat HURT: el jugador queda projectat amb velocitat
|
||||
// inicial `hurt_config.initial_speed_px_per_s` cap a la direcció que
|
||||
// doni més recorregut lliure (provem els 4 cardinals i triem el de
|
||||
// major distància fins a topar amb un mur). Decelera linealment fins
|
||||
// a parar i, en eixe instant, torna a GROUNDED. Sense col·lisió amb
|
||||
// altres jugadors mentre dura — ja que es travessa l'eventual
|
||||
// solapament inicial.
|
||||
void enterHurt();
|
||||
// inicial `hurt_config.initial_speed_px_per_s`. La direcció es tria
|
||||
// així:
|
||||
// - Si `preferred != NONE` i hi cap el recorregut natural complet
|
||||
// (sense topar amb mur abans d'esgotar la inèrcia), s'usa
|
||||
// `preferred`.
|
||||
// - Altrament (o si `preferred == NONE`), prova els 4 cardinals
|
||||
// i tria el que dóna més recorregut lliure.
|
||||
// Decelera linealment fins a parar i, en eixe instant, torna a
|
||||
// GROUNDED. Sense col·lisió amb altres jugadors mentre dura.
|
||||
void enterHurt(Cardinal preferred = Cardinal::NONE);
|
||||
|
||||
// Tria la direcció definitiva del HURT. Si `preferred` cabe el
|
||||
// recorregut natural complet, la retorna; d'altra manera tria la
|
||||
// direcció amb més recorregut lliure entre els 4 cardinals.
|
||||
[[nodiscard]] auto chooseHurtDirection(Cardinal preferred) const -> Cardinal;
|
||||
|
||||
// Recorregut lliure (en píxels, fins a `max_px`) en la direcció
|
||||
// (dx, dy) abans de topar amb un mur o eixir del playfield. La
|
||||
// base-box es projecta `i` píxels i es comprova; retorna `i-1` al
|
||||
// primer xoc.
|
||||
[[nodiscard]] auto reachInDirection(float dx, float dy, int max_px) const -> int;
|
||||
|
||||
// Vectors unitaris dels 4 cardinals. `NONE` retorna (0, 0).
|
||||
[[nodiscard]] static auto deltasFor(Cardinal c) -> std::pair<float, float>;
|
||||
|
||||
// Estat de la màquina d'estats. Mutat per `tick`/`applyMovement`. Per
|
||||
// defecte GROUNDED al spawn.
|
||||
|
||||
Reference in new issue
Block a user