#include "game/map/paint_layer.hpp" #include #include #include #include #include #include "core/rendering/screen.hpp" #include "core/rendering/surface.hpp" #include "core/resources/resource_cache.hpp" #include "game/map/map.hpp" #include "game/map/tilemap.hpp" namespace { // Blit d'un tile del tileset sobre un buffer ARGB de destí, respectant // l'índex transparent del GIF. Mateixa convenció que la versió a // `tilemap.cpp`: el tileset guarda índexs + paleta, i la destinació // és un buffer ARGB32 lineal. Duplicat intencionat per no acoblar // Tilemap i PaintLayer; si en el futur un tercer consumidor el vol // reutilitzar, es promou a un helper compartit. 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 PaintLayer::PaintLayer(const Tilemap& tilemap, EventBus* bus) : bus_(bus), cols_(tilemap.getCols()), rows_(tilemap.getRows()), tile_size_(tilemap.getTileSize()), origin_x_(tilemap.getOriginX()), origin_y_(tilemap.getOriginY()), width_px_(tilemap.getWidth()), height_px_(tilemap.getHeight()), tileset_cols_(tilemap.getTilesetCols()), tileset_path_(tilemap.getTilesetPath()) { painted_.assign(static_cast(rows_), std::vector(static_cast(cols_), -1)); pixels_.assign( static_cast(width_px_) * static_cast(height_px_), 0U); if (bus_ != nullptr) { // Subscripció a `on_tile_entered`: el Player publica aquest event // amb `PaintedTile{col, row, tile_index}`. Filtrem pintabilitat // (sòl transitable) via `Map::isPaintable` i, si aplica, marquem // i re-publiquem `tile_painted` amb només la posició. on_tile_sub_ = bus_->subscribe("on_tile_entered", [this](const Event& ev) { // Re-propaguem el `player_id` de qui ha trepitjat la cel·la, perquè // `tile_painted` (que consumeixen els sarcòfags i el HUD futur) // sàpiga quin jugador ha pintat. const auto* paint = std::get_if(&ev.payload); if (paint != nullptr) { tryPaint(paint->col, paint->row, paint->tile_index, ev.player_id); } }); } std::cout << "PaintLayer: " << cols_ << "x" << rows_ << " tiles (" << width_px_ << "x" << height_px_ << " px), tileset " << tileset_path_ << '\n'; } PaintLayer::~PaintLayer() { if (bus_ != nullptr && on_tile_sub_ != 0) { bus_->unsubscribe(on_tile_sub_); } } auto PaintLayer::isPainted(int col, int row) const -> bool { if (col < 0 || col >= cols_ || row < 0 || row >= rows_) { return false; } return painted_[static_cast(row)][static_cast(col)] >= 0; } void PaintLayer::clear() { // Mateixa geometria que al ctor: matriu de tiles a -1 (no pintat) i // buffer ARGB a zero (alpha=0 = transparent → render no escriu res). painted_.assign(static_cast(rows_), std::vector(static_cast(cols_), -1)); std::ranges::fill(pixels_, 0U); } auto PaintLayer::tryPaint(int col, int row, int tile_index, int player_id) -> bool { if (col < 0 || col >= cols_ || row < 0 || row >= rows_) { return false; } if (tile_index < 0) { return false; } if (painted_[static_cast(row)][static_cast(col)] >= 0) { // Primer que pinta guanya: no sobreescrivim. return false; } if (!Map::isPaintable(col, row)) { return false; } painted_[static_cast(row)][static_cast(col)] = tile_index; blitPaintTile(col, row, tile_index); if (bus_ != nullptr) { bus_->publish(Event{ .name = "tile_painted", .payload = TilePos{.col = col, .row = row}, .player_id = player_id, }); } return true; } void PaintLayer::blitPaintTile(int col, int row, int tile_index) { const auto TILESET = Resource::Cache::get().getSurface(tileset_path_); if (TILESET == nullptr) { return; } const int DX = col * tile_size_; const int DY = row * tile_size_; const int SRC_COL = tile_index % tileset_cols_; const int SRC_ROW = tile_index / tileset_cols_; blitTile(*TILESET, tile_size_, SRC_COL, SRC_ROW, pixels_, width_px_, DX, DY); } void PaintLayer::render() const { // Còpia ARGB al canvas, anàloga a `Tilemap::render`. Píxels amb // alpha=0 (tiles no pintats) es salten per deixar visible el tilemap // base per sota. auto& screen = Screen::get(); auto* canvas = screen.getCanvasArgb(); const int CANVAS_W = screen.getCanvasWidth(); const int CANVAS_H = screen.getCanvasHeight(); 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_); 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; } } } }