This commit is contained in:
2026-04-05 23:47:54 +02:00
parent 25ecc74251
commit c4a26ffa0f
96 changed files with 457 additions and 307 deletions

View File

@@ -33,6 +33,7 @@ Input::Input(std::string game_controller_db_path)
{Action::LEFT, KeyState{.scancode = SDL_SCANCODE_LEFT}},
{Action::RIGHT, KeyState{.scancode = SDL_SCANCODE_RIGHT}},
{Action::JUMP, KeyState{.scancode = SDL_SCANCODE_UP}},
{Action::DOWN, KeyState{.scancode = SDL_SCANCODE_DOWN}},
// Inputs de control
{Action::ACCEPT, KeyState{.scancode = SDL_SCANCODE_RETURN}},

View File

@@ -7,6 +7,7 @@ const std::unordered_map<InputAction, std::string> ACTION_TO_STRING = {
{InputAction::LEFT, "LEFT"},
{InputAction::RIGHT, "RIGHT"},
{InputAction::JUMP, "JUMP"},
{InputAction::DOWN, "DOWN"},
{InputAction::PAUSE, "PAUSE"},
{InputAction::EXIT, "EXIT"},
{InputAction::ACCEPT, "ACCEPT"},
@@ -30,6 +31,7 @@ const std::unordered_map<std::string, InputAction> STRING_TO_ACTION = {
{"LEFT", InputAction::LEFT},
{"RIGHT", InputAction::RIGHT},
{"JUMP", InputAction::JUMP},
{"DOWN", InputAction::DOWN},
{"PAUSE", InputAction::PAUSE},
{"EXIT", InputAction::EXIT},
{"ACCEPT", InputAction::ACCEPT},

View File

@@ -11,6 +11,7 @@ enum class InputAction : int { // Acciones de entrada posibles en el juego
LEFT,
RIGHT,
JUMP,
DOWN,
// Inputs de control
PAUSE,

View File

@@ -35,12 +35,8 @@ auto RenderInfo::get() -> RenderInfo* {
return RenderInfo::render_info;
}
// Constructor: en DEBUG se activa inmediatamente (notifica a Notifier del offset)
RenderInfo::RenderInfo() {
#ifdef _DEBUG
toggle();
#endif
}
// Constructor: arranca oculto; quien quiera activarlo debe llamar a toggle()
RenderInfo::RenderInfo() = default;
// Actualiza la animación de entrada/salida del overlay
void RenderInfo::update(float delta_time) {

View File

@@ -135,6 +135,7 @@ void Debug::loadFromFile() {
spawn_settings_.flip = Defaults::Game::Player::SPAWN_FLIP;
initial_scene_ = SceneManager::Scene::GAME;
lazy_loading_ = false;
render_info_enabled_ = false;
std::ifstream file(debug_file_path_);
if (!file.good()) {
@@ -145,8 +146,13 @@ void Debug::loadFromFile() {
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
file.close();
bool needs_save = false;
try {
auto yaml = fkyaml::node::deserialize(content);
// Detecta si falta alguna clave esperada para regenerar el fichero con los nuevos defaults
for (const char* key : {"room", "spawn_x", "spawn_y", "spawn_flip", "initial_scene", "lazy_loading", "render_info"}) {
if (!yaml.contains(key)) { needs_save = true; break; }
}
if (yaml.contains("room")) {
spawn_settings_.room = yaml["room"].get_value<std::string>();
}
@@ -166,6 +172,9 @@ void Debug::loadFromFile() {
if (yaml.contains("lazy_loading")) {
lazy_loading_ = yaml["lazy_loading"].get_value<bool>();
}
if (yaml.contains("render_info")) {
render_info_enabled_ = yaml["render_info"].get_value<bool>();
}
} catch (...) {
// YAML inválido: resetear a defaults y sobreescribir
spawn_settings_.room = Defaults::Game::Room::INITIAL;
@@ -174,8 +183,11 @@ void Debug::loadFromFile() {
spawn_settings_.flip = Defaults::Game::Player::SPAWN_FLIP;
initial_scene_ = SceneManager::Scene::GAME;
lazy_loading_ = false;
render_info_enabled_ = false;
saveToFile();
return;
}
if (needs_save) { saveToFile(); }
}
// Guarda la configuración de debug en debug.yaml
@@ -190,6 +202,7 @@ void Debug::saveToFile() const {
file << "spawn_flip: " << ((spawn_settings_.flip == Flip::RIGHT) ? "right" : "left") << "\n";
file << "initial_scene: " << sceneToString(initial_scene_) << "\n";
file << "lazy_loading: " << (lazy_loading_ ? "true" : "false") << " # carga perezosa de recursos (dev)\n";
file << "render_info: " << (render_info_enabled_ ? "true" : "false") << " # overlay de info activo al arrancar\n";
}
#endif // _DEBUG

View File

@@ -48,6 +48,7 @@ class Debug {
[[nodiscard]] auto getInitialScene() const -> SceneManager::Scene { return initial_scene_; } // Obtiene la escena inicial de debug
void setInitialScene(SceneManager::Scene s) { initial_scene_ = s; } // Establece la escena inicial de debug
[[nodiscard]] auto getLazyLoading() const -> bool { return lazy_loading_; } // Indica si el modo lazy de recursos está activo
[[nodiscard]] auto getRenderInfoEnabled() const -> bool { return render_info_enabled_; } // Indica si el overlay RenderInfo arranca activo
private:
static Debug* debug; // [SINGLETON] Objeto privado
@@ -66,6 +67,7 @@ class Debug {
SpawnSettings spawn_settings_; // Configuración de spawn para debug
SceneManager::Scene initial_scene_ = SceneManager::Scene::GAME; // Escena inicial en debug
bool lazy_loading_ = false; // Carga lazy de recursos (dev)
bool render_info_enabled_ = false; // Overlay de RenderInfo activo al arrancar
};
#endif // _DEBUG

View File

@@ -177,6 +177,9 @@ Director::Director() {
#endif
Notifier::init("", "8bithud");
RenderInfo::init();
#ifdef _DEBUG
if (Debug::get()->getRenderInfoEnabled()) { RenderInfo::get()->toggle(); }
#endif
Console::init("8bithud");
Screen::get()->setNotificationsEnabled(true);

View File

@@ -14,9 +14,8 @@
#include "utils/utils.hpp" // Para stringToColor, toLower
// Constructor
EditorStatusBar::EditorStatusBar(std::string room_number, std::string room_name)
: room_number_(std::move(room_number)),
room_name_(std::move(room_name)) {
EditorStatusBar::EditorStatusBar(std::string room_number)
: room_number_(std::move(room_number)) {
const float SURFACE_WIDTH = Options::game.width;
constexpr float SURFACE_HEIGHT = 6.0F * Tile::SIZE; // 48 pixels, igual que el scoreboard
@@ -56,8 +55,8 @@ void EditorStatusBar::fillTexture() {
const Uint8 VALUE_COLOR = stringToColor("white");
const Uint8 DETAIL_COLOR = stringToColor("bright_yellow");
// Línea 1: Nombre de la habitación
text->writeColored(LEFT_X, LINE1_Y, toLower(room_number_ + " " + room_name_), LABEL_COLOR);
// Línea 1: Número de la habitación
text->writeColored(LEFT_X, LINE1_Y, toLower(room_number_), LABEL_COLOR);
// Línea 2: Propiedades de room o info de enemigo
if (!line2_.empty()) {

View File

@@ -11,7 +11,7 @@ class Surface;
class EditorStatusBar {
public:
EditorStatusBar(std::string room_number, std::string room_name);
explicit EditorStatusBar(std::string room_number);
~EditorStatusBar() = default;
void render();
@@ -40,7 +40,6 @@ class EditorStatusBar {
// Variables
std::string room_number_; // Número de la habitación
std::string room_name_; // Nombre de la habitación
int mouse_tile_x_{0}; // Coordenada X del ratón en tiles
int mouse_tile_y_{0}; // Coordenada Y del ratón en tiles
std::string line2_; // Contenido de la línea 2

View File

@@ -219,7 +219,7 @@ void MapEditor::enter(std::shared_ptr<Room> room, std::shared_ptr<Player> player
room_->resetEnemyPositions(room_data_.enemies);
// Crear la barra de estado
statusbar_ = std::make_unique<EditorStatusBar>(room_->getNumber(), room_->getName());
statusbar_ = std::make_unique<EditorStatusBar>(room_->getNumber());
// Resetear estado
drag_ = {};
@@ -1320,7 +1320,6 @@ auto MapEditor::createNewRoom(const std::string& direction) -> std::string { //
// Crear Room::Data por defecto con conexión recíproca
Room::Data new_room;
new_room.number = std::string(name_buf).substr(0, std::string(name_buf).find('.'));
new_room.name = "NO_NAME";
new_room.bg_color = "black";
new_room.border_color = "magenta";
new_room.tile_set_file = "standard.gif";

View File

@@ -37,25 +37,12 @@ auto RoomSaver::conveyorBeltToString(int direction) -> std::string {
// Genera el YAML completo como texto con formato compacto
auto RoomSaver::buildYAML(const fkyaml::node& original_yaml, const Room::Data& room_data) -> std::string { // NOLINT(readability-function-cognitive-complexity)
(void)original_yaml; // Ya no se usa; mantenido para compatibilidad con la firma de saveYAML
std::ostringstream out;
// --- Cabecera: nombre como comentario ---
out << "# " << room_data.name << "\n";
// --- Sección room ---
out << "room:\n";
// Escribir todos los campos name_* del YAML original (preserva name_ca, name_en, etc.)
if (original_yaml.contains("room")) {
const auto& room_node = original_yaml["room"];
for (auto it = room_node.begin(); it != room_node.end(); ++it) {
const auto KEY = it.key().get_value<std::string>();
if (KEY.substr(0, 5) == "name_") {
out << " " << KEY << ": \"" << it.value().get_value<std::string>() << "\"\n";
}
}
}
out << " bgColor: " << room_data.bg_color << "\n";
out << " border: " << room_data.border_color << "\n";
out << " tileSetFile: " << room_data.tile_set_file << "\n";

View File

@@ -65,6 +65,7 @@ void Player::handleInput() {
}
wanna_jump_ = Input::get()->checkAction(InputAction::JUMP);
wanna_down_ = Input::get()->checkAction(InputAction::DOWN);
}
// La lógica de movimiento está distribuida en move
@@ -189,6 +190,13 @@ void Player::updateOnSlope(float delta_time) {
startJump();
return;
}
// DOWN: dejarse caer atravesando la rampa
if (wanna_down_) {
y_ += 1.0F;
vy_ = 0.0F;
transitionToState(State::ON_AIR);
return;
}
handleShouldFall();
}

View File

@@ -103,6 +103,7 @@ class Player {
Direction wanna_go_ = Direction::NONE;
bool wanna_jump_ = false;
bool wanna_down_ = false;
// --- Variables de estado ---
State state_ = State::ON_GROUND; // Estado en el que se encuentra el jugador. Util apara saber si está saltando o cayendo

View File

@@ -8,8 +8,8 @@
#include "utils/utils.hpp" // Para checkCollision
// Constructor
ItemManager::ItemManager(std::string room_name, std::shared_ptr<Scoreboard::Data> scoreboard_data)
: room_name_(std::move(room_name)),
ItemManager::ItemManager(std::string room_id, std::shared_ptr<Scoreboard::Data> scoreboard_data)
: room_id_(std::move(room_id)),
data_(std::move(scoreboard_data)) {
}
@@ -49,7 +49,7 @@ auto ItemManager::checkCollision(SDL_FRect& rect) -> bool { // NOLINT(readabili
for (int i = 0; i < static_cast<int>(items_.size()); ++i) {
if (::checkCollision(rect, items_.at(i)->getCollider())) {
// Registra el item como recogido
ItemTracker::get()->addItem(room_name_, items_.at(i)->getPos());
ItemTracker::get()->addItem(room_id_, items_.at(i)->getPos());
// Elimina el item de la colección
items_.erase(items_.begin() + i);

View File

@@ -23,10 +23,10 @@ class ItemManager {
public:
/**
* @brief Constructor
* @param room_name Nombre de la habitación (para ItemTracker)
* @param room_id Identificador de la habitación (para ItemTracker)
* @param scoreboard_data Puntero compartido a los datos del scoreboard
*/
ItemManager(std::string room_name, std::shared_ptr<Scoreboard::Data> scoreboard_data);
ItemManager(std::string room_id, std::shared_ptr<Scoreboard::Data> scoreboard_data);
~ItemManager() = default;
// Prohibir copia y movimiento para evitar duplicación accidental
@@ -68,6 +68,6 @@ class ItemManager {
private:
std::vector<std::shared_ptr<Item>> items_; // Colección de items
std::string room_name_; // Nombre de la habitación
std::string room_id_; // Identificador de la habitación
std::shared_ptr<Scoreboard::Data> data_; // Datos del scoreboard
};

View File

@@ -23,7 +23,7 @@ Room::Room(const std::string& room_path, std::shared_ptr<Scoreboard::Data> data)
// Crea los managers de enemigos e items
enemy_manager_ = std::make_unique<EnemyManager>();
item_manager_ = std::make_unique<ItemManager>(room->name, data_);
item_manager_ = std::make_unique<ItemManager>(room->number, data_);
initializeRoom(*room);
openTheJail(); // Abre la Jail si se da el caso
@@ -44,7 +44,6 @@ Room::~Room() = default;
void Room::initializeRoom(const Data& room) {
// Asignar valores a las variables miembro
number_ = room.number;
name_ = room.name;
bg_color_ = room.bg_color;
border_color_ = room.border_color;
item_color1_ = room.item_color1.empty() ? "yellow" : room.item_color1;
@@ -69,7 +68,7 @@ void Room::initializeRoom(const Data& room) {
for (const auto& item : room.items) {
const SDL_FPoint ITEM_POS = {item.x, item.y};
if (!ItemTracker::get()->hasBeenPicked(room.name, ITEM_POS)) {
if (!ItemTracker::get()->hasBeenPicked(room.number, ITEM_POS)) {
// Crear una copia local de los datos del item
Item::Data item_copy = item;
item_copy.color1 = stringToColor(item_color1_);

View File

@@ -40,7 +40,6 @@ class Room {
struct Data {
std::string number; // Numero de la habitación
std::string name; // Nombre de la habitación
std::string bg_color; // Color de fondo de la habitación
std::string border_color; // Color del borde de la pantalla
std::string item_color1; // Color 1 para los items de la habitación
@@ -62,7 +61,6 @@ class Room {
// --- Funciones ---
[[nodiscard]] auto getNumber() const -> const std::string& { return number_; } // Devuelve el numero de la habitación
[[nodiscard]] auto getName() const -> const std::string& { return name_; } // Devuelve el nombre de la habitación
[[nodiscard]] auto getBGColor() const -> Uint8 { return stringToColor(bg_color_); } // Devuelve el color de la habitación
[[nodiscard]] auto getBorderColor() const -> Uint8 { return stringToColor(border_color_); } // Devuelve el color del borde
void renderMap(); // Dibuja el mapa en pantalla
@@ -122,7 +120,6 @@ class Room {
// --- Variables ---
std::string number_; // Numero de la habitación
std::string name_; // Nombre de la habitación
std::string bg_color_; // Color de fondo de la habitación
std::string border_color_; // Color del borde de la pantalla
std::string item_color1_; // Color 1 para los items de la habitación

View File

@@ -5,7 +5,6 @@
#include "core/resources/resource_helper.hpp" // Para Resource::Helper
#include "external/fkyaml_node.hpp" // Para fkyaml::node
#include "game/options.hpp" // Para Options::language
#include "utils/defines.hpp" // Para Tile::SIZE
#include "utils/utils.hpp" // Para stringToColor
@@ -71,12 +70,6 @@ void RoomLoader::parseRoomConfig(const fkyaml::node& yaml, Room::Data& room, con
room.number = file_name.substr(0, file_name.find_last_of('.'));
// Basic properties
const std::string LANG_KEY = "name_" + Options::language;
if (room_node.contains(LANG_KEY)) {
room.name = room_node[LANG_KEY].get_value<std::string>();
} else if (room_node.contains("name")) {
room.name = room_node["name"].get_value<std::string>();
}
if (room_node.contains("bgColor")) {
room.bg_color = readColorNode(room_node["bgColor"]);
}

View File

@@ -43,6 +43,7 @@ Scoreboard::Scoreboard(std::shared_ptr<Data> data)
// Pinta el objeto en pantalla
void Scoreboard::render() {
return;
surface_->render(nullptr, &surface_dest_);
}

View File

@@ -69,7 +69,6 @@ Game::Game(Mode mode)
initPlayer(spawn_data_, room_);
total_items_ = getTotalItems();
createRoomNameTexture();
game_backbuffer_surface_ = std::make_shared<Surface>(Options::game.width, Options::game.height);
changeRoom(current_room_);
@@ -340,7 +339,6 @@ void Game::updatePlaying(float delta_time) {
checkPlayerAndEnemies();
checkIfPlayerIsAlive();
checkEndGame();
checkRestoringJail(delta_time);
checkSomeCheevos();
break;
@@ -466,7 +464,6 @@ void Game::renderPlaying() {
// Si el editor está activo, delegar el renderizado de entidades y statusbar
if (MapEditor::get()->isActive()) {
MapEditor::get()->render();
renderRoomName();
Screen::get()->render();
return;
}
@@ -478,7 +475,6 @@ void Game::renderPlaying() {
if (mode_ == Mode::GAME) {
player_->render();
}
renderRoomName();
scoreboard_->render();
#ifdef _DEBUG
@@ -520,7 +516,6 @@ void Game::renderFadeToEnding() {
room_->renderEnemies();
room_->renderItems();
player_->render(); // Player congelado pero visible
renderRoomName();
scoreboard_->render();
// 4. Restaurar renderer original
@@ -715,12 +710,6 @@ void Game::handleDebugMouseDrag(float delta_time) {
}
#endif
// Escribe el nombre de la pantalla
void Game::renderRoomName() {
// Dibuja la textura con el nombre de la habitación
room_name_surface_->render(nullptr, &room_name_rect_);
}
// Cambia de habitación
auto Game::changeRoom(const std::string& room_path) -> bool {
// En las habitaciones los limites tienen la cadena del fichero o un 0 en caso de no limitar con nada
@@ -733,9 +722,6 @@ auto Game::changeRoom(const std::string& room_path) -> bool {
// Crea un objeto habitación nuevo a partir del fichero
room_ = std::make_shared<Room>(room_path, scoreboard_data_);
// Pone el nombre de la habitación en la textura
fillRoomNameTexture();
// Pone el color del marcador en función del color del borde de la habitación
setScoreBoardColor();
@@ -880,48 +866,6 @@ void Game::togglePause() {
scoreboard_->setPaused(paused_);
}
// Da vidas al jugador cuando está en la Jail
void Game::checkRestoringJail(float delta_time) {
if (room_->getName() != "THE JAIL" || scoreboard_data_->lives == 9) {
jail_restore_time_ = 0.0F; // Reset timer cuando no está en la Jail
return;
}
if (!paused_) {
jail_restore_time_ += delta_time;
}
// Incrementa el numero de vidas
if (jail_restore_time_ >= JAIL_RESTORE_INTERVAL) {
jail_restore_time_ -= JAIL_RESTORE_INTERVAL; // Mantiene el excedente para precisión
scoreboard_data_->lives++;
Audio::get()->playSound("death.wav", Audio::Group::GAME);
// Invalida el logro de completar el juego sin entrar a la jail
const bool HAVE_THE_ITEMS = scoreboard_data_->items >= int(total_items_ * 0.9F);
if (!HAVE_THE_ITEMS) {
Cheevos::get()->setUnobtainable(9);
}
}
}
// Crea la textura con el nombre de la habitación
void Game::fillRoomNameTexture() { // NOLINT(readability-convert-member-functions-to-static)
// Pone la textura como destino de renderizado
auto previuos_renderer = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(room_name_surface_);
// Rellena la textura de color
room_name_surface_->clear(stringToColor("white"));
// Escribe el texto en la textura
auto text = Resource::Cache::get()->getText("smb2");
text->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, GameCanvas::CENTER_X, text->getCharacterSize() / 2, room_->getName(), 1, room_->getBGColor());
// Deja el renderizador por defecto
Screen::get()->setRendererSurface(previuos_renderer);
}
// Comprueba algunos logros
void Game::checkSomeCheevos() { // NOLINT(readability-convert-member-functions-to-static)
auto* cheevos = Cheevos::get();
@@ -989,15 +933,6 @@ void Game::initPlayer(const Player::SpawnData& spawn_point, std::shared_ptr<Room
if (IGNORE_INPUT) { player_->setIgnoreInput(true); }
}
// Crea la textura para poner el nombre de la habitación
void Game::createRoomNameTexture() {
auto text = Resource::Cache::get()->getText("smb2");
room_name_surface_ = std::make_shared<Surface>(Options::game.width, text->getCharacterSize() * 2);
// Establece el destino de la textura
room_name_rect_ = {.x = 0.0F, .y = PlayArea::HEIGHT, .w = Options::game.width, .h = text->getCharacterSize() * 2.0F};
}
// Hace sonar la música
void Game::keepMusicPlaying() {
const std::string MUSIC_PATH = mode_ == Mode::GAME ? "574070_KUVO_Farewell_to_school.ogg" : "574071_EA_DTV.ogg";

View File

@@ -42,7 +42,6 @@ class Game {
static constexpr float BLACK_SCREEN_DURATION = 0.30F; // Duración de la pantalla negra en segundos (20 frames a 66.67fps)
static constexpr float GAME_OVER_THRESHOLD = 0.255F; // Tiempo antes del game over en segundos (17 frames a 66.67fps)
static constexpr float DEMO_ROOM_DURATION = 6.0F; // Duración de cada habitación en modo demo en segundos (400 frames)
static constexpr float JAIL_RESTORE_INTERVAL = 1.5F; // Intervalo de restauración de vidas en la Jail en segundos (100 frames)
static constexpr float FADE_STEP_INTERVAL = 0.05F; // Intervalo entre pasos de fade en segundos
static constexpr float POST_FADE_DELAY = 2.0F; // Duración de la pantalla negra después del fade
@@ -57,7 +56,6 @@ class Game {
void update(); // Actualiza el juego, las variables, comprueba la entrada, etc.
void render(); // Pinta los objetos en pantalla
void handleEvents(); // Comprueba los eventos de la cola
void renderRoomName(); // Escribe el nombre de la pantalla
void transitionToState(State new_state); // Cambia al estado especificado y resetea los timers
void updatePlaying(float delta_time); // Actualiza el juego en estado PLAYING
void updateBlackScreen(float delta_time); // Actualiza el juego en estado BLACK_SCREEN
@@ -80,12 +78,9 @@ class Game {
auto checkEndGame() -> bool; // Comprueba si ha finalizado el juego
static auto getTotalItems() -> int; // Obtiene la cantidad total de items que hay en el mapeado del juego
void togglePause(); // Pone el juego en pausa
void checkRestoringJail(float delta_time); // Da vidas al jugador cuando está en la Jail
void fillRoomNameTexture(); // Pone el nombre de la habitación en la textura
void checkSomeCheevos(); // Comprueba algunos logros
void checkEndGameCheevos(); // Comprueba los logros de completar el juego
void initPlayer(const Player::SpawnData& spawn_point, std::shared_ptr<Room> room); // Inicializa al jugador
void createRoomNameTexture(); // Crea la textura para poner el nombre de la habitación
void keepMusicPlaying(); // Hace sonar la música
void demoInit(); // DEMO MODE: Inicializa las variables para el modo demo
void demoCheckRoomChange(float delta_time); // DEMO MODE: Comprueba si se ha de cambiar de habitación
@@ -102,7 +97,6 @@ class Game {
std::shared_ptr<RoomTracker> room_tracker_; // Lleva el control de las habitaciones visitadas
std::shared_ptr<Room> room_; // Objeto encargado de gestionar cada habitación del juego
std::shared_ptr<Player> player_; // Objeto con el jugador
std::shared_ptr<Surface> room_name_surface_; // Textura para escribir el nombre de la habitación
std::shared_ptr<Surface> game_backbuffer_surface_; // Backbuffer para efectos de fade
// Variables de estado del juego
@@ -119,9 +113,6 @@ class Game {
// Variables de demo mode
DemoData demo_; // Variables para el modo demo
// Variables de efectos visuales
SDL_FRect room_name_rect_; // Rectangulo donde pintar la textura con el nombre de la habitación
float jail_restore_time_{0.0F}; // Tiempo acumulado para restauración de vidas en la Jail
#ifdef _DEBUG
// Variables de debug para arrastre con ratón

View File

@@ -361,6 +361,12 @@ auto stringToColor(const std::string& str) -> Uint8 {
{"white", 14},
{"bright_white", 15},
// Índices extendidos (paletas de 32 colores): nombres genéricos COLOR_N
{"color_16", 16}, {"color_17", 17}, {"color_18", 18}, {"color_19", 19},
{"color_20", 20}, {"color_21", 21}, {"color_22", 22}, {"color_23", 23},
{"color_24", 24}, {"color_25", 25}, {"color_26", 26}, {"color_27", 27},
{"color_28", 28}, {"color_29", 29}, {"color_30", 30}, {"color_31", 31},
{"transparent", 255}};
// Busca el color en el mapa
@@ -393,6 +399,7 @@ auto colorToString(Uint8 index) -> std::string {
"yellow", "bright_yellow",
"white", "bright_white"};
if (index < NAMES.size()) { return NAMES[index]; }
if (index < 32) { return "color_" + std::to_string(index); }
if (index == 255) { return "transparent"; }
return std::to_string(index);
}