diff --git a/data/player/morcus.yaml b/data/player/morcus.yaml index 1bcb827..571c76a 100644 --- a/data/player/morcus.yaml +++ b/data/player/morcus.yaml @@ -15,11 +15,16 @@ collision: # `size` es reserva per a la col·lisió amb murs/sarcòfags i per enfilar # els passadissos. Centrada en el mateix punt que la caixa base. hit_size: 8 - # Alçada del cos en píxels per al filtre de col·lisió per altura: un - # actor saltant amb jump_z > height passa per damunt sense col·lisió. - # Mateix camp i semàntica que als YAML d'entitats (mummy, rolling_rock, - # etc.) — així el filtre queda uniforme per a tots els colls·lisionables. - height: 24 + # Alçada del cos en píxels. Funció DOBLE segons amb què col·lisione: + # · Contra entitats (mummy, rolling_rock, sarc): si `jump_z > height` + # d'aquella entitat, no hi ha col·lisió. Igual que als YAMLs + # d'entitats. + # · Contra altres jugadors: la col·lisió és binària per estat aeri. + # Si un està en l'aire i l'altre no → cap col·lisió. Si tots dos + # estan en l'aire, llavors `body_height` decideix si els rangs + # verticals solapen i s'aplica el rebot mid-aire. `0` = sense cos + # (ni rebot quan tots dos volen). + height: 12 # Animacions en mapa (clau = nom). Camps per animació: # frames → llista d'índexs dins de l'spritesheet. @@ -92,7 +97,7 @@ turn: # d'entitats: és una propietat de col·lisió, no de la física del salt. # Amb impulse=240 i gravity=600: pic ≈ 48 px, temps total en l'aire ≈ 0.8 s. jump: - impulse: 240 + impulse: 200 gravity: 600 landing_stun_ms: 200 shadow_y_offset: 3 diff --git a/data/player/sam.yaml b/data/player/sam.yaml index 80bdcb6..8599617 100644 --- a/data/player/sam.yaml +++ b/data/player/sam.yaml @@ -15,11 +15,16 @@ collision: # `size` es reserva per a la col·lisió amb murs/sarcòfags i per enfilar # els passadissos. Centrada en el mateix punt que la caixa base. hit_size: 8 - # Alçada del cos en píxels per al filtre de col·lisió per altura: un - # actor saltant amb jump_z > height passa per damunt sense col·lisió. - # Mateix camp i semàntica que als YAML d'entitats (mummy, rolling_rock, - # etc.) — així el filtre queda uniforme per a tots els colls·lisionables. - height: 24 + # Alçada del cos en píxels. Funció DOBLE segons amb què col·lisione: + # · Contra entitats (mummy, rolling_rock, sarc): si `jump_z > height` + # d'aquella entitat, no hi ha col·lisió. Igual que als YAMLs + # d'entitats. + # · Contra altres jugadors: la col·lisió és binària per estat aeri. + # Si un està en l'aire i l'altre no → cap col·lisió. Si tots dos + # estan en l'aire, llavors `body_height` decideix si els rangs + # verticals solapen i s'aplica el rebot mid-aire. `0` = sense cos + # (ni rebot quan tots dos volen). + height: 12 # Animacions en mapa (clau = nom). Camps per animació: # frames → llista d'índexs dins de l'spritesheet. @@ -92,7 +97,7 @@ turn: # d'entitats: és una propietat de col·lisió, no de la física del salt. # Amb impulse=240 i gravity=600: pic ≈ 48 px, temps total en l'aire ≈ 0.8 s. jump: - impulse: 240 + impulse: 200 gravity: 600 landing_stun_ms: 200 shadow_y_offset: 3 diff --git a/source/game/entities/player.cpp b/source/game/entities/player.cpp index ac5b928..3999d7a 100644 --- a/source/game/entities/player.cpp +++ b/source/game/entities/player.cpp @@ -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(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(body_height)) { + return false; + } const auto OTHER = p->getHitBox(); return SDL_HasRectIntersectionFloat(&PROPOSED_HIT, &OTHER); }); diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index a4278c2..579ce18 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -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(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(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);