#include "game/map/tilemap.hpp" #include #include #include #include #include #include #include #include #include "core/rendering/screen.hpp" #include "core/rendering/surface.hpp" #include "core/resources/resource_cache.hpp" #include "core/resources/resource_helper.hpp" #include "external/fkyaml_node.hpp" namespace { // Cada símbol del grid es mapeja a (col, fila) dins del tileset. using TileCoord = std::pair; using Legend = std::unordered_map; auto splitLines(const std::string& text) -> std::vector { std::vector lines; std::istringstream iss(text); std::string line; while (std::getline(iss, line)) { if (!line.empty()) { lines.push_back(line); } } return lines; } // La llegenda usa índexs lineals del tileset (esq→dre, dalt→baix). // Els convertim a (col, fila) amb el nombre de tiles per fila del tileset. auto parseLegend(const fkyaml::node& node, int tileset_cols) -> Legend { Legend legend; for (auto it = node.begin(); it != node.end(); ++it) { const auto KEY_STR = it.key().get_value(); if (KEY_STR.empty()) { continue; } const auto INDEX = it.value().get_value(); const int COL = INDEX % tileset_cols; const int ROW = INDEX / tileset_cols; legend[KEY_STR.front()] = {COL, ROW}; } return legend; } // Copia un tile (col, fila) del tileset al buffer ARGB a la posició de // destí (dest_x, dest_y). Els píxels amb l'índex de transparència del // tileset es deixen com estan al buffer (alpha=0 per defecte). void blitTile(const Surface& tileset, int tile_size, int src_col, int src_row, std::vector& dest, int dest_w, int dest_x, int dest_y) { const int SRC_X0 = src_col * tile_size; const int SRC_Y0 = src_row * tile_size; const auto TRANSPARENT = tileset.getTransparentColor(); for (int y = 0; y < tile_size; ++y) { for (int x = 0; x < tile_size; ++x) { const auto IDX = tileset.getPixel(SRC_X0 + x, SRC_Y0 + y); if (IDX == TRANSPARENT) { continue; } const auto ARGB = tileset.getPaletteColor(IDX); const auto DST_OFFSET = (static_cast(dest_y + y) * static_cast(dest_w)) + static_cast(dest_x + x); dest[DST_OFFSET] = ARGB; } } } } // namespace Tilemap::Tilemap(const std::string& yaml_path) { const auto FULL = Resource::Helper::resolve(yaml_path); std::ifstream file(FULL); if (!file.is_open()) { throw std::runtime_error("Tilemap: no s'ha pogut obrir " + FULL); } auto yaml = fkyaml::node::deserialize(file); const auto TILESET_PATH = yaml["tileset"].get_value(); const auto TILE_SIZE = yaml["tile_size"].get_value(); if (yaml.contains("origin")) { origin_x_ = yaml["origin"][0].get_value(); origin_y_ = yaml["origin"][1].get_value(); } // Carreguem el tileset al cache (idempotent) i el pillem per accedir // als seus píxels i paleta. La paleta del tileset ve del GIF mateix. // Ens cal abans de la llegenda per saber quants tiles per fila té. const auto& cache_name = TILESET_PATH; Resource::Cache::get().loadSurface(cache_name, TILESET_PATH); const auto TILESET = Resource::Cache::get().getSurface(cache_name); const int TILESET_COLS = TILESET->getWidth() / TILE_SIZE; const auto LEGEND = parseLegend(yaml["legend"], TILESET_COLS); const auto LINES = splitLines(yaml["data"].get_value()); const int ROWS = static_cast(LINES.size()); const int COLS = ROWS > 0 ? static_cast(LINES.front().size()) : 0; width_px_ = COLS * TILE_SIZE; height_px_ = ROWS * TILE_SIZE; pixels_.assign( static_cast(width_px_) * static_cast(height_px_), 0U); for (int row = 0; row < ROWS; ++row) { const auto& line = LINES[row]; for (int col = 0; col < COLS && col < static_cast(line.size()); ++col) { const char SYMBOL = line[col]; const auto IT = LEGEND.find(SYMBOL); if (IT == LEGEND.end()) { continue; } blitTile(*TILESET, TILE_SIZE, IT->second.first, IT->second.second, pixels_, width_px_, col * TILE_SIZE, row * TILE_SIZE); } } std::cout << "Tilemap: " << COLS << "x" << ROWS << " tiles (" << width_px_ << "x" << height_px_ << " px) des de " << FULL << '\n'; } void Tilemap::render() const { auto& screen = Screen::get(); auto* canvas = screen.getCanvasArgb(); const int CANVAS_W = screen.getCanvasWidth(); const int CANVAS_H = screen.getCanvasHeight(); // Clipping de files: només pintem les que cauen dins del canvas. const int Y_START = std::max(0, -origin_y_); const int Y_END = std::min(height_px_, CANVAS_H - origin_y_); const int X_START = std::max(0, -origin_x_); const int X_END = std::min(width_px_, CANVAS_W - origin_x_); // Màscara del canal alfa (els píxels transparents del tilemap queden // amb alpha=0 i cal mantenir el canvas sota visible). constexpr std::uint32_t ALPHA_MASK = 0xFF000000U; for (int y = Y_START; y < Y_END; ++y) { const auto* src_row = &pixels_[static_cast(y) * static_cast(width_px_)]; auto* dst_row = &canvas[(static_cast(origin_y_ + y) * static_cast(CANVAS_W)) + static_cast(origin_x_)]; for (int x = X_START; x < X_END; ++x) { const auto PIXEL = src_row[x]; if ((PIXEL & ALPHA_MASK) != 0U) { dst_row[x] = PIXEL; } } } }