diff --git a/source/game/entities/concrete/door.cpp b/source/game/entities/concrete/door.cpp index 5e0be92..c2de2a4 100644 --- a/source/game/entities/concrete/door.cpp +++ b/source/game/entities/concrete/door.cpp @@ -6,6 +6,7 @@ #include "game/entities/entity_factory.hpp" #include "game/entities/event_bus.hpp" #include "game/entities/player.hpp" +#include "game/map/map.hpp" namespace { @@ -80,6 +81,12 @@ void Door::open() { if (state_ != State::CLOSED) { return; } state_ = State::OPENING; setAnimation(anim_opening_); + // Marquem el passadís com a transitable: a partir d'aquest tic + // `Map::collides` deixa de bloquejar el rect del passadís. La + // transició d'animació OPENING → OPEN encara dura el seu temps, + // però la geometria ja és permeable — coherent amb el feel de + // "la porta s'obri i el jugador pot avançar al moment". + Map::door_open = true; // Edge case: si `opening` té ≤1 frame i és loop=true, mai es // completarà. Per força, forcem loop=false del frame únic aquí. Els // placeholders defineixen `loop: false` explícitament al YAML, així diff --git a/source/game/entities/player.cpp b/source/game/entities/player.cpp index fc97b5c..7577fb6 100644 --- a/source/game/entities/player.cpp +++ b/source/game/entities/player.cpp @@ -987,11 +987,27 @@ void Player::clampToPlayfield() { const auto MIN_X = static_cast(map_.playfield_x - collision_x_offset); const auto MAX_X = static_cast( map_.playfield_x + map_.playfield_w - collision_x_offset - collision_size); - const auto MIN_Y = static_cast(map_.playfield_y - collision_y_offset); + auto min_y = static_cast(map_.playfield_y - collision_y_offset); const auto MAX_Y = static_cast( map_.playfield_y + map_.playfield_h - collision_y_offset - collision_size); + + // Passadís de sortida: si la base box queda enterament dins del rang + // X del passadís de la porta, deixem que pugi al strip del mur trasser + // (fins a `door_corridor.y`). Fora del rang, MIN_Y es queda al + // playfield. La regla "enterament dins" exigeix alinear-se al passadís + // abans de pujar — la base box (12 px) hi cap amb 4 px de marge. + if (map_.door_corridor_w > 0) { + const float BASE_X = getPosX() + static_cast(collision_x_offset); + const float BASE_RIGHT = BASE_X + static_cast(collision_size); + const auto CORRIDOR_LEFT = static_cast(map_.door_corridor_x); + const auto CORRIDOR_RIGHT = static_cast(map_.door_corridor_x + map_.door_corridor_w); + if (BASE_X >= CORRIDOR_LEFT && BASE_RIGHT <= CORRIDOR_RIGHT) { + min_y = static_cast(map_.door_corridor_y - collision_y_offset); + } + } + setPosX(std::clamp(getPosX(), MIN_X, MAX_X)); - setPosY(std::clamp(getPosY(), MIN_Y, MAX_Y)); + setPosY(std::clamp(getPosY(), min_y, MAX_Y)); } auto Player::getBaseBox() const -> SDL_FRect { diff --git a/source/game/map/map.cpp b/source/game/map/map.cpp index f199505..b55912d 100644 --- a/source/game/map/map.cpp +++ b/source/game/map/map.cpp @@ -114,6 +114,18 @@ namespace Map { if (overlapStrict(base_box, getSarcRect(col, row))) { return true; } } } + // Porta tancada: el passadís de sortida actua com a paret. Quan la + // porta passa a OPEN, l'entitat Door posa `door_open = true` i + // aquesta clàusula deixa de bloquejar — el jugador pot creuar. + if (!door_open && door_corridor_w > 0 && door_corridor_h > 0) { + const SDL_FRect CORRIDOR{ + .x = static_cast(door_corridor_x), + .y = static_cast(door_corridor_y), + .w = static_cast(door_corridor_w), + .h = static_cast(door_corridor_h), + }; + if (overlapStrict(base_box, CORRIDOR)) { return true; } + } return false; }