feat(player): direcció preferida al HURT segons l'offset d'impacte

This commit is contained in:
JailDesigner committed 2026-04-25 15:29:48 +02:00
1 parent 4667e0abf9
commit c38c80753a
2 files changed
+108 -64

No files matched your search

+84 -57
View File
@@ -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);
}
}