fix(player): snapToLane respecta col·lisió PvP i empeny amb push_factor
This commit is contained in:
1 parent
1af43ef052
commit
4f1a3260a5
3 files changed
+108
-5
No files matched your search
@@ -531,12 +531,87 @@ void Player::snapToLane(
|
||||
const float LANE = base + (static_cast<float>(IDX) * pitch);
|
||||
const float DELTA = LANE - (this->*get_pos)();
|
||||
|
||||
if (std::abs(DELTA) > 0.0F && std::abs(DELTA) <= THRESHOLD) {
|
||||
const float STEP = std::min(std::abs(DELTA), SPEED_PX_PER_S * delta_time);
|
||||
const float SAVED = (this->*get_pos)();
|
||||
(this->*set_pos)(SAVED + std::copysign(STEP, DELTA));
|
||||
if (map_.collides(baseBox())) { (this->*set_pos)(SAVED); }
|
||||
if (std::abs(DELTA) <= 0.0F || std::abs(DELTA) > THRESHOLD) { return; }
|
||||
|
||||
const float FULL_STEP = std::min(std::abs(DELTA), SPEED_PX_PER_S * delta_time);
|
||||
const float SAVED = (this->*get_pos)();
|
||||
|
||||
// Fase 1: snap a velocitat plena. Si no xoca amb res, commit — és el
|
||||
// cas habitual (carril lliure) i no ha de patir la friccó de l'empenta.
|
||||
(this->*set_pos)(SAVED + std::copysign(FULL_STEP, DELTA));
|
||||
if (!collidesWithWorld(baseBox())) { return; }
|
||||
|
||||
// Mur al carril objectiu: no hi ha ningú a qui empentar, revert directe.
|
||||
if (map_.collides(baseBox())) {
|
||||
(this->*set_pos)(SAVED);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fase 2: sols un altre jugador al carril. Reintentem amb step escalat
|
||||
// per `push_factor_` (mateixa friccó que el push horitzontal del
|
||||
// resolver) i mirem d'arrossegar els blockers. Si la pushada no cap
|
||||
// (contra mur o tercer jugador), el snap es revertix sencer.
|
||||
const float PUSH_STEP = std::min(std::abs(DELTA), SPEED_PX_PER_S * push_factor_ * delta_time);
|
||||
const float SIGNED_PUSH = std::copysign(PUSH_STEP, DELTA);
|
||||
(this->*set_pos)(SAVED + SIGNED_PUSH);
|
||||
if (collidesWithWorld(baseBox())) {
|
||||
if (map_.collides(baseBox()) || !tryPushSnapBlockers(SIGNED_PUSH, get_pos, set_pos)) {
|
||||
(this->*set_pos)(SAVED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Player::tryPushSnapBlockers(
|
||||
float signed_step,
|
||||
float (Player::*get_pos)() const,
|
||||
void (Player::*set_pos)(float)) -> bool {
|
||||
if (all_players_ == nullptr) { return false; }
|
||||
|
||||
// Blockers = jugadors que ara mateix solapen la nostra hit-box (ja
|
||||
// amb el snap aplicat). Mateix criteri actor-actor que `collidesWithWorld`.
|
||||
std::vector<Player*> blockers;
|
||||
const SDL_FRect MY_HIT = getHitBox();
|
||||
for (Player* p : *all_players_) {
|
||||
if (p == nullptr || p == this) { continue; }
|
||||
const SDL_FRect OTHER_HIT = p->getHitBox();
|
||||
if (SDL_HasRectIntersectionFloat(&MY_HIT, &OTHER_HIT)) {
|
||||
blockers.push_back(p);
|
||||
}
|
||||
}
|
||||
if (blockers.empty()) { return false; }
|
||||
|
||||
// Desem la posició original de cada blocker i el desplacem el mateix
|
||||
// step que s'ha aplicat a `this`: com que els dos es mouen igual en el
|
||||
// mateix eix, la distància relativa es conserva i, per tant, si abans
|
||||
// del snap no es solapaven, després de l'empenta tampoc.
|
||||
std::vector<float> saved;
|
||||
saved.reserve(blockers.size());
|
||||
for (Player* p : blockers) {
|
||||
const float S = (p->*get_pos)();
|
||||
saved.push_back(S);
|
||||
(p->*set_pos)(S + signed_step);
|
||||
}
|
||||
|
||||
// Validació: `this` ha de quedar net (ni mur ni cap jugador), i cada
|
||||
// blocker també (cobreix el cas de 3+ jugadors on un blocker podria
|
||||
// acabar dins d'un tercer o contra un mur).
|
||||
const bool OK = !collidesWithWorld(baseBox()) && std::ranges::none_of(blockers, [](const Player* p) {
|
||||
return p->collidesWithWorld(p->getBaseBox());
|
||||
});
|
||||
|
||||
if (!OK) {
|
||||
for (size_t i = 0; i < blockers.size(); ++i) {
|
||||
(blockers[i]->*set_pos)(saved[i]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Els blockers s'han desplaçat sense input propi: mateixa convenció
|
||||
// d'animació que el push horitzontal del resolver (stand_*, no walk).
|
||||
for (Player* p : blockers) {
|
||||
p->setBeingPushed(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Player::snapToHorizontalLane(float delta_time) {
|
||||
|
||||
@@ -117,6 +117,14 @@ class Player : public AnimatedSprite {
|
||||
// posar cada frame que el push s'haja de mantenir.
|
||||
void setBeingPushed(bool pushed) { being_pushed_ = pushed; }
|
||||
|
||||
// Factor d'escala d'empenta jugador↔jugador (viu al YAML del mapa).
|
||||
// L'usa el resolver de GameScene per al push horitzontal, i també
|
||||
// `snapToLane` quan descobreix que un altre jugador li bloqueja el
|
||||
// carril objectiu: per coherència, la pushada del snap va al mateix
|
||||
// ritme que l'horitzontal. Si no es crida, el valor per defecte 1.0
|
||||
// manté el snap a velocitat plena (comportament pre-multiplayer).
|
||||
void setPushFactor(float factor) { push_factor_ = factor; }
|
||||
|
||||
// Y de la base del jugador (cara inferior de la caixa de colisió). Usada
|
||||
// per al Z-order contra els sarcs: peus per sobre d'un sarc → darrere;
|
||||
// per sota → davant.
|
||||
@@ -191,6 +199,18 @@ class Player : public AnimatedSprite {
|
||||
float (Player::*get_pos)() const,
|
||||
void (Player::*set_pos)(float));
|
||||
|
||||
// Quan el snap lateral trobaria un altre jugador al carril objectiu,
|
||||
// l'empentem el mateix step en el mateix eix. Així `this` pot entrar
|
||||
// al passadís encara que l'altre estiga estacionat al mitj — mateix
|
||||
// contracte que el push horitzontal però disparat pel snap. Retorna
|
||||
// true si la push ha quedat neta (ningú xoca amb el mapa ni amb un
|
||||
// tercer jugador); false fa que el caller revertisca el propi snap.
|
||||
// Marca els blockers amb `being_pushed_` per a la seua animació.
|
||||
auto tryPushSnapBlockers(
|
||||
float signed_step,
|
||||
float (Player::*get_pos)() const,
|
||||
void (Player::*set_pos)(float)) -> bool;
|
||||
|
||||
int id_{-1};
|
||||
Controls controls_;
|
||||
|
||||
@@ -225,6 +245,11 @@ class Player : public AnimatedSprite {
|
||||
// Serveix només per a l'elecció d'animació — la posició ja la
|
||||
// gestiona `intended_vx_/vy_` escalats pel resolver.
|
||||
bool being_pushed_{false};
|
||||
|
||||
// Escala d'empenta usada per `snapToLane` quan descobreix un blocker
|
||||
// (mateixa del resolver). 1.0 = sense escalar (comportament si
|
||||
// GameScene no ha cridat setPushFactor).
|
||||
float push_factor_{1.0F};
|
||||
// Còpia plana de la geometria del mapa + punters a `collides`/
|
||||
// `inside_playfield`. POD trivial; copiada al ctor i no muta durant
|
||||
// la vida del Player. Vol dir que aquest Player només és vàlid per
|
||||
|
||||
Reference in new issue
Block a user