diff --git a/source/game/map/tilemap.cpp b/source/game/map/tilemap.cpp index 9a3c1ee..64cbc93 100644 --- a/source/game/map/tilemap.cpp +++ b/source/game/map/tilemap.cpp @@ -13,6 +13,10 @@ #include "core/resources/resource_helper.hpp" #include "external/fkyaml_node.hpp" +#ifdef _DEBUG +#include "game/map/tilemap_writer.hpp" +#endif + namespace { // Copia un tile (col, fila) del tileset al buffer ARGB a la posició de @@ -45,8 +49,8 @@ Tilemap::Tilemap(const std::string& yaml_path) { const auto* end = begin + bytes.size(); auto yaml = fkyaml::node::deserialize(begin, end); - const auto TILESET_PATH = yaml["tileset"].get_value(); - const auto TILE_SIZE = yaml["tile_size"].get_value(); + tileset_path_ = yaml["tileset"].get_value(); + 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(); @@ -55,10 +59,9 @@ Tilemap::Tilemap(const std::string& yaml_path) { // 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 llegir `data` 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; + Resource::Cache::get().loadSurface(tileset_path_, tileset_path_); + const auto TILESET = Resource::Cache::get().getSurface(tileset_path_); + tileset_cols_ = TILESET->getWidth() / tile_size_; // `data` és una seqüència de seqüències d'enters: cada fila és una // llista d'índexs lineals al tileset (esq→dre, dalt→baix). Un valor @@ -67,30 +70,65 @@ Tilemap::Tilemap(const std::string& yaml_path) { if (!data.is_sequence() || data.empty()) { throw std::runtime_error("Tilemap: `data` buit o amb format incorrecte a " + yaml_path); } - const int ROWS = static_cast(data.size()); - const int COLS = static_cast(data[0].size()); - width_px_ = COLS * TILE_SIZE; - height_px_ = ROWS * TILE_SIZE; + rows_ = static_cast(data.size()); + cols_ = static_cast(data[0].size()); + 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) { +#ifdef _DEBUG + data_.assign(static_cast(rows_), std::vector(static_cast(cols_), -1)); +#endif + + for (int row = 0; row < rows_; ++row) { const auto& line = data[row]; const int LINE_COLS = static_cast(line.size()); - for (int col = 0; col < COLS && col < LINE_COLS; ++col) { + for (int col = 0; col < cols_ && col < LINE_COLS; ++col) { const int INDEX = line[col].get_value(); +#ifdef _DEBUG + data_[static_cast(row)][static_cast(col)] = INDEX; +#endif if (INDEX < 0) { continue; } - const int SRC_COL = INDEX % TILESET_COLS; - const int SRC_ROW = INDEX / TILESET_COLS; - blitTile(*TILESET, TILE_SIZE, SRC_COL, SRC_ROW, pixels_, width_px_, col * TILE_SIZE, row * TILE_SIZE); + const int SRC_COL = INDEX % tileset_cols_; + const int SRC_ROW = INDEX / tileset_cols_; + blitTile(*TILESET, tile_size_, SRC_COL, SRC_ROW, pixels_, width_px_, col * tile_size_, row * tile_size_); } } - std::cout << "Tilemap: " << COLS << "x" << ROWS << " tiles (" + std::cout << "Tilemap: " << cols_ << "x" << rows_ << " tiles (" << width_px_ << "x" << height_px_ << " px) des de " << yaml_path << '\n'; } +#ifdef _DEBUG +void Tilemap::setTileAt(int col, int row, int index) { + if (col < 0 || col >= cols_ || row < 0 || row >= rows_) { return; } + if (index < 0) { return; } // El mapa no admet cel·les buides (política de projecte). + // No validem l'índex contra el total de tiles del tileset: la + // invariant la garanteix el TilePicker, que només ofereix índexs en + // rang. + + data_[static_cast(row)][static_cast(col)] = index; + + // Netegem el rectangle del tile al buffer de píxels i re-blitegem. + const int DX = col * tile_size_; + const int DY = row * tile_size_; + for (int y = 0; y < tile_size_; ++y) { + auto* dst = &pixels_[(static_cast(DY + y) * static_cast(width_px_)) + static_cast(DX)]; + for (int x = 0; x < tile_size_; ++x) { dst[x] = 0U; } + } + const auto TILESET = Resource::Cache::get().getSurface(tileset_path_); + const int SRC_COL = index % tileset_cols_; + const int SRC_ROW = index / tileset_cols_; + blitTile(*TILESET, tile_size_, SRC_COL, SRC_ROW, pixels_, width_px_, DX, DY); +} + +auto Tilemap::save(const std::string& yaml_path) const -> bool { + return TilemapWriter::saveDataBlock(yaml_path, data_); +} +#endif + void Tilemap::render() const { auto& screen = Screen::get(); auto* canvas = screen.getCanvasArgb(); diff --git a/source/game/map/tilemap.hpp b/source/game/map/tilemap.hpp index 75b7aaf..61f9920 100644 --- a/source/game/map/tilemap.hpp +++ b/source/game/map/tilemap.hpp @@ -27,10 +27,48 @@ class Tilemap { [[nodiscard]] auto getOriginX() const -> int { return origin_x_; } [[nodiscard]] auto getOriginY() const -> int { return origin_y_; } + // Metadades per a consumidors que necessiten conèixer la geometria + // lògica del tilemap (p.ex. l'editor per a la conversió px→cel·la). + // Sempre disponibles (en Debug i Release) perquè són barates i no + // lliguen amb cap codi específic de l'editor. + [[nodiscard]] auto getTileSize() const -> int { return tile_size_; } + [[nodiscard]] auto getCols() const -> int { return cols_; } + [[nodiscard]] auto getRows() const -> int { return rows_; } + [[nodiscard]] auto getTilesetPath() const -> const std::string& { return tileset_path_; } + [[nodiscard]] auto getTilesetCols() const -> int { return tileset_cols_; } + +#ifdef _DEBUG + // API d'edició (només Debug). L'editor és absent en Release, així que + // aquestes operacions no formen part de l'API pública del joc final. + // + // `setTileAt` valida els límits i reemplaça el tile de (col,row) per + // `index`. Internament neteja el rectangle `tile_size×tile_size` del + // buffer de píxels i re-blita el tile nou; el render al pròxim frame + // ja reflectirà el canvi. + // + // `save` persisteix `data_` al YAML indicat via `TilemapWriter`, que + // només toca el bloc `data:` i preserva la resta byte a byte. + void setTileAt(int col, int row, int index); + [[nodiscard]] auto save(const std::string& yaml_path) const -> bool; +#endif + private: int width_px_{0}; int height_px_{0}; int origin_x_{0}; int origin_y_{0}; + int tile_size_{0}; + int cols_{0}; + int rows_{0}; + int tileset_cols_{0}; + std::string tileset_path_; std::vector pixels_; + +#ifdef _DEBUG + // Graella en memòria només necessària per a l'editor: guardar-la en + // Release gastaria ~1 KB sense retorn, perquè el `pixels_` ja té tot + // el que cal per renderitzar. En Debug la usem com a font de veritat + // de l'edició i com a payload de `save()`. + std::vector> data_; +#endif };