feat(player): saltar travessa el caminant; rebot mid-aire entre dos saltadors

This commit is contained in:
JailDesigner committed 2026-04-25 14:20:38 +02:00
1 parent 3646e6a4aa
commit 188f035333
4 files changed
+78 -32

No files matched your search

+19 -9
View File
@@ -322,17 +322,27 @@ auto Player::collidesWithWorld(const SDL_FRect& box) const -> bool {
// (16×16) segueix topant amb murs/sarcs però els jugadors poden
// acostar-se fins al contacte real.
//
// Filtre per altura: si aquest jugador (this) està en l'aire i passa
// per damunt del cos de l'altre, no hi ha col·lisió — saltar permet
// sobrevolar l'altre. `body_height` és estàtic (compartit entre
// jugadors), però per al filtre val la pena consultar-lo per actor
// perquè en el futur cada character podria tenir-ne un de propi.
// Regla de col·lisió jugador↔jugador (segons el feel triat):
// - Tots dos a terra → col·lisió normal.
// - Un en l'aire i l'altre no → SENSE col·lisió. El saltador es
// travessa net del caminant; ningú actua de "muro" durant el
// vol, ni a la pujada ni a la baixada.
// - Tots dos en l'aire → col·lisió només si els rangs verticals
// [jump_z, jump_z + body_height] solapen. Així permetem el xoc
// mid-aire quan tots dos van un contra l'altre i estan a la
// mateixa franja d'alçada.
const SDL_FRect PROPOSED_HIT = hitBoxFromBase(box);
const float Z = jump_z_;
const bool AIRBORNE = (state_ == State::AIRBORNE);
return std::ranges::any_of(*all_players_, [this, &PROPOSED_HIT, AIRBORNE, Z](const Player* p) {
const bool MY_AIR = (state_ == State::AIRBORNE);
const float MY_Z = jump_z_;
return std::ranges::any_of(*all_players_, [this, &PROPOSED_HIT, MY_AIR, MY_Z](const Player* p) {
if (p == nullptr || p == this) { return false; }
if (AIRBORNE && body_height > 0 && Z > static_cast<float>(body_height)) { return false; }
if (MY_AIR != p->isAirborne()) { return false; }
// Aquí els dos estats coincideixen. Si tots dos volen, el filtre
// per altura mira que els rangs verticals encara solapin; si no,
// estan a prou distància en Y "real" i no es toquen.
if (MY_AIR && body_height > 0 && std::fabs(MY_Z - p->jump_z_) >= static_cast<float>(body_height)) {
return false;
}
const auto OTHER = p->getHitBox();
return SDL_HasRectIntersectionFloat(&PROPOSED_HIT, &OTHER);
});
+37 -11
View File
@@ -103,22 +103,22 @@ namespace {
auto* b = ps[1];
if (a == nullptr || b == nullptr) { return; }
// Filtre per altura: si un dels dos està en l'aire per damunt
// del cos de l'altre, no hi ha contacte possible — el saltador
// sobrevola. Saltar fa de "moviment lliure" per al resolver.
// `body_height` és estàtic carregat des del YAML; cppcheck el llig
// com a sempre-zero perquè no veu la inicialització en runtime.
const auto BODY_H = static_cast<float>(Player::body_height);
// cppcheck-suppress knownConditionTrueFalse
if (BODY_H > 0.0F && a->isAirborne() && a->getJumpZ() > BODY_H) { return; }
// cppcheck-suppress knownConditionTrueFalse
if (BODY_H > 0.0F && b->isAirborne() && b->getJumpZ() > BODY_H) { return; }
const bool A_AIR = a->isAirborne();
const bool B_AIR = b->isAirborne();
// Aire ↔ tierra: el saltador es travessa net del caminant. Sense
// resolver, sense head-on, sense push. Els dos continuen amb la
// velocitat que portaven. Important: això NO és el filtre per
// altura del cos — és binari. Coincideix amb la regla d'`Player::
// collidesWithWorld`: durant un salt no hi ha mai col·lisió amb
// un caminant, ni baix ni alt.
if (A_AIR != B_AIR) { return; }
// Calculem l'overlap previst en l'espai de la hit-box petita:
// els jugadors han de poder acostar-se molt (caixes de moviment
// 16×16 quasi tocant) sense disparar el resolver; només quan el
// contacte real (hit-box 8×8) s'activaria, neutralitzem o apliquem
// push.
// push (a terra) o invertim velocitats (en l'aire).
const SDL_FRect BOX_A = a->getHitBox();
const SDL_FRect BOX_B = b->getHitBox();
const SDL_FRect NEXT_A{
@@ -135,6 +135,32 @@ namespace {
};
if (!rectsOverlap(NEXT_A, NEXT_B)) { return; }
// Tots dos en l'aire (A_AIR == B_AIR == true aquí, ja descartat
// l'altre cas a dalt): rebot mid-aire si els rangs verticals
// [z, z+body_height] solapen. Si la diferència d'alçades és
// massa gran, ja no hi ha contacte real (un està molt més amunt
// que l'altre) i no fem res. Si solapen, invertim ambdues
// velocitats horitzontals i els deixem rebotar fins a aterrar
// — els dos cauen en stun, com a càstig per abusar del salt.
if (A_AIR) {
// cppcheck-suppress knownConditionTrueFalse
const auto BODY_H = static_cast<float>(Player::body_height);
// cppcheck-suppress knownConditionTrueFalse
if (BODY_H <= 0.0F) { return; }
if (std::fabs(a->getJumpZ() - b->getJumpZ()) >= BODY_H) { return; }
// Saltadors estàtics horitzontalment (cas degenerat: tots dos
// jumpejant verticals al mateix x): no hi ha velocitat a
// invertir, deixem-los — el predictiu no tornarà a disparar
// perquè en el següent frame ja no hi haurà velocitat
// d'acostament.
const float REL_VX = a->getIntendedVX() - b->getIntendedVX();
const float REL_VY = a->getIntendedVY() - b->getIntendedVY();
if (REL_VX == 0.0F && REL_VY == 0.0F) { return; }
a->setIntendedVelocity(-a->getIntendedVX(), -a->getIntendedVY());
b->setIntendedVelocity(-b->getIntendedVX(), -b->getIntendedVY());
return;
}
const bool MOVING_A = (a->getInputVX() != 0.0F) || (a->getInputVY() != 0.0F);
const bool MOVING_B = (b->getInputVX() != 0.0F) || (b->getInputVY() != 0.0F);