#include "game/map/map.hpp" #include #include #include "core/resources/resource_helper.hpp" #include "external/fkyaml_node.hpp" namespace { // AABB amb inequitat estricta: dos rects que es toquen exactament (la // cantonada o una vora al píxel) NO es consideren intersecció. SDL3 // SDL_HasRectIntersectionFloat tracta el toc com a intersecció, cosa // que bloqueja el moviment als encreuaments del nostre laberint. auto overlapStrict(const SDL_FRect& a, const SDL_FRect& b) -> bool { return (a.x + a.w) > b.x && (b.x + b.w) > a.x && (a.y + a.h) > b.y && (b.y + b.h) > a.y; } } // namespace namespace Map { void loadFrom(const std::string& yaml_path) { auto bytes = Resource::Helper::loadFile(yaml_path); if (bytes.empty()) { throw std::runtime_error("Map: no s'ha pogut obrir " + yaml_path); } const auto* begin = reinterpret_cast(bytes.data()); const auto* end = begin + bytes.size(); auto yaml = fkyaml::node::deserialize(begin, end); if (!yaml.contains("geometry")) { throw std::runtime_error("Map: manca bloc `geometry:` a " + yaml_path); } const auto& g = yaml["geometry"]; grid_cols = g["grid_cols"].get_value(); grid_rows = g["grid_rows"].get_value(); sarc_w = g["sarc_w"].get_value(); sarc_h = g["sarc_h"].get_value(); pitch_x = g["pitch_x"].get_value(); pitch_y = g["pitch_y"].get_value(); hud_h = g["hud_h"].get_value(); back_wall_h = g["back_wall_h"].get_value(); side_wall_w = g["side_wall_w"].get_value(); // Tile size + origen del tilemap (per a consumidors externs que // converteixen px ↔ tile). Fallbacks conservadors: tile_size per // defecte 16, origen [0, 0]. Si falten del YAML, només el sistema // d'entitats es veu afectat; la lògica de col·lisions actual no. tile_size = yaml.contains("tile_size") ? yaml["tile_size"].get_value() : 16; tilemap_origin_x = 0; tilemap_origin_y = 0; if (yaml.contains("origin")) { const auto& o = yaml["origin"]; if (o.is_sequence() && o.size() == 2) { tilemap_origin_x = o[0].get_value(); tilemap_origin_y = o[1].get_value(); } } // Derivats: el passadís és el sobrant del pitch sobre el sarcòfag, i // l'exterior reutilitza la mateixa amplada per mantenir el pas lliure. corridor_w = pitch_x - sarc_w; corridor_h = pitch_y - sarc_h; outer_corridor = corridor_w; board_w = ((grid_cols - 1) * pitch_x) + sarc_w; board_h = ((grid_rows - 1) * pitch_y) + sarc_h; origin_x = side_wall_w + outer_corridor; origin_y = hud_h + back_wall_h + outer_corridor; playfield_x = side_wall_w; playfield_y = hud_h + back_wall_h; playfield_w = board_w + (2 * outer_corridor); playfield_h = board_h + (2 * outer_corridor); std::cout << "Map: geometria " << grid_cols << "x" << grid_rows << " sarcs " << sarc_w << "x" << sarc_h << " pitch " << pitch_x << "x" << pitch_y << " des de " << yaml_path << '\n'; } auto getSarcRect(int col, int row) -> SDL_FRect { return { .x = static_cast(origin_x + (col * pitch_x)), .y = static_cast(origin_y + (row * pitch_y)), .w = static_cast(sarc_w), .h = static_cast(sarc_h), }; } auto collides(const SDL_FRect& base_box) -> bool { // Recorregut trivial dels sarcòfags. Amb una graella petita no val // la pena indexar per col/row prèviament; si algun dia creix, es pot // calcular el rang de columnes/files candidates a partir de base_box. for (int row = 0; row < grid_rows; ++row) { for (int col = 0; col < grid_cols; ++col) { if (overlapStrict(base_box, getSarcRect(col, row))) { return true; } } } return false; } auto insidePlayfield(const SDL_FRect& box) -> bool { return box.x >= static_cast(playfield_x) && box.y >= static_cast(playfield_y) && box.x + box.w <= static_cast(playfield_x + playfield_w) && box.y + box.h <= static_cast(playfield_y + playfield_h); } auto isPaintable(int col, int row) -> bool { // Construïm la caixa píxel del tile a partir de l'origen del tilemap. // El grid és 16-alineat respecte a `tilemap_origin_*`; per a un tile // (col,row) la cantonada superior-esquerra cau a: // x = tilemap_origin_x + col * tile_size // y = tilemap_origin_y + row * tile_size const SDL_FRect BOX{ .x = static_cast(tilemap_origin_x + (col * tile_size)), .y = static_cast(tilemap_origin_y + (row * tile_size)), .w = static_cast(tile_size), .h = static_cast(tile_size), }; if (!insidePlayfield(BOX)) { return false; } if (collides(BOX)) { return false; } return true; } auto makeInfo() -> Info { return { .playfield_x = playfield_x, .playfield_y = playfield_y, .playfield_w = playfield_w, .playfield_h = playfield_h, .pitch_x = pitch_x, .pitch_y = pitch_y, .grid_cols = grid_cols, .grid_rows = grid_rows, .tile_size = tile_size, .tilemap_origin_x = tilemap_origin_x, .tilemap_origin_y = tilemap_origin_y, .collides = &collides, .inside_playfield = &insidePlayfield, }; } } // namespace Map