From c4caa9021ed97b004c884c719578039b0a099201 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sat, 25 Apr 2026 11:55:04 +0200 Subject: [PATCH] feat(player): resol el solapament en aterrar damunt d'un altre jugador --- source/game/entities/player.cpp | 73 ++++++++++++++++++++++++++++++++- source/game/entities/player.hpp | 11 +++++ 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/source/game/entities/player.cpp b/source/game/entities/player.cpp index 9ec9243..1cccecd 100644 --- a/source/game/entities/player.cpp +++ b/source/game/entities/player.cpp @@ -502,12 +502,81 @@ auto Player::integrateJump(float delta_time) -> bool { return false; } +void Player::resolveLandingOverlap() { + if (all_players_ == nullptr) { return; } + for (Player* other : *all_players_) { + if (other == nullptr || other == this) { continue; } + const SDL_FRect MY_HIT = getHitBox(); + const SDL_FRect OT_HIT = other->getHitBox(); + SDL_FRect inter; + if (!SDL_GetRectIntersectionFloat(&MY_HIT, &OT_HIT, &inter)) { continue; } + + // Eix de separació: el que té els centres més separats. És + // l'axis-aligned MTV "natural" — equivalent a empentar l'altre + // cap a fora seguint la direcció centre→centre, projectada al + // costat dominant. Coincideix amb la intuïció: si el saltador + // li cau una mica per sobre i a la dreta, l'altre va cap a la + // dreta-baix; si li cau directament al damunt (dx≈0), l'altre + // s'aparta verticalment. + 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; + float push_x = 0.0F; + float push_y = 0.0F; + if (std::fabs(DX) >= std::fabs(DY)) { + push_x = (DX >= 0.0F ? 1.0F : -1.0F) * inter.w; + } else { + push_y = (DY >= 0.0F ? 1.0F : -1.0F) * inter.h; + } + + // 1r intent: empentar l'altre. Si la nova posició no col·lisiona + // amb res (mur, sarc, tercer jugador), commit i marquem-lo com + // a empentat per coherència de l'animació de l'altre frame. + const float OSX = other->getPosX(); + const float OSY = other->getPosY(); + other->setPosX(OSX + push_x); + other->setPosY(OSY + push_y); + if (!other->collidesWithWorld(other->getBaseBox())) { + other->setBeingPushed(true); + continue; + } + + // L'altre xocaria amb un mur: revertim i provem d'apartar el + // saltador cap al sentit oposat. Així respectem la regla "mai + // ficar ningú dins d'un mur": si no hi ha camí per a l'altre, + // qui es desplaça és el qui ha triat saltar. + other->setPosX(OSX); + other->setPosY(OSY); + const float MSX = getPosX(); + const float MSY = getPosY(); + setPosX(MSX - push_x); + setPosY(MSY - push_y); + if (!collidesWithWorld(getBaseBox())) { + continue; + } + + // Cap dels dos pot apartar-se sense xocar amb un mur. Cas + // degenerat (jugador encaixonat entre obstacles); el deixem com + // estava i acceptem el solapament residual fins al pròxim frame + // amb input. + setPosX(MSX); + setPosY(MSY); + } +} + void Player::applyMovement(float delta_time) { // Integració del salt: si estem AIRBORNE, avança jump_z_/jump_vz_. // El facing i intended_v* es mantenen del moment del despegue (no es // recalculen). Si toquem el sòl aquest frame, transicionem a - // LANDING_STUN ja al final d'`integrateJump`. - integrateJump(delta_time); + // LANDING_STUN i resolem el possible solapament amb un altre + // jugador que estigués sota — apartem-lo (o, si no es pot, ens + // apartem nosaltres). + if (integrateJump(delta_time)) { + resolveLandingOverlap(); + } // En LANDING_STUN no movem el jugador horitzontalment (la velocitat // ja s'ha posat a 0 al `tick`). Encara així animem (per estar al diff --git a/source/game/entities/player.hpp b/source/game/entities/player.hpp index ed71651..b7a7386 100644 --- a/source/game/entities/player.hpp +++ b/source/game/entities/player.hpp @@ -401,6 +401,17 @@ class Player : public AnimatedSprite { // `applyMovement` puga disparar la resolució del solapament PvP). auto integrateJump(float delta_time) -> bool; + // En aterrar, si la hit-box del jugador solapa amb la d'un altre, + // separa els dos sense ficar-los dins d'un mur. Algoritme: + // 1. Calcula la direcció centre→centre i la magnitud de + // solapament (MTV axis-aligned). + // 2. Empenta l'altre cap a fora; si no cap (mur al darrere), + // reverteix i empenta el saltador cap al sentit oposat. + // 3. Si tampoc cap, es deixa l'estat com està (overlap residual); + // a la pràctica aquest cas és molt rar i el usuari ja ha + // acceptat que no és crític. + void resolveLandingOverlap(); + // Estat del gir en curs. Mentre `turning_remaining_ms_ > 0`, el facing/ // flip es manté forçat a `intermediate_cardinal_` i l'animació es força // a `stand` encara que el moviment continuï. En esgotar-se el timer,