diff --git a/data/room/04.yaml b/data/room/04.yaml index c20a8e7..a3a2cda 100644 --- a/data/room/04.yaml +++ b/data/room/04.yaml @@ -39,7 +39,7 @@ tilemap: - [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 269, 269, 269, 269, 269, -1, -1, -1, -1, -1, -1, -1, 24, 25] - [-1, -1, -1, -1, 141, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 24, 25] - [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 141, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 24, 25] - - [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 6, 7, 7, 7, 50, 25] + - [124, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 6, 7, 7, 7, 50, 25] # Mapa de colisiones (0 = vacio, 1 = solido) collision: - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1] @@ -62,4 +62,4 @@ tilemap: - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 1, 1] - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1] - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1] - - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1] + - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1] diff --git a/source/core/rendering/surface.cpp b/source/core/rendering/surface.cpp index ceb8bbf..8d8a7af 100644 --- a/source/core/rendering/surface.cpp +++ b/source/core/rendering/surface.cpp @@ -294,32 +294,34 @@ void Surface::render(int x, int y, SDL_FRect* src_rect, SDL_FlipMode flip) { // float w = (src_rect != nullptr) ? src_rect->w : surface_data_->width; float h = (src_rect != nullptr) ? src_rect->h : surface_data_->height; + // Guardar dimensiones originales antes del clipping (necesarias para flip) + float orig_w = (src_rect != nullptr) ? src_rect->w : static_cast(surface_data_->width); + float orig_h = (src_rect != nullptr) ? src_rect->h : static_cast(surface_data_->height); + // Limitar la región para evitar accesos fuera de rango en origen w = std::min(w, surface_data_->width - sx); h = std::min(h, surface_data_->height - sy); - w = std::min(w, surface_data_dest->width - x); - h = std::min(h, surface_data_dest->height - y); // Limitar la región para evitar accesos fuera de rango en destino - w = std::min(w, surface_data_dest->width - x); - h = std::min(h, surface_data_dest->height - y); + w = std::min(w, surface_data_dest->width - static_cast(x)); + h = std::min(h, surface_data_dest->height - static_cast(y)); // Renderiza píxel por píxel aplicando el flip si es necesario const Uint8* src_ptr = surface_data_->data.get(); Uint8* dst_ptr = surface_data_dest->data.get(); for (int iy = 0; iy < h; ++iy) { for (int ix = 0; ix < w; ++ix) { - // Coordenadas de origen - int src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + w - 1 - ix) : (sx + ix); - int src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + h - 1 - iy) : (sy + iy); + // Coordenadas de origen (flip usa dimensiones originales, no clipped) + int src_x = (flip == SDL_FLIP_HORIZONTAL) ? static_cast(sx + orig_w - 1 - ix) : static_cast(sx + ix); + int src_y = (flip == SDL_FLIP_VERTICAL) ? static_cast(sy + orig_h - 1 - iy) : static_cast(sy + iy); // Coordenadas de destino int dest_x = x + ix; int dest_y = y + iy; - // Verificar que las coordenadas de destino están dentro de los límites - if (dest_x >= 0 && dest_x < surface_data_dest->width && dest_y >= 0 && dest_y < surface_data_dest->height) { - // Copia el píxel si no es transparente + // Verificar que las coordenadas están dentro de los límites + if (dest_x >= 0 && dest_x < surface_data_dest->width && dest_y >= 0 && dest_y < surface_data_dest->height && + src_x >= 0 && src_x < surface_data_->width && src_y >= 0 && src_y < surface_data_->height) { Uint8 color = src_ptr[static_cast(src_x + (src_y * surface_data_->width))]; if (color != static_cast(transparent_color_)) { dst_ptr[static_cast(dest_x + (dest_y * surface_data_dest->width))] = sub_palette_[color]; diff --git a/source/game/entities/player.cpp b/source/game/entities/player.cpp index 88fddc2..b6295d6 100644 --- a/source/game/entities/player.cpp +++ b/source/game/entities/player.cpp @@ -239,7 +239,7 @@ void Player::moveHorizontal(float delta_time) { auto [tc, ox, oy] = getCollisionContext(); float new_x = x_ + (vx_ * delta_time); - // Colisión con paredes + // Colisión con paredes (room actual) if (vx_ < 0.0F) { float wall = tc.checkWallLeft(new_x + ox, y_ + oy, WIDTH, HEIGHT); if (wall != Collision::NONE) { @@ -252,6 +252,10 @@ void Player::moveHorizontal(float delta_time) { } } + // Cross-room: comprobar muros en rooms adyacentes + auto cross = getCrossRoomChecks(); + checkCrossRoomWallH(new_x, cross); + x_ = new_x; // Si estamos en una slope, ajustar Y para seguirla @@ -352,6 +356,8 @@ void Player::moveVertical(float delta_time) { auto [tc, ox, oy] = getCollisionContext(); float displacement = vy_ * delta_time; + float old_y = y_; + if (vy_ < 0.0F) { // Subiendo: comprobar techo float new_y = y_ + displacement; @@ -385,6 +391,10 @@ void Player::moveVertical(float delta_time) { #endif } } + + // Cross-room: comprobar suelo/techo en rooms adyacentes + auto cross = getCrossRoomChecks(); + checkCrossRoomFloor(old_y, cross); } // ============================================================================ @@ -423,6 +433,12 @@ void Player::checkFalling() { transitionToState(State::ON_SLOPE); return; } + + // Cross-room: comprobar suelo en rooms adyacentes antes de declarar caída + if (hasCrossRoomGround(getCrossRoomChecks())) { + return; + } + vy_ = 0.0F; transitionToState(State::ON_AIR); } @@ -553,6 +569,109 @@ void Player::syncSpriteAndCollider() { collider_box_ = getRect(); } +// Cross-room collision: asigna una room adyacente por índice +void Player::setBorderRoom(int index, std::shared_ptr room) { + if (index >= 0 && index < BORDER_ROOM_COUNT) { + border_rooms_[index] = std::move(room); + } +} + +// Cross-room collision: limpia todas las rooms adyacentes +void Player::clearBorderRooms() { + for (auto& r : border_rooms_) { + r.reset(); + } +} + +// Cross-room: construye la lista de rooms adyacentes que solapan con la bbox del jugador +auto Player::getCrossRoomChecks() const -> CrossRoomChecks { + // Offsets por room: TOP, RIGHT, BOTTOM, LEFT, TR, BR, BL, TL + static constexpr float PW = static_cast(PlayArea::WIDTH); + static constexpr float PH = static_cast(PlayArea::HEIGHT); + static constexpr struct { float ox; float oy; } OFFSETS[BORDER_ROOM_COUNT] = { + {0, PH}, {-PW, 0}, {0, -PH}, {PW, 0}, {-PW, PH}, {-PW, -PH}, {PW, -PH}, {PW, PH} + }; + + bool over_top = y_ < 0.0F; + bool over_right = (x_ + WIDTH) > PlayArea::RIGHT; + bool over_bottom = (y_ + HEIGHT) > PlayArea::BOTTOM; + bool over_left = x_ < 0.0F; + bool needed[BORDER_ROOM_COUNT] = { + over_top, over_right, over_bottom, over_left, + over_top && over_right, over_bottom && over_right, + over_bottom && over_left, over_top && over_left + }; + + CrossRoomChecks result; + for (int i = 0; i < BORDER_ROOM_COUNT; ++i) { + if (needed[i] && border_rooms_[i]) { + result.entries[result.count++] = {&border_rooms_[i]->getTileCollider(), OFFSETS[i].ox, OFFSETS[i].oy}; + } + } + return result; +} + +// Cross-room: comprueba muros horizontales en rooms adyacentes +void Player::checkCrossRoomWallH(float& new_x, const CrossRoomChecks& checks) const { + for (int i = 0; i < checks.count; ++i) { + const auto& [tc, ox, oy] = checks.entries[i]; + if (vx_ < 0.0F) { + float wall = tc->checkWallLeft(new_x + ox, y_ + oy, WIDTH, HEIGHT); + if (wall != Collision::NONE) { + float corrected = wall - ox; + if (corrected > new_x) { new_x = corrected; } + } + } else if (vx_ > 0.0F) { + float wall = tc->checkWallRight(new_x + ox, y_ + oy, WIDTH, HEIGHT); + if (wall != Collision::NONE) { + float corrected = wall - WIDTH - ox; + if (corrected < new_x) { new_x = corrected; } + } + } + } +} + +// Cross-room: comprueba suelo/techo en rooms adyacentes (usa old_y para rango correcto de checkFloor) +void Player::checkCrossRoomFloor(float old_y, const CrossRoomChecks& checks) { + for (int i = 0; i < checks.count; ++i) { + const auto& [tc, ox, oy] = checks.entries[i]; + if (vy_ < 0.0F) { + float ceiling = tc->checkCeiling(x_ + ox, y_ + oy, WIDTH); + if (ceiling != Collision::NONE) { + float corrected = ceiling - oy; + if (corrected > y_) { + y_ = corrected; + vy_ = 0.0F; + } + } + } else if (vy_ > 0.0F) { + float old_foot = old_y + oy + HEIGHT; + float new_foot = y_ + oy + HEIGHT; + auto hit = tc->checkFloor(x_ + ox, old_foot, WIDTH, new_foot); + if (hit.y != Collision::NONE) { + float corrected = hit.y - HEIGHT - oy; + if (corrected < y_) { + y_ = corrected; + vy_ = 0.0F; + transitionToState(State::ON_GROUND); + } + } + } + } +} + +// Cross-room: comprueba si hay suelo bajo el jugador en alguna room adyacente +auto Player::hasCrossRoomGround(const CrossRoomChecks& checks) const -> bool { + for (int i = 0; i < checks.count; ++i) { + const auto& [tc, ox, oy] = checks.entries[i]; + float foot = y_ + oy + HEIGHT; + if (tc->hasGroundBelow(x_ + ox, foot, WIDTH)) { + return true; + } + } + return false; +} + // Aplica el desplazamiento de una plataforma móvil al jugador void Player::applyPlatformDisplacement(float dx, float surface_y) { y_ = surface_y - HEIGHT; // Snap vertical al top de la plataforma diff --git a/source/game/entities/player.hpp b/source/game/entities/player.hpp index 2743b36..04f866e 100644 --- a/source/game/entities/player.hpp +++ b/source/game/entities/player.hpp @@ -76,6 +76,12 @@ class Player { [[nodiscard]] auto getVY() const -> float { return vy_; } void applyPlatformDisplacement(float dx, float surface_y); void clearPlatformFlag() { on_platform_ = false; } + + // Cross-room collision: rooms adyacentes (TOP=0, RIGHT=1, BOTTOM=2, LEFT=3, TR=4, BR=5, BL=6, TL=7) + static constexpr int BORDER_ROOM_COUNT = 8; + void setBorderRoom(int index, std::shared_ptr room); + void clearBorderRooms(); + void setPaused(bool value) { is_paused_ = value; } void setIgnoreInput(bool value) { ignore_input_ = value; } [[nodiscard]] auto getIgnoreInput() const -> bool { return ignore_input_; } @@ -136,6 +142,9 @@ class Player { Room::Border border_ = Room::Border::TOP; int last_grounded_position_ = 0; + // --- Cross-room collision --- + std::shared_ptr border_rooms_[BORDER_ROOM_COUNT]{}; + // --- Renderizado y sonido --- JA_Sound_t* jump_sound_ = nullptr; JA_Sound_t* land_sound_ = nullptr; @@ -147,6 +156,21 @@ class Player { void handleJumpAndDrop(); void moveHorizontal(float delta_time); void moveVertical(float delta_time); + + // Cross-room collision helpers + struct CrossRoomEntry { + const TileCollider* tc; + float ox; + float oy; + }; + struct CrossRoomChecks { + CrossRoomEntry entries[BORDER_ROOM_COUNT]{}; + int count{0}; + }; + auto getCrossRoomChecks() const -> CrossRoomChecks; + void checkCrossRoomWallH(float& new_x, const CrossRoomChecks& checks) const; + void checkCrossRoomFloor(float old_y, const CrossRoomChecks& checks); + auto hasCrossRoomGround(const CrossRoomChecks& checks) const -> bool; void followSlope(); void exitSlope(); void detectSlopeEntry(); diff --git a/source/game/scenes/game.cpp b/source/game/scenes/game.cpp index 9112fb4..c6130ea 100644 --- a/source/game/scenes/game.cpp +++ b/source/game/scenes/game.cpp @@ -310,6 +310,39 @@ void Game::updatePlaying(float delta_time) { player_->clearPlatformFlag(); checkPlayerAndPlatforms(); + // Cross-room collision: precargar rooms adyacentes para colisiones + { + player_->clearBorderRooms(); + auto loadBorder = [&](Room::Border b) -> std::shared_ptr { + auto name = room_->getRoom(b); + return (name != "0") ? getOrCreateRoom(name) : nullptr; + }; + auto top = loadBorder(Room::Border::TOP); + auto right = loadBorder(Room::Border::RIGHT); + auto bottom = loadBorder(Room::Border::BOTTOM); + auto left = loadBorder(Room::Border::LEFT); + if (top) { player_->setBorderRoom(0, top); } + if (right) { player_->setBorderRoom(1, right); } + if (bottom) { player_->setBorderRoom(2, bottom); } + if (left) { player_->setBorderRoom(3, left); } + // Diagonales + auto loadDiag = [&](const std::shared_ptr& a, Room::Border ab, + const std::shared_ptr& b, Room::Border ba) -> std::shared_ptr { + std::string name; + if (a) { name = a->getRoom(ab); } + if ((name.empty() || name == "0") && b) { name = b->getRoom(ba); } + return (!name.empty() && name != "0") ? getOrCreateRoom(name) : nullptr; + }; + auto tr = loadDiag(top, Room::Border::RIGHT, right, Room::Border::TOP); + auto br = loadDiag(bottom, Room::Border::RIGHT, right, Room::Border::BOTTOM); + auto bl = loadDiag(bottom, Room::Border::LEFT, left, Room::Border::BOTTOM); + auto tl = loadDiag(top, Room::Border::LEFT, left, Room::Border::TOP); + if (tr) { player_->setBorderRoom(4, tr); } + if (br) { player_->setBorderRoom(5, br); } + if (bl) { player_->setBorderRoom(6, bl); } + if (tl) { player_->setBorderRoom(7, tl); } + } + #ifdef _DEBUG // Maneja el arrastre del jugador con el ratón (debug) handleDebugMouseDrag(delta_time);