This commit is contained in:
2026-04-11 16:25:56 +02:00
parent 5b2f986d32
commit bb38600aac
57 changed files with 371 additions and 347 deletions

View File

@@ -1,5 +1,6 @@
#pragma once
#include <cstdint> // Para int8_t, uint8_t
#include <string> // Para string
#include <utility> // Para move
@@ -7,13 +8,13 @@
class Audio {
public:
// --- Enums ---
enum class Group : int {
enum class Group : std::int8_t {
ALL = -1, // Todos los grupos
GAME = 0, // Sonidos del juego
INTERFACE = 1 // Sonidos de la interfaz
};
enum class MusicState {
enum class MusicState : std::uint8_t {
PLAYING, // Reproduciendo música
PAUSED, // Música pausada
STOPPED, // Música detenida

View File

@@ -407,8 +407,9 @@ auto Input::handleEvent(const SDL_Event& event) -> std::string { // NOLINT(read
return addGamepad(event.gdevice.which);
case SDL_EVENT_GAMEPAD_REMOVED:
return removeGamepad(event.gdevice.which);
default:
return {};
}
return {};
}
auto Input::addGamepad(int device_index) -> std::string { // NOLINT(readability-convert-member-functions-to-static)

View File

@@ -2,11 +2,12 @@
#include <SDL3/SDL.h>
#include <cstdint>
#include <string>
#include <unordered_map>
// --- Enums ---
enum class InputAction : int { // Acciones de entrada posibles en el juego
enum class InputAction : std::uint8_t { // Acciones de entrada posibles en el juego
// Inputs de movimiento
LEFT,
RIGHT,

View File

@@ -224,8 +224,8 @@ namespace GIF {
if ((screen_descriptor.fields & 0x80) != 0) {
int global_color_table_size = 1 << ((screen_descriptor.fields & 0x07) + 1);
global_color_table.resize(global_color_table_size);
std::memcpy(global_color_table.data(), buffer, 3 * global_color_table_size);
buffer += 3 * global_color_table_size;
std::memcpy(global_color_table.data(), buffer, static_cast<size_t>(3) * static_cast<size_t>(global_color_table_size));
buffer += static_cast<ptrdiff_t>(3) * global_color_table_size;
}
// Supongamos que 'buffer' es el puntero actual y TRAILER es 0x3B

View File

@@ -69,7 +69,7 @@ namespace {
if (SZ == 0) { return palette; }
// Matriz de coste NxM (ampliada a SZxSZ con ceros para hacerla cuadrada)
std::vector<int> cost(SZ * SZ, 0);
std::vector<int> cost(static_cast<size_t>(SZ) * static_cast<size_t>(SZ), 0);
for (int i = 0; i < N; ++i) {
for (int j = 0; j < M; ++j) {
cost[(i * SZ) + j] = rgbDistanceSq(palette[i], reference[j]);

View File

@@ -3,6 +3,7 @@
#include <SDL3/SDL.h>
#include <array>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
@@ -14,7 +15,7 @@ using Palette = std::array<Uint32, 256>;
class Surface;
// Modo de ordenación de paletas
enum class PaletteSortMode : int {
enum class PaletteSortMode : std::uint8_t {
ORIGINAL = 0, // Paleta tal cual viene del fichero
OPTIMAL = 1, // Asignación óptima a la paleta por defecto (Hungarian algorithm)
REFERENCE = 2, // Asignación greedy a la paleta por defecto

View File

@@ -1,5 +1,6 @@
#pragma once
#include <cstdint>
#include <memory> // Para shared_ptr
#include <vector> // Para vector
@@ -10,7 +11,7 @@ class Surface;
class PixelReveal {
public:
// Modo de revelado: aleatorio por fila o en orden de bisección (dithering ordenado 1D)
enum class RevealMode { RANDOM,
enum class RevealMode : std::uint8_t { RANDOM,
ORDERED };
// Constructor

View File

@@ -1,5 +1,7 @@
#pragma once
#include <cstdint>
class RenderInfo {
public:
// Singleton
@@ -20,7 +22,7 @@ class RenderInfo {
static constexpr float SLIDE_SPEED = 120.0F;
private:
enum class Status { HIDDEN,
enum class Status : std::uint8_t { HIDDEN,
RISING,
ACTIVE,
VANISHING };

View File

@@ -292,12 +292,12 @@ void Screen::adjustWindowSize() {
// Reservamos memoria una sola vez.
// Si el buffer es más pequeño que la superficie, crash asegurado.
border_pixel_buffer_.resize(static_cast<size_t>(window_width_ * window_height_));
game_pixel_buffer_.resize(static_cast<size_t>(Options::game.width * Options::game.height));
border_pixel_buffer_.resize(static_cast<size_t>(window_width_) * static_cast<size_t>(window_height_));
game_pixel_buffer_.resize(static_cast<size_t>(Options::game.width) * static_cast<size_t>(Options::game.height));
// border_pixel_buffer_ es el buffer que se sube a la GPU (tamaño total ventana).
if (Options::video.border.enabled) {
border_pixel_buffer_.resize(static_cast<size_t>(window_width_ * window_height_));
border_pixel_buffer_.resize(static_cast<size_t>(window_width_) * static_cast<size_t>(window_height_));
}
// Lógica de centrado y redimensionado de ventana SDL
@@ -397,15 +397,17 @@ void Screen::textureToRenderer() {
// Rellena solo el marco con el color cacheado — sin lookups de paleta.
// El área central (juego) se deja sin tocar; el overlay la sobreescribe igualmente.
const auto BORDER_W_SZ = static_cast<size_t>(BORDER_W);
const auto GAME_W_SZ = static_cast<size_t>(GAME_W);
// Franjas superior e inferior (ancho completo)
std::fill_n(border_pixel_buffer_.data(), OFF_Y * BORDER_W, border_argb_color_);
std::fill_n(&border_pixel_buffer_[(OFF_Y + GAME_H) * BORDER_W],
(BORDER_H - OFF_Y - GAME_H) * BORDER_W,
std::fill_n(border_pixel_buffer_.data(), static_cast<size_t>(OFF_Y) * BORDER_W_SZ, border_argb_color_);
std::fill_n(&border_pixel_buffer_[(static_cast<size_t>(OFF_Y) + GAME_H) * BORDER_W_SZ],
(static_cast<size_t>(BORDER_H - OFF_Y - GAME_H)) * BORDER_W_SZ,
border_argb_color_);
// Columnas laterales en las filas del área de juego
for (int y = OFF_Y; y < OFF_Y + GAME_H; ++y) {
std::fill_n(&border_pixel_buffer_[y * BORDER_W], OFF_X, border_argb_color_);
std::fill_n(&border_pixel_buffer_[(y * BORDER_W) + OFF_X + GAME_W],
std::fill_n(&border_pixel_buffer_[static_cast<size_t>(y) * BORDER_W_SZ], OFF_X, border_argb_color_);
std::fill_n(&border_pixel_buffer_[(static_cast<size_t>(y) * BORDER_W_SZ) + OFF_X + GAME_W],
BORDER_W - OFF_X - GAME_W,
border_argb_color_);
}
@@ -413,9 +415,9 @@ void Screen::textureToRenderer() {
// Overlay del juego sobre el centro del buffer (ambos paths)
game_surface_->toARGBBuffer(game_pixel_buffer_.data());
for (int y = 0; y < GAME_H; ++y) {
const Uint32* src = &game_pixel_buffer_[y * GAME_W];
Uint32* dst = &border_pixel_buffer_[((OFF_Y + y) * BORDER_W) + OFF_X];
std::memcpy(dst, src, GAME_W * sizeof(Uint32));
const Uint32* src = &game_pixel_buffer_[static_cast<size_t>(y) * GAME_W_SZ];
Uint32* dst = &border_pixel_buffer_[((static_cast<size_t>(OFF_Y) + y) * BORDER_W_SZ) + OFF_X];
std::memcpy(dst, src, GAME_W_SZ * sizeof(Uint32));
}
shader_backend_->uploadPixels(border_pixel_buffer_.data(), BORDER_W, BORDER_H);

View File

@@ -4,6 +4,7 @@
#include <SDL3/SDL_pixels.h> // Para Uint32
#include <cstddef> // Para size_t
#include <cstdint> // Para uint8_t
#include <memory> // Para shared_ptr, __shared_ptr_access
#include <string> // Para string
#include <utility> // Para std::pair
@@ -18,7 +19,7 @@ class Text;
class Screen {
public:
// Tipos de filtro
enum class Filter : Uint32 {
enum class Filter : std::uint8_t {
NEAREST = 0,
LINEAR = 1,
};

View File

@@ -762,7 +762,7 @@ namespace Rendering {
}
// Copia directa — el upscale lo hace la GPU en el primer render pass
std::memcpy(mapped, pixels, static_cast<size_t>(width * height * 4));
std::memcpy(mapped, pixels, static_cast<size_t>(width) * static_cast<size_t>(height) * 4U);
SDL_UnmapGPUTransferBuffer(device_, upload_buffer_);
}

View File

@@ -2,13 +2,14 @@
#include <SDL3/SDL.h>
#include <cstdint>
#include <string>
#include <utility>
namespace Rendering {
/** @brief Identificador del shader de post-procesado activo */
enum class ShaderType { POSTFX,
enum class ShaderType : std::uint8_t { POSTFX,
CRTPI };
/**

View File

@@ -61,7 +61,7 @@ static auto parseAnimations(const fkyaml::node& yaml, float frame_width, float f
animation.speeds.push_back(s.get_value<float>());
}
} else {
float spd = speed_node.get_value<float>();
auto spd = speed_node.get_value<float>();
if (spd > 0.0F) {
animation.speeds.assign(animation.frames.size(), spd);
}

View File

@@ -2,6 +2,7 @@
#include <SDL3/SDL.h>
#include <cstdint>
#include <memory> // Para shared_ptr
#include "core/rendering/sprite/animated_sprite.hpp" // Para SurfaceAnimatedSprite
@@ -9,7 +10,7 @@
class Surface;
// Direcció de la dissolució
enum class DissolveDirection { NONE,
enum class DissolveDirection : std::uint8_t { NONE,
DOWN,
UP };
@@ -41,7 +42,7 @@ class DissolveSprite : public AnimatedSprite {
void setColorReplace(Uint8 source, Uint8 target);
private:
enum class TransitionMode { NONE,
enum class TransitionMode : std::uint8_t { NONE,
DISSOLVING,
GENERATING };

View File

@@ -177,10 +177,10 @@ void Surface::fillRect(const SDL_FRect* rect, Uint8 color) { // NOLINT(readabil
// Rellenar fila a fila con memset (memoria contigua por fila)
Uint8* data_ptr = surface_data_->data.get();
const int SURF_WIDTH = static_cast<int>(surface_data_->width);
const int ROW_WIDTH = static_cast<int>(x_end) - static_cast<int>(x_start);
const auto SURF_WIDTH = static_cast<size_t>(surface_data_->width);
const auto ROW_WIDTH = static_cast<size_t>(static_cast<int>(x_end) - static_cast<int>(x_start));
for (int y = static_cast<int>(y_start); y < static_cast<int>(y_end); ++y) {
std::memset(data_ptr + (y * SURF_WIDTH) + static_cast<int>(x_start), color, ROW_WIDTH);
std::memset(data_ptr + (static_cast<size_t>(y) * SURF_WIDTH) + static_cast<size_t>(x_start), color, ROW_WIDTH);
}
}
@@ -194,10 +194,10 @@ void Surface::drawRectBorder(const SDL_FRect* rect, Uint8 color) { // NOLINT(re
// Dibujar bordes horizontales con memset (líneas contiguas en memoria)
Uint8* data_ptr = surface_data_->data.get();
const int SURF_WIDTH = static_cast<int>(surface_data_->width);
const int ROW_WIDTH = static_cast<int>(x_end) - static_cast<int>(x_start);
std::memset(data_ptr + (static_cast<int>(y_start) * SURF_WIDTH) + static_cast<int>(x_start), color, ROW_WIDTH);
std::memset(data_ptr + ((static_cast<int>(y_end) - 1) * SURF_WIDTH) + static_cast<int>(x_start), color, ROW_WIDTH);
const auto SURF_WIDTH = static_cast<size_t>(surface_data_->width);
const auto ROW_WIDTH = static_cast<size_t>(static_cast<int>(x_end) - static_cast<int>(x_start));
std::memset(data_ptr + (static_cast<size_t>(y_start) * SURF_WIDTH) + static_cast<size_t>(x_start), color, ROW_WIDTH);
std::memset(data_ptr + ((static_cast<size_t>(y_end) - 1) * SURF_WIDTH) + static_cast<size_t>(x_start), color, ROW_WIDTH);
// Dibujar bordes verticales
for (int y = y_start; y < y_end; ++y) {
@@ -295,8 +295,8 @@ void Surface::render(int x, int y, SDL_FRect* src_rect, SDL_FlipMode flip) { //
float h = (src_rect != nullptr) ? src_rect->h : surface_data_->height;
// Guardar dimensiones originales antes del clipping (necesarias para flip)
float orig_w = (src_rect != nullptr) ? src_rect->w : static_cast<float>(surface_data_->width);
float orig_h = (src_rect != nullptr) ? src_rect->h : static_cast<float>(surface_data_->height);
float orig_w = (src_rect != nullptr) ? src_rect->w : surface_data_->width;
float orig_h = (src_rect != nullptr) ? src_rect->h : surface_data_->height;
// Limitar la región para evitar accesos fuera de rango en origen
w = std::min(w, surface_data_->width - sx);
@@ -467,7 +467,7 @@ static auto computeFadeDensity(int screen_y, int fade_h, int canvas_height) -> f
}
// Render amb dissolució als cantons superior/inferior (hash 2D, sense parpelleig)
void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, SDL_FRect* src_rect) const {
void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, const SDL_FRect* src_rect) const {
// Aplicar render offset
x += Screen::get()->getRenderOffsetX();
y += Screen::get()->getRenderOffsetY();
@@ -508,7 +508,7 @@ void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height
}
// Idem però reemplaçant un color índex
void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color, SDL_FRect* src_rect) const {
void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color, const SDL_FRect* src_rect) const {
// Aplicar render offset
x += Screen::get()->getRenderOffsetX();
y += Screen::get()->getRenderOffsetY();
@@ -599,8 +599,8 @@ void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture) { //
const int WIDTH = surface_data_->width;
const int HEIGHT = surface_data_->height;
for (int y = 0; y < HEIGHT; ++y) {
const Uint8* src_row = src + (y * WIDTH);
Uint32* dst_row = pixels + (y * row_stride);
const Uint8* src_row = src + (static_cast<size_t>(y) * static_cast<size_t>(WIDTH));
Uint32* dst_row = pixels + (static_cast<size_t>(y) * static_cast<size_t>(row_stride));
for (int x = 0; x < WIDTH; ++x) {
dst_row[x] = pal[src_row[x]];
}
@@ -648,8 +648,8 @@ void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FR
const int WIDTH = surface_data_->width;
const int HEIGHT = surface_data_->height;
for (int y = 0; y < HEIGHT; ++y) {
const Uint8* src_row = src + (y * WIDTH);
Uint32* dst_row = pixels + (y * row_stride);
const Uint8* src_row = src + (static_cast<size_t>(y) * static_cast<size_t>(WIDTH));
Uint32* dst_row = pixels + (static_cast<size_t>(y) * static_cast<size_t>(row_stride));
for (int x = 0; x < WIDTH; ++x) {
dst_row[x] = pal[src_row[x]];
}

View File

@@ -85,10 +85,10 @@ class Surface {
void renderWithColorReplace(int x, int y, Uint8 source_color = 0, Uint8 target_color = 0, SDL_FRect* src_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE) const;
// Render amb dissolució als cantons superior/inferior (hash 2D, sense parpelleig)
void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, SDL_FRect* src_rect = nullptr) const;
void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, const SDL_FRect* src_rect = nullptr) const;
// Idem però reemplaçant un color índex (per a sprites sobre fons del mateix color)
void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color, SDL_FRect* src_rect = nullptr) const;
void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color, const SDL_FRect* src_rect = nullptr) const;
// Establece un color en la paleta
void setColor(int index, Uint32 color);

View File

@@ -22,22 +22,19 @@ auto Text::nextCodepoint(const std::string& s, size_t& pos) -> uint32_t { // NO
if (c < 0x80) {
cp = c;
extra = 0;
} else if (c < 0xC0) {
} else if (c < 0xC0 || c >= 0xF8) {
// Byte de continuación suelto o lead byte inválido
pos++;
return 0xFFFD;
} // byte de continuación suelto
else if (c < 0xE0) {
} else if (c < 0xE0) {
cp = c & 0x1F;
extra = 1;
} else if (c < 0xF0) {
cp = c & 0x0F;
extra = 2;
} else if (c < 0xF8) {
} else {
cp = c & 0x07;
extra = 3;
} else {
pos++;
return 0xFFFD;
}
pos++;
@@ -291,7 +288,7 @@ void Text::writeDX(Uint8 flags, int x, int y, const std::string& text, int kerni
if (COLORED) {
writeColored(x, y, text, text_color, kerning, lenght);
} else {
writeColored(x, y, text, text_color, kerning, lenght);
write(x, y, text, kerning, lenght);
}
}

View File

@@ -235,7 +235,7 @@ namespace Resource {
// Obtiene todas las habitaciones
auto Cache::getRooms() -> std::vector<RoomResource>& {
if (loading_mode_ == LoadingMode::LAZY) {
for (auto& r : rooms_) {
for (const auto& r : rooms_) {
if (r.room == nullptr) { loadRoomByName(r.name); }
}
}
@@ -248,6 +248,7 @@ namespace Resource {
std::cerr << "[ ERROR ] Path: " << file_path << '\n';
std::cerr << "[ ERROR ] Reason: " << e.what() << '\n';
std::cerr << "[ ERROR ] Check config/assets.yaml configuration\n";
// cppcheck-suppress rethrowNoCurrentException -- helper [[noreturn]] invocado desde dentro de un catch; cppcheck no puede ver que el rethrow es válido a través de la llamada.
throw;
}
@@ -548,6 +549,8 @@ namespace Resource {
exit(0);
}
break;
default:
break;
}
}
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include <cstdint>
#include <memory> // Para shared_ptr
#include <string> // Para string
#include <utility>
@@ -11,7 +12,7 @@ namespace Resource {
class Cache {
public:
enum class LoadingMode {
enum class LoadingMode : std::uint8_t {
EAGER, // Carga todos los recursos en init() (comportamiento por defecto, producción)
LAZY // Sólo registra nombres; carga cada recurso la primera vez que se pide (desarrollo)
};

View File

@@ -75,11 +75,11 @@ namespace Resource {
}
// Buscar la última entrada con el mismo prefijo de ruta e insertar después
std::string entry = " - " + var_path + "\n";
auto last_pos = content.rfind(var_path.substr(0, var_path.rfind('/')));
if (last_pos != std::string::npos) {
auto end_of_line = content.find('\n', last_pos);
if (end_of_line != std::string::npos) {
std::string entry = " - " + var_path + "\n";
content.insert(end_of_line + 1, entry);
}
}

View File

@@ -12,7 +12,7 @@ namespace Resource {
class List {
public:
// --- Enums ---
enum class Type : int {
enum class Type : std::uint8_t {
DATA, // Datos
BITMAP, // Imágenes
ANIMATION, // Animaciones

View File

@@ -41,9 +41,11 @@ void Debug::render() { // NOLINT(readability-make-member-function-const)
// Watch window: valores persistentes (key: value)
for (const auto& [key, value] : watches_) {
const std::string LINE = key + ": " + value;
text->write(x_, y, LINE);
w = std::max(w, text->length(LINE));
std::string line = key;
line += ": ";
line += value;
text->write(x_, y, line);
w = std::max(w, text->length(line));
y += DESP_Y;
if (y > 192 - CHAR_SIZE) {
y = y_;

View File

@@ -14,13 +14,9 @@
// Constructor
EditorStatusBar::EditorStatusBar(std::string room_number)
: room_number_(std::move(room_number)) {
const float SURFACE_WIDTH = Options::game.width;
constexpr float SURFACE_HEIGHT = 24.0F; // 3 líneas de 8px
surface_ = std::make_shared<Surface>(SURFACE_WIDTH, SURFACE_HEIGHT);
surface_dest_ = {.x = 0, .y = Options::game.height - SURFACE_HEIGHT, .w = SURFACE_WIDTH, .h = SURFACE_HEIGHT};
}
: surface_(std::make_shared<Surface>(Options::game.width, SURFACE_HEIGHT)),
surface_dest_{.x = 0, .y = Options::game.height - SURFACE_HEIGHT, .w = Options::game.width, .h = SURFACE_HEIGHT},
room_number_(std::move(room_number)) {}
// Pinta la barra de estado en pantalla
void EditorStatusBar::render() {

View File

@@ -27,10 +27,11 @@ class EditorStatusBar {
// Constantes de posición (en pixels dentro de la surface de 256x24)
// Font 8bithud lowercase = 6px alto → 3 líneas con 8px de separación
static constexpr int LINE1_Y = 1; // Room number + tile coords + extra
static constexpr int LINE2_Y = 9; // Propiedades de room / enemy info
static constexpr int LINE3_Y = 17; // Conexiones+items / enemy detail
static constexpr int LEFT_X = 4; // Margen izquierdo
static constexpr float SURFACE_HEIGHT = 24.0F; // 3 líneas de 8px
static constexpr int LINE1_Y = 1; // Room number + tile coords + extra
static constexpr int LINE2_Y = 9; // Propiedades de room / enemy info
static constexpr int LINE3_Y = 17; // Conexiones+items / enemy detail
static constexpr int LEFT_X = 4; // Margen izquierdo
// Objetos
std::shared_ptr<Surface> surface_; // Surface donde dibujar la barra

View File

@@ -92,7 +92,7 @@ void MapEditor::loadSettings() {
}
}
} catch (...) {
// Fichero corrupto o vacío, usar defaults
// @INTENTIONAL: fichero corrupto o vacío usar defaults
}
}
@@ -231,8 +231,8 @@ void MapEditor::enter(std::shared_ptr<Room> room, std::shared_ptr<Player> player
painting_ = false; // Siempre dejar de pintar al cambiar de room
// Asegurar que collision_tile_map tiene el tamaño correcto
if (room_data_.collision_tile_map.size() != static_cast<size_t>(Map::WIDTH * Map::HEIGHT)) {
room_data_.collision_tile_map.resize(Map::WIDTH * Map::HEIGHT, 0);
if (room_data_.collision_tile_map.size() != static_cast<size_t>(Map::WIDTH) * static_cast<size_t>(Map::HEIGHT)) {
room_data_.collision_tile_map.resize(static_cast<size_t>(Map::WIDTH) * static_cast<size_t>(Map::HEIGHT), 0);
}
active_ = true;
@@ -361,30 +361,31 @@ void MapEditor::update(float delta_time) {
}
// Renderiza el editor
void MapEditor::renderCollisionOverlay() const {
auto collision_surface = Resource::Cache::get()->getSurface("collision.gif");
if (!collision_surface) { return; }
const int TILE_W = Tile::SIZE;
for (int y = 0; y < Map::HEIGHT; ++y) {
for (int x = 0; x < Map::WIDTH; ++x) {
int index = (y * Map::WIDTH) + x;
if (index >= static_cast<int>(room_data_.collision_tile_map.size())) { continue; }
int tile = room_data_.collision_tile_map[index];
if (tile <= 0) { continue; } // 0 = vacío, no dibujar
SDL_FRect clip = {
.x = static_cast<float>(tile * TILE_W),
.y = 0,
.w = static_cast<float>(TILE_W),
.h = static_cast<float>(TILE_W)};
collision_surface->render(x * TILE_W, y * TILE_W, &clip);
}
}
}
void MapEditor::render() {
// El tilemap ya ha sido renderizado por Game::renderPlaying() antes de llamar aquí
// Si estamos editando colisiones, superponer el mapa de colisiones
if (editing_collision_) {
auto collision_surface = Resource::Cache::get()->getSurface("collision.gif");
if (collision_surface) {
const int TILE_W = Tile::SIZE;
for (int y = 0; y < Map::HEIGHT; ++y) {
for (int x = 0; x < Map::WIDTH; ++x) {
int index = (y * Map::WIDTH) + x;
if (index >= static_cast<int>(room_data_.collision_tile_map.size())) { continue; }
int tile = room_data_.collision_tile_map[index];
if (tile <= 0) { continue; } // 0 = vacío, no dibujar
SDL_FRect clip = {
.x = static_cast<float>(tile * TILE_W),
.y = 0,
.w = static_cast<float>(TILE_W),
.h = static_cast<float>(TILE_W)};
collision_surface->render(x * TILE_W, y * TILE_W, &clip);
}
}
}
}
if (editing_collision_) { renderCollisionOverlay(); }
// Grid (debajo de todo)
if (settings_.grid) {
@@ -526,7 +527,7 @@ void MapEditor::handleEvent(const SDL_Event& event) { // NOLINT(readability-fun
// Deseleccionar entidades
selection_.clear();
const std::string tileset_name = editing_collision_ ? "collision.gif" : room_->getTileSetFile();
const std::string TILESET_NAME = editing_collision_ ? "collision.gif" : room_->getTileSetFile();
int tile_index = (mouse_tile_y_ * 32) + mouse_tile_x_;
int current = 0;
if (editing_collision_) {
@@ -539,10 +540,10 @@ void MapEditor::handleEvent(const SDL_Event& event) { // NOLINT(readability-fun
: -1;
}
tile_picker_.on_select = [this, tileset_name](int col, int row, int width, int height) {
brush_ = buildPatternFromTileset(tileset_name, col, row, width, height);
tile_picker_.on_select = [this, TILESET_NAME](int col, int row, int width, int height) {
brush_ = buildPatternFromTileset(TILESET_NAME, col, row, width, height);
};
tile_picker_.open(tileset_name, current, 0, -1, -1, 0, 1, true);
tile_picker_.open(TILESET_NAME, current, 0, -1, -1, 0, 1, true);
return;
}
@@ -664,7 +665,7 @@ void MapEditor::handleMouseUp() {
if (selection_.is(drag_.entity_type) && selection_.index == drag_.index) {
selection_.clear(); // deselect
} else {
selection_ = {drag_.entity_type, drag_.index}; // select
selection_ = {.type = drag_.entity_type, .index = drag_.index}; // select
}
} else {
selection_.clear();
@@ -696,7 +697,8 @@ void MapEditor::handleMouseUp() {
drag_ = {};
}
// Commit de un drag de entidad (initial, bound1, bound2) para cualquier EntityType
// Commit de un drag de entidad (initial, bound1, bound2) para cualquier EntityType.
// NOLINTNEXTLINE(readability-function-cognitive-complexity) -- switch sobre EntityType con una rama por tipo; refactor a visitor requiere cambio de diseño.
auto MapEditor::commitEntityDrag() -> bool {
const int IDX = drag_.index;
const int SNAP_X = static_cast<int>(drag_.snap_x);
@@ -710,14 +712,14 @@ auto MapEditor::commitEntityDrag() -> bool {
room_data_.enemies[IDX].x = drag_.snap_x;
room_data_.enemies[IDX].y = drag_.snap_y;
room_->getEnemyManager()->getEnemy(IDX)->resetToInitialPosition(room_data_.enemies[IDX]);
selection_ = {EntityType::ENEMY, IDX};
selection_ = {.type = EntityType::ENEMY, .index = IDX};
return true;
}
break;
case EntityType::ITEM:
if (IDX >= 0 && IDX < room_->getItemManager()->getCount()) {
room_->getItemManager()->getItem(IDX)->setPosition(drag_.snap_x, drag_.snap_y);
selection_ = {EntityType::ITEM, IDX};
selection_ = {.type = EntityType::ITEM, .index = IDX};
return true;
}
break;
@@ -733,7 +735,7 @@ auto MapEditor::commitEntityDrag() -> bool {
}
}
room_->getPlatformManager()->getPlatform(IDX)->resetToInitialPosition(plat);
selection_ = {EntityType::PLATFORM, IDX};
selection_ = {.type = EntityType::PLATFORM, .index = IDX};
return true;
}
break;
@@ -744,7 +746,7 @@ auto MapEditor::commitEntityDrag() -> bool {
// sprite→data igual que con items.
room_data_.keys[IDX].x = drag_.snap_x;
room_data_.keys[IDX].y = drag_.snap_y;
selection_ = {EntityType::KEY, IDX};
selection_ = {.type = EntityType::KEY, .index = IDX};
return true;
}
break;
@@ -756,7 +758,7 @@ auto MapEditor::commitEntityDrag() -> bool {
room_data_.doors[IDX].x = drag_.snap_x;
room_data_.doors[IDX].y = drag_.snap_y;
room_->getDoorManager()->moveDoor(IDX, drag_.snap_x, drag_.snap_y);
selection_ = {EntityType::DOOR, IDX};
selection_ = {.type = EntityType::DOOR, .index = IDX};
return true;
}
break;
@@ -772,7 +774,7 @@ auto MapEditor::commitEntityDrag() -> bool {
room_data_.enemies[IDX].x1 = SNAP_X;
room_data_.enemies[IDX].y1 = SNAP_Y;
room_->getEnemyManager()->getEnemy(IDX)->resetToInitialPosition(room_data_.enemies[IDX]);
selection_ = {EntityType::ENEMY, IDX};
selection_ = {.type = EntityType::ENEMY, .index = IDX};
return true;
}
break;
@@ -788,7 +790,7 @@ auto MapEditor::commitEntityDrag() -> bool {
room_data_.enemies[IDX].x2 = SNAP_X;
room_data_.enemies[IDX].y2 = SNAP_Y;
room_->getEnemyManager()->getEnemy(IDX)->resetToInitialPosition(room_data_.enemies[IDX]);
selection_ = {EntityType::ENEMY, IDX};
selection_ = {.type = EntityType::ENEMY, .index = IDX};
return true;
}
break;
@@ -803,7 +805,8 @@ auto MapEditor::commitEntityDrag() -> bool {
return false;
}
// Mueve visualmente la entidad arrastrada a la posición snapped
// Mueve visualmente la entidad arrastrada a la posición snapped.
// NOLINTNEXTLINE(readability-function-cognitive-complexity) -- switch sobre EntityType con cases paralelos para cada tipo.
void MapEditor::moveEntityVisual() {
switch (drag_.target) {
case DragTarget::ENTITY_INITIAL:
@@ -857,8 +860,6 @@ void MapEditor::moveEntityVisual() {
case DragTarget::ENTITY_BOUND1:
case DragTarget::ENTITY_BOUND2:
// Los boundaries se actualizan visualmente en renderEntityBoundaries() via drag_.snap
break;
default:
break;
}
@@ -946,7 +947,8 @@ void MapEditor::renderSelectionHighlight() {
game_surface->drawRectBorder(&border, DRAG_COLOR);
}
// Estampa el patrón del brush en la posición indicada (anclaje top-left)
// Estampa el patrón del brush en la posición indicada (anclaje top-left).
// NOLINTNEXTLINE(readability-function-cognitive-complexity) -- nested loops + casos TRANSPARENT/ERASE/tile normal y ramas collision vs normal.
void MapEditor::stampBrushAt(int tile_x, int tile_y) {
if (brush_.isEmpty()) { return; }
for (int dy = 0; dy < brush_.height; ++dy) {
@@ -997,7 +999,7 @@ auto MapEditor::sampleBrush(int x1, int y1, int x2, int y2) const -> BrushPatter
BrushPattern p;
p.width = (x2 - x1) + 1;
p.height = (y2 - y1) + 1;
p.tiles.reserve(static_cast<size_t>(p.width * p.height));
p.tiles.reserve(static_cast<size_t>(p.width) * static_cast<size_t>(p.height));
const auto& src = editing_collision_ ? room_data_.collision_tile_map : room_data_.tile_map;
for (int y = y1; y <= y2; ++y) {
for (int x = x1; x <= x2; ++x) {
@@ -1012,7 +1014,7 @@ auto MapEditor::sampleBrush(int x1, int y1, int x2, int y2) const -> BrushPatter
// Construye un BrushPattern leyendo tiles consecutivos de un tileset.
// Usado por el TilePicker cuando se hace selección rectangular.
auto MapEditor::buildPatternFromTileset(const std::string& tileset_name, int col, int row, int width, int height) const -> BrushPattern {
auto MapEditor::buildPatternFromTileset(const std::string& tileset_name, int col, int row, int width, int height) -> BrushPattern {
BrushPattern p;
auto surface = Resource::Cache::get()->getSurface(tileset_name);
if (!surface || width <= 0 || height <= 0) { return p; }
@@ -1020,7 +1022,7 @@ auto MapEditor::buildPatternFromTileset(const std::string& tileset_name, int col
if (cols <= 0) { return p; }
p.width = width;
p.height = height;
p.tiles.reserve(static_cast<size_t>(width * height));
p.tiles.reserve(static_cast<size_t>(width) * static_cast<size_t>(height));
for (int dy = 0; dy < height; ++dy) {
for (int dx = 0; dx < width; ++dx) {
int tile = ((row + dy) * cols) + (col + dx);
@@ -1036,8 +1038,8 @@ void MapEditor::renderBrushPreview() {
auto game_surface = Screen::get()->getRendererSurface();
if (!game_surface) { return; }
const std::string tileset_name = editing_collision_ ? std::string("collision.gif") : room_->getTileSetFile();
auto tileset = Resource::Cache::get()->getSurface(tileset_name);
const std::string TILESET_NAME = editing_collision_ ? std::string("collision.gif") : room_->getTileSetFile();
auto tileset = Resource::Cache::get()->getSurface(TILESET_NAME);
int cols = (tileset) ? (static_cast<int>(tileset->getWidth()) / Tile::SIZE) : 0;
constexpr auto TS = static_cast<float>(Tile::SIZE);
@@ -1052,7 +1054,7 @@ void MapEditor::renderBrushPreview() {
float dst_y = static_cast<float>(ty) * TS;
if (value == BrushPattern::ERASE) {
SDL_FRect erase_cell = {.x = dst_x, .y = dst_y, .w = TS, .h = TS};
game_surface->fillRect(&erase_cell, static_cast<Uint8>(room_data_.bg_color));
game_surface->fillRect(&erase_cell, room_data_.bg_color);
} else if (tileset && cols > 0) {
SDL_FRect src = {
.x = static_cast<float>(value % cols) * TS,
@@ -1077,7 +1079,7 @@ void MapEditor::renderBrushPreview() {
}
// Renderiza el rectángulo del eyedropper en progreso (cyan brillante)
void MapEditor::renderEyedropperRect() {
void MapEditor::renderEyedropperRect() const {
auto game_surface = Screen::get()->getRendererSurface();
if (!game_surface) { return; }
int x1 = std::clamp(eyedropper_.start_tile_x, 0, Map::WIDTH - 1);
@@ -1152,7 +1154,7 @@ auto MapEditor::entityBoundaries(EntityType type, int index) const -> BoundaryDa
switch (type) {
case EntityType::ENEMY: {
const auto& e = room_data_.enemies[index];
return {e.x1, e.y1, e.x2, e.y2};
return {.x1 = e.x1, .y1 = e.y1, .x2 = e.x2, .y2 = e.y2};
}
default:
return {};
@@ -1212,7 +1214,8 @@ auto MapEditor::entityLabel(EntityType type) -> const char* {
}
}
// Dibuja marcadores de boundaries y líneas de ruta para enemigos y plataformas
// Dibuja marcadores de boundaries y líneas de ruta para enemigos y plataformas.
// NOLINTNEXTLINE(readability-function-cognitive-complexity) -- switch sobre EntityType con ramas de enemigo patrullante vs plataforma con waypoints.
void MapEditor::renderEntityBoundaries() {
auto game_surface = Screen::get()->getRendererSurface();
if (!game_surface) { return; }
@@ -1395,7 +1398,7 @@ void MapEditor::updateStatusBarInfo() { // NOLINT(readability-function-cognitiv
const auto& e = room_data_.enemies[selection_.index];
std::string anim = e.animation_path;
auto dot = anim.rfind('.');
if (dot != std::string::npos) { anim = anim.substr(0, dot); }
if (dot != std::string::npos) { anim.resize(dot); }
line2 = "enemy " + std::to_string(selection_.index) + ": " + anim;
line3 = "vx:" + std::to_string(static_cast<int>(e.vx)) +
@@ -1420,7 +1423,7 @@ void MapEditor::updateStatusBarInfo() { // NOLINT(readability-function-cognitiv
if (selection_.index < static_cast<int>(room_data_.platforms.size())) {
const auto& p = room_data_.platforms[selection_.index];
std::string anim = p.animation_path;
if (anim.size() > 5 && anim.substr(anim.size() - 5) == ".yaml") { anim = anim.substr(0, anim.size() - 5); }
if (anim.ends_with(".yaml")) { anim.resize(anim.size() - 5); }
line2 = "platform " + std::to_string(selection_.index) + ": " + anim;
line3 = "speed:" + std::to_string(static_cast<int>(p.speed)) + " " + (p.loop == LoopMode::CIRCULAR ? "circular" : "pingpong");
if (p.easing != "linear") { line3 += " " + p.easing; }
@@ -1432,7 +1435,7 @@ void MapEditor::updateStatusBarInfo() { // NOLINT(readability-function-cognitiv
const auto& k = room_data_.keys[selection_.index];
std::string anim = k.animation_path;
auto dot = anim.rfind('.');
if (dot != std::string::npos) { anim = anim.substr(0, dot); }
if (dot != std::string::npos) { anim.resize(dot); }
line2 = "key " + std::to_string(selection_.index) + ": " + anim;
line3 = "id: " + k.id;
}
@@ -1443,7 +1446,7 @@ void MapEditor::updateStatusBarInfo() { // NOLINT(readability-function-cognitiv
const auto& d = room_data_.doors[selection_.index];
std::string anim = d.animation_path;
auto dot = anim.rfind('.');
if (dot != std::string::npos) { anim = anim.substr(0, dot); }
if (dot != std::string::npos) { anim.resize(dot); }
line2 = "door " + std::to_string(selection_.index) + ": " + anim;
line3 = "id: " + d.id;
}
@@ -1495,7 +1498,6 @@ auto MapEditor::getSetCompletions() const -> std::vector<std::string> {
case EntityType::PLATFORM:
return {"ANIMATION", "SPEED", "LOOP", "EASING"};
case EntityType::KEY:
return {"ID", "ANIMATION"};
case EntityType::DOOR:
return {"ID", "ANIMATION"};
default:
@@ -1534,7 +1536,7 @@ auto MapEditor::getAnimationCompletions() const -> std::vector<std::string> {
if (path.extension() != ".yaml") { continue; }
result.push_back(toUpper(path.stem().string()));
}
std::sort(result.begin(), result.end());
std::ranges::sort(result);
return result;
}
@@ -1652,7 +1654,7 @@ auto MapEditor::addEnemy() -> std::string {
// Seleccionar el nuevo enemigo
int new_index = static_cast<int>(room_data_.enemies.size()) - 1;
selection_ = {EntityType::ENEMY, new_index};
selection_ = {.type = EntityType::ENEMY, .index = new_index};
autosave();
return "Added enemy " + std::to_string(new_index);
@@ -1699,7 +1701,7 @@ auto MapEditor::duplicateEnemy() -> std::string {
// Seleccionar el nuevo enemigo
int new_index = static_cast<int>(room_data_.enemies.size()) - 1;
selection_ = {EntityType::ENEMY, new_index};
selection_ = {.type = EntityType::ENEMY, .index = new_index};
autosave();
return "Duplicated as enemy " + std::to_string(new_index);
@@ -1907,7 +1909,7 @@ auto MapEditor::createNewRoom(const std::string& direction) -> std::string { //
// Comprobar que no hay ya una room en esa dirección
if (!direction.empty()) {
std::string* existing = nullptr;
const std::string* existing = nullptr;
if (direction == "UP") {
existing = &room_data_.upper_room;
} else if (direction == "DOWN") {
@@ -1923,12 +1925,14 @@ auto MapEditor::createNewRoom(const std::string& direction) -> std::string { //
}
// Encontrar el primer número libre (reutiliza huecos)
auto& rooms = Resource::Cache::get()->getRooms();
const auto& rooms = Resource::Cache::get()->getRooms();
std::set<int> used;
for (const auto& r : rooms) {
try {
used.insert(std::stoi(r.name.substr(0, r.name.find('.'))));
} catch (...) {}
} catch (...) {
// @INTENTIONAL: nombre de room no es numérico → saltar
}
}
int new_num = 1;
while (used.contains(new_num)) { ++new_num; }
@@ -1970,7 +1974,7 @@ auto MapEditor::createNewRoom(const std::string& direction) -> std::string { //
// Persistir vía la autoridad del formato (no más std::ofstream a pelo)
auto save_result = RoomFormat::saveYAML(new_path, new_room);
if (save_result.find("Error") == 0) { return save_result; }
if (save_result.starts_with("Error")) { return save_result; }
// Registrar en Resource::List (mapa + assets.yaml) y cache
Resource::List::get()->addAsset(new_path, Resource::List::Type::ROOM);
@@ -2212,7 +2216,7 @@ auto MapEditor::addItem() -> std::string {
room_->getItemManager()->addItem(std::make_shared<Item>(new_item));
int new_index = static_cast<int>(room_data_.items.size()) - 1;
selection_ = {EntityType::ITEM, new_index};
selection_ = {.type = EntityType::ITEM, .index = new_index};
autosave();
return "Added item " + std::to_string(new_index);
@@ -2250,7 +2254,7 @@ auto MapEditor::duplicateItem() -> std::string {
room_->getItemManager()->addItem(std::make_shared<Item>(copy));
int new_index = static_cast<int>(room_data_.items.size()) - 1;
selection_ = {EntityType::ITEM, new_index};
selection_ = {.type = EntityType::ITEM, .index = new_index};
autosave();
return "Duplicated as item " + std::to_string(new_index);
@@ -2328,14 +2332,14 @@ auto MapEditor::addPlatform() -> std::string {
constexpr float CENTER_Y = PlayArea::CENTER_Y;
constexpr float ROUTE_HALF = 2.0F * Tile::SIZE;
new_platform.path = {
{CENTER_X - ROUTE_HALF, CENTER_Y, 0.0F},
{CENTER_X + ROUTE_HALF, CENTER_Y, 0.0F}};
{.x = CENTER_X - ROUTE_HALF, .y = CENTER_Y, .wait = 0.0F},
{.x = CENTER_X + ROUTE_HALF, .y = CENTER_Y, .wait = 0.0F}};
room_data_.platforms.push_back(new_platform);
room_->getPlatformManager()->addPlatform(std::make_shared<MovingPlatform>(new_platform));
int new_index = static_cast<int>(room_data_.platforms.size()) - 1;
selection_ = {EntityType::PLATFORM, new_index};
selection_ = {.type = EntityType::PLATFORM, .index = new_index};
autosave();
return "Added platform " + std::to_string(new_index);
@@ -2375,7 +2379,7 @@ auto MapEditor::duplicatePlatform() -> std::string {
room_->getPlatformManager()->addPlatform(std::make_shared<MovingPlatform>(copy));
int new_index = static_cast<int>(room_data_.platforms.size()) - 1;
selection_ = {EntityType::PLATFORM, new_index};
selection_ = {.type = EntityType::PLATFORM, .index = new_index};
autosave();
return "Duplicated as platform " + std::to_string(new_index);
@@ -2441,7 +2445,7 @@ auto MapEditor::addKey() -> std::string {
}
int new_index = static_cast<int>(room_data_.keys.size()) - 1;
selection_ = {EntityType::KEY, new_index};
selection_ = {.type = EntityType::KEY, .index = new_index};
autosave();
return "Added key " + std::to_string(new_index);
@@ -2484,7 +2488,7 @@ auto MapEditor::duplicateKey() -> std::string {
}
int new_index = static_cast<int>(room_data_.keys.size()) - 1;
selection_ = {EntityType::KEY, new_index};
selection_ = {.type = EntityType::KEY, .index = new_index};
autosave();
return "Duplicated as key " + std::to_string(new_index);
@@ -2568,7 +2572,7 @@ auto MapEditor::addDoor() -> std::string {
}
int new_index = static_cast<int>(room_data_.doors.size()) - 1;
selection_ = {EntityType::DOOR, new_index};
selection_ = {.type = EntityType::DOOR, .index = new_index};
autosave();
return "Added door " + std::to_string(new_index);
@@ -2608,7 +2612,7 @@ auto MapEditor::duplicateDoor() -> std::string {
}
int new_index = static_cast<int>(room_data_.doors.size()) - 1;
selection_ = {EntityType::DOOR, new_index};
selection_ = {.type = EntityType::DOOR, .index = new_index};
autosave();
return "Duplicated as door " + std::to_string(new_index);
@@ -2629,7 +2633,7 @@ static auto pickGridColor(Uint8 bg, const std::shared_ptr<Surface>& surface) ->
}
// Dibuja la cuadrícula de tiles (líneas de puntos, 1 pixel sí / 1 no)
void MapEditor::renderGrid() const {
void MapEditor::renderGrid() {
auto game_surface = Screen::get()->getRendererSurface();
if (!game_surface) { return; }

View File

@@ -4,9 +4,10 @@
#include <SDL3/SDL.h>
#include <memory> // Para shared_ptr, unique_ptr
#include <string> // Para string
#include <vector> // Para vector
#include <cstdint> // Para uint8_t
#include <memory> // Para shared_ptr, unique_ptr
#include <string> // Para string
#include <vector> // Para vector
#include "game/editor/mini_map.hpp" // Para MiniMap
#include "game/editor/tile_picker.hpp" // Para TilePicker
@@ -23,7 +24,7 @@
class EditorStatusBar;
// Tipo de entidad editable en el editor
enum class EntityType { NONE,
enum class EntityType : std::uint8_t { NONE,
ENEMY,
ITEM,
PLATFORM,
@@ -106,7 +107,7 @@ class MapEditor {
auto deleteRoom() -> std::string;
// Opciones del editor (llamados desde console_commands / teclas)
auto showInfo(bool show) -> std::string;
static auto showInfo(bool show) -> std::string;
auto showGrid(bool show) -> std::string;
auto setEditingCollision(bool collision) -> std::string;
[[nodiscard]] auto isGridEnabled() const -> bool { return settings_.grid; }
@@ -164,7 +165,7 @@ class MapEditor {
void saveSettings() const;
// Tipos para drag & drop
enum class DragTarget { NONE,
enum class DragTarget : std::uint8_t { NONE,
PLAYER,
ENTITY_INITIAL,
ENTITY_BOUND1,
@@ -183,18 +184,19 @@ class MapEditor {
// Métodos internos
void updateMousePosition();
void renderCollisionOverlay() const;
void renderEntityBoundaries();
static void renderBoundaryMarker(float x, float y, Uint8 color);
void renderSelectionHighlight();
void renderBrushPreview();
void renderEyedropperRect();
void renderGrid() const;
void renderEyedropperRect() const;
static void renderGrid();
void handleMouseDown(float game_x, float game_y);
void handleMouseUp();
void stampBrushAt(int tile_x, int tile_y);
void commitEyedropper();
[[nodiscard]] auto sampleBrush(int x1, int y1, int x2, int y2) const -> BrushPattern;
[[nodiscard]] auto buildPatternFromTileset(const std::string& tileset_name, int col, int row, int width, int height) const -> BrushPattern;
[[nodiscard]] static auto buildPatternFromTileset(const std::string& tileset_name, int col, int row, int width, int height) -> BrushPattern;
// Reconstruye todas las puertas vivas desde room_data_, limpiando primero
// los WALLs antiguos del CollisionMap. Lo usa setDoorProperty cuando un
@@ -213,12 +215,12 @@ class MapEditor {
struct BoundaryData {
int x1, y1, x2, y2;
};
auto entityCount(EntityType type) const -> int;
[[nodiscard]] auto entityCount(EntityType type) const -> int;
auto entityRect(EntityType type, int index) -> SDL_FRect;
static auto entityHasBoundaries(EntityType type) -> bool;
auto entityBoundaries(EntityType type, int index) const -> BoundaryData;
auto entityPosition(EntityType type, int index) const -> std::pair<float, float>;
auto entityDataCount(EntityType type) const -> int;
[[nodiscard]] auto entityBoundaries(EntityType type, int index) const -> BoundaryData;
[[nodiscard]] auto entityPosition(EntityType type, int index) const -> std::pair<float, float>;
[[nodiscard]] auto entityDataCount(EntityType type) const -> int;
static auto entityLabel(EntityType type) -> const char*;
// Estado del editor

View File

@@ -85,7 +85,7 @@ auto MiniMap::getOrBuildTileColorTable(const std::string& tileset_name) -> const
// Posiciona las rooms en un grid usando BFS desde las conexiones
void MiniMap::layoutRooms() {
auto& rooms = Resource::Cache::get()->getRooms();
const auto& rooms = Resource::Cache::get()->getRooms();
if (rooms.empty()) { return; }
// Mapa de nombre → Room::Data

View File

@@ -166,8 +166,8 @@ void TilePicker::render() {
int cells_h = row_max - row_min + 1;
float rx = tileset_screen_x + static_cast<float>(col_min * out_cell);
float ry = tileset_screen_y + static_cast<float>(row_min * out_cell);
float rw = static_cast<float>((cells_w * out_cell) - spacing_out_);
float rh = static_cast<float>((cells_h * out_cell) - spacing_out_);
auto rw = static_cast<float>((cells_w * out_cell) - spacing_out_);
auto rh = static_cast<float>((cells_h * out_cell) - spacing_out_);
if (ry + rh > 0 && ry < static_cast<float>(visible_height_)) {
SDL_FRect rect_box = {.x = rx, .y = ry, .w = rw, .h = rh};
game_surface->drawRectBorder(&rect_box, 15);
@@ -198,6 +198,27 @@ void TilePicker::render() {
}
}
// Invoca el callback con el rect formado por rect_start_tile_ y el hover actual, luego cierra.
void TilePicker::commitRectSelection() {
int end_tile = (hover_tile_ >= 0) ? hover_tile_ : last_valid_hover_;
if (end_tile >= 0 && rect_start_tile_ >= 0) {
int c1 = rect_start_tile_ % tileset_width_;
int r1 = rect_start_tile_ / tileset_width_;
int c2 = end_tile % tileset_width_;
int r2 = end_tile / tileset_width_;
int col_min = std::min(c1, c2);
int col_max = std::max(c1, c2);
int row_min = std::min(r1, r2);
int row_max = std::max(r1, r2);
int width = col_max - col_min + 1;
int height = row_max - row_min + 1;
if (on_select) { on_select(col_min, row_min, width, height); }
}
selecting_rect_ = false;
rect_start_tile_ = -1;
close();
}
// Maneja eventos del picker
void TilePicker::handleEvent(const SDL_Event& event) {
if (!open_) { return; }
@@ -226,23 +247,7 @@ void TilePicker::handleEvent(const SDL_Event& event) {
}
if (event.type == SDL_EVENT_MOUSE_BUTTON_UP && event.button.button == SDL_BUTTON_LEFT && selecting_rect_) {
int end_tile = (hover_tile_ >= 0) ? hover_tile_ : last_valid_hover_;
if (end_tile >= 0 && rect_start_tile_ >= 0) {
int c1 = rect_start_tile_ % tileset_width_;
int r1 = rect_start_tile_ / tileset_width_;
int c2 = end_tile % tileset_width_;
int r2 = end_tile / tileset_width_;
int col_min = std::min(c1, c2);
int col_max = std::max(c1, c2);
int row_min = std::min(r1, r2);
int row_max = std::max(r1, r2);
int width = col_max - col_min + 1;
int height = row_max - row_min + 1;
if (on_select) { on_select(col_min, row_min, width, height); }
}
selecting_rect_ = false;
rect_start_tile_ = -1;
close();
commitRectSelection();
}
if (event.type == SDL_EVENT_MOUSE_WHEEL) {

View File

@@ -44,6 +44,7 @@ class TilePicker {
private:
void updateMousePosition();
void commitRectSelection();
bool open_{false};
std::shared_ptr<Surface> tileset_; // Surface del tileset original

View File

@@ -2,6 +2,7 @@
#include <SDL3/SDL.h>
#include <cstdint>
#include <memory> // Para shared_ptr
#include <string> // Para string
@@ -24,7 +25,7 @@ class AnimatedSprite;
*/
class Door : public SolidActor {
public:
enum class State : int {
enum class State : std::uint8_t {
CLOSED = 0,
OPENING = 1,
OPENED = 2

View File

@@ -82,7 +82,7 @@ void MovingPlatform::recalcSegmentLength() {
float dx = path_[to].x - path_[from].x;
float dy = path_[to].y - path_[from].y;
segment_length_ = std::sqrt(dx * dx + dy * dy);
segment_length_ = std::sqrt((dx * dx) + (dy * dy));
}
// Avanza al siguiente segmento
@@ -174,8 +174,8 @@ void MovingPlatform::update(float delta_time) {
int from = getSegmentFrom();
int to = getSegmentTo();
float new_x = path_[from].x + (path_[to].x - path_[from].x) * t;
float new_y = path_[from].y + (path_[to].y - path_[from].y) * t;
float new_x = path_[from].x + ((path_[to].x - path_[from].x) * t);
float new_y = path_[from].y + ((path_[to].y - path_[from].y) * t);
sprite_->setPosX(new_x);
sprite_->setPosY(new_y);
}

View File

@@ -2,6 +2,7 @@
#include <SDL3/SDL.h>
#include <cstdint>
#include <memory> // Para shared_ptr
#include <string> // Para string
#include <vector> // Para vector
@@ -18,7 +19,7 @@ struct Waypoint {
};
// Modo de recorrido de la ruta
enum class LoopMode { PINGPONG,
enum class LoopMode : std::uint8_t { PINGPONG,
CIRCULAR };
// Tipo de función de easing

View File

@@ -540,18 +540,10 @@ void Player::transitionToState(State state) {
switch (state) {
case State::ON_GROUND:
vy_ = 0;
// Clamp vx al aterrizar (el salto puede dar un boost extra)
if (vx_ > HORIZONTAL_VELOCITY) { vx_ = HORIZONTAL_VELOCITY; }
if (vx_ < -HORIZONTAL_VELOCITY) { vx_ = -HORIZONTAL_VELOCITY; }
if (previous_state_ == State::ON_AIR) {
Audio::get()->playSound(land_sound_, Audio::Group::GAME);
}
break;
case State::ON_SLOPE:
vy_ = 0;
if (vx_ > HORIZONTAL_VELOCITY) { vx_ = HORIZONTAL_VELOCITY; }
if (vx_ < -HORIZONTAL_VELOCITY) { vx_ = -HORIZONTAL_VELOCITY; }
// Clamp vx al aterrizar (el salto puede dar un boost extra)
vx_ = std::clamp(vx_, -HORIZONTAL_VELOCITY, HORIZONTAL_VELOCITY);
if (previous_state_ == State::ON_AIR) {
Audio::get()->playSound(land_sound_, Audio::Group::GAME);
}

View File

@@ -2,6 +2,7 @@
#include <SDL3/SDL.h>
#include <cstdint>
#include <memory> // Para shared_ptr
#include <string> // Para string
#include <utility>
@@ -17,13 +18,13 @@ class SolidActor;
class Player {
public:
// --- Enums y Structs ---
enum class State {
enum class State : std::uint8_t {
ON_GROUND,
ON_SLOPE,
ON_AIR,
};
enum class Direction {
enum class Direction : std::uint8_t {
LEFT,
RIGHT,
UP,
@@ -151,7 +152,7 @@ class Player {
void syncSpriteAndCollider();
void placeSprite();
void animate(float delta_time);
auto handleBorders() const -> Room::Border;
[[nodiscard]] auto handleBorders() const -> Room::Border;
// --- Inicialización ---
void initSprite(const std::string& animations_path);

View File

@@ -25,6 +25,7 @@
*/
class SolidActor {
public:
// NOLINTNEXTLINE(performance-enum-size) -- bitmask con margen para crecer
enum Flags : uint32_t {
BLOCKS_PLAYER = 1U << 0U,
CARRY_ON_TOP = 1U << 1U,

View File

@@ -5,7 +5,7 @@
CollisionMap::CollisionMap(std::vector<int> collision_tile_map)
: collision_tile_map_(std::move(collision_tile_map)),
extended_tile_map_(EW * EH, 0),
extended_tile_map_(static_cast<size_t>(EW) * static_cast<size_t>(EH), 0),
tile_collider_(extended_tile_map_, EW, EH, CollisionBorder::PX) {
buildExtendedCenter();
}

View File

@@ -2,6 +2,7 @@
#include <SDL3/SDL.h>
#include <cstdint>
#include <memory> // Para shared_ptr
#include <string> // Para string
#include <vector> // Para vector
@@ -28,7 +29,7 @@ class TilemapRenderer;
class Room {
public:
// -- Enumeraciones y estructuras ---
enum class Border : int {
enum class Border : std::uint8_t {
TOP = 0,
RIGHT = 1,
BOTTOM = 2,

View File

@@ -78,7 +78,7 @@ auto RoomFormat::convertAutoSurface(const fkyaml::node& node) -> int {
auto RoomFormat::flattenTilemap(const std::vector<std::vector<int>>& tilemap_2d) -> std::vector<int> {
std::vector<int> tilemap_flat;
tilemap_flat.reserve(Map::WIDTH * Map::HEIGHT);
tilemap_flat.reserve(static_cast<size_t>(Map::WIDTH) * static_cast<size_t>(Map::HEIGHT));
for (const auto& row : tilemap_2d) {
for (int tile : row) {
@@ -535,8 +535,8 @@ auto RoomFormat::createDefault() -> Room::Data {
data.right_room = "0";
// Tilemaps del tamaño correcto, vacíos
data.tile_map.resize(Map::WIDTH * Map::HEIGHT, -1);
data.collision_tile_map.resize(Map::WIDTH * Map::HEIGHT, 0);
data.tile_map.resize(static_cast<size_t>(Map::WIDTH) * static_cast<size_t>(Map::HEIGHT), -1);
data.collision_tile_map.resize(static_cast<size_t>(Map::WIDTH) * static_cast<size_t>(Map::HEIGHT), 0);
return data;
}

View File

@@ -16,13 +16,9 @@
// Constructor
Scoreboard::Scoreboard(std::shared_ptr<Data> data)
: data_(std::move(data)) {
const float SURFACE_WIDTH = Options::game.width;
constexpr float SURFACE_HEIGHT = 24.0F; // 3 líneas de 8px
surface_ = std::make_shared<Surface>(SURFACE_WIDTH, SURFACE_HEIGHT);
surface_dest_ = {.x = 0, .y = Options::game.height - SURFACE_HEIGHT, .w = SURFACE_WIDTH, .h = SURFACE_HEIGHT};
}
: data_(std::move(data)),
surface_(std::make_shared<Surface>(Options::game.width, SURFACE_HEIGHT)),
surface_dest_{.x = 0, .y = Options::game.height - SURFACE_HEIGHT, .w = Options::game.width, .h = SURFACE_HEIGHT} {}
// Pinta el objeto en pantalla
void Scoreboard::render() {
@@ -91,19 +87,19 @@ void Scoreboard::fillTexture() {
const std::string TIME_LABEL = Locale::get()->get("scoreboard.time");
// Ancho total: labels proporcionales + valores monoespaciados
const int LINE1_W = text->length(LIVES_LABEL) + text->lengthMono(LIVES_STR, MONO_W) + text->length(SEP) + text->length(ITEMS_LABEL) + text->lengthMono(ITEMS_STR, MONO_W) + text->length(SEP) + text->length(TIME_LABEL) + text->lengthMono(TIME_STR, MONO_W);
const int LINE1_W = text->length(LIVES_LABEL) + Text::lengthMono(LIVES_STR, MONO_W) + text->length(SEP) + text->length(ITEMS_LABEL) + Text::lengthMono(ITEMS_STR, MONO_W) + text->length(SEP) + text->length(TIME_LABEL) + Text::lengthMono(TIME_STR, MONO_W);
int x = (CANVAS_W - LINE1_W) / 2;
text->writeColored(x, LINE1_Y, LIVES_LABEL, LABEL_COLOR);
x += text->length(LIVES_LABEL);
text->writeColoredMono(x, LINE1_Y, LIVES_STR, VALUE_COLOR, MONO_W);
x += text->lengthMono(LIVES_STR, MONO_W);
x += Text::lengthMono(LIVES_STR, MONO_W);
text->writeColored(x, LINE1_Y, SEP, LABEL_COLOR);
x += text->length(SEP);
text->writeColored(x, LINE1_Y, ITEMS_LABEL, LABEL_COLOR);
x += text->length(ITEMS_LABEL);
text->writeColoredMono(x, LINE1_Y, ITEMS_STR, VALUE_COLOR, MONO_W);
x += text->lengthMono(ITEMS_STR, MONO_W);
x += Text::lengthMono(ITEMS_STR, MONO_W);
text->writeColored(x, LINE1_Y, SEP, LABEL_COLOR);
x += text->length(SEP);
text->writeColored(x, LINE1_Y, TIME_LABEL, LABEL_COLOR);

View File

@@ -39,6 +39,7 @@ class Scoreboard {
// Constantes de tiempo
// Posición de los elementos (2 líneas centradas verticalmente en surface de 24px)
static constexpr float SURFACE_HEIGHT = 24.0F; // 3 líneas de 8px
static constexpr int LINE1_Y = 5;
static constexpr int LINE2_Y = 13;

View File

@@ -1,12 +1,13 @@
#pragma once
#include <cstdint>
#include <vector>
#include "utils/defines.hpp"
class TileCollider {
public:
enum class Tile : int {
enum class Tile : std::uint8_t {
EMPTY = 0,
WALL = 1,
PASSABLE = 2,

View File

@@ -11,9 +11,8 @@ TilemapRenderer::TilemapRenderer(std::vector<int> tile_map, int tile_set_width,
: tile_map_(std::move(tile_map)),
tile_set_width_(tile_set_width),
tileset_surface_(std::move(tileset_surface)),
bg_color_(bg_color) {
map_surface_ = std::make_shared<Surface>(PlayArea::WIDTH, PlayArea::HEIGHT);
}
bg_color_(bg_color),
map_surface_(std::make_shared<Surface>(PlayArea::WIDTH, PlayArea::HEIGHT)) {}
void TilemapRenderer::initialize(const std::vector<int>& collision_tile_map) {
fillMapTexture(collision_tile_map);

View File

@@ -368,12 +368,14 @@ namespace Options {
if (sh_node.contains("current_postfx_preset")) {
try {
video.shader.current_postfx_preset_name = sh_node["current_postfx_preset"].get_value<std::string>();
} catch (...) {}
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
}
}
if (sh_node.contains("current_crtpi_preset")) {
try {
video.shader.current_crtpi_preset_name = sh_node["current_crtpi_preset"].get_value<std::string>();
} catch (...) {}
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
}
}
}
@@ -551,24 +553,28 @@ namespace Options {
if (a.contains("enabled")) {
try {
audio.enabled = a["enabled"].get_value<bool>();
} catch (...) {}
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
}
}
if (a.contains("volume")) {
try {
audio.volume = std::clamp(a["volume"].get_value<float>(), 0.0F, 1.0F);
} catch (...) {}
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
}
}
if (a.contains("music")) {
const auto& m = a["music"];
if (m.contains("enabled")) {
try {
audio.music.enabled = m["enabled"].get_value<bool>();
} catch (...) {}
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
}
}
if (m.contains("volume")) {
try {
audio.music.volume = std::clamp(m["volume"].get_value<float>(), 0.0F, 1.0F);
} catch (...) {}
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
}
}
}
if (a.contains("sound")) {
@@ -576,12 +582,14 @@ namespace Options {
if (s.contains("enabled")) {
try {
audio.sound.enabled = s["enabled"].get_value<bool>();
} catch (...) {}
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
}
}
if (s.contains("volume")) {
try {
audio.sound.volume = std::clamp(s["volume"].get_value<float>(), 0.0F, 1.0F);
} catch (...) {}
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
}
}
}
}
@@ -594,27 +602,32 @@ namespace Options {
if (c.contains("transparent")) {
try {
console.transparent = c["transparent"].get_value<bool>();
} catch (...) {}
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
}
}
if (c.contains("bg_color")) {
try {
console.bg_color = std::clamp(c["bg_color"].get_value<int>(), 0, 255);
} catch (...) {}
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
}
}
if (c.contains("msg_color")) {
try {
console.msg_color = std::clamp(c["msg_color"].get_value<int>(), 0, 255);
} catch (...) {}
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
}
}
if (c.contains("prompt_color")) {
try {
console.prompt_color = std::clamp(c["prompt_color"].get_value<int>(), 0, 255);
} catch (...) {}
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
}
}
if (c.contains("command_color")) {
try {
console.command_color = std::clamp(c["command_color"].get_value<int>(), 0, 255);
} catch (...) {}
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
}
}
}
@@ -876,7 +889,8 @@ namespace Options {
if (node.contains(key)) {
try {
target = node[key].get_value<float>();
} catch (...) {}
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
}
}
}
@@ -1166,32 +1180,38 @@ namespace Options {
if (p.contains("mask_type")) {
try {
preset.mask_type = p["mask_type"].get_value<int>();
} catch (...) {}
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
}
}
if (p.contains("enable_scanlines")) {
try {
preset.enable_scanlines = p["enable_scanlines"].get_value<bool>();
} catch (...) {}
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
}
}
if (p.contains("enable_multisample")) {
try {
preset.enable_multisample = p["enable_multisample"].get_value<bool>();
} catch (...) {}
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
}
}
if (p.contains("enable_gamma")) {
try {
preset.enable_gamma = p["enable_gamma"].get_value<bool>();
} catch (...) {}
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
}
}
if (p.contains("enable_curvature")) {
try {
preset.enable_curvature = p["enable_curvature"].get_value<bool>();
} catch (...) {}
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
}
}
if (p.contains("enable_sharper")) {
try {
preset.enable_sharper = p["enable_sharper"].get_value<bool>();
} catch (...) {}
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
}
}
crtpi_presets.push_back(preset);
}

View File

@@ -1,5 +1,7 @@
#pragma once
#include <cstdint>
/*
Namespace SceneManager: gestiona el flujo entre las diferentes escenas del juego.
@@ -10,7 +12,7 @@
namespace SceneManager {
// --- Escenas del programa ---
enum class Scene {
enum class Scene : std::uint8_t {
LOGO, // Pantalla del logo
TITLE, // Pantalla de título/menú principal
GAME, // Juego principal
@@ -19,7 +21,7 @@ namespace SceneManager {
};
// --- Opciones para transiciones entre escenas ---
enum class Options {
enum class Options : std::uint8_t {
NONE, // Sin opciones especiales
LOGO_TO_TITLE, // Del logo al título
};

View File

@@ -818,6 +818,7 @@ auto Game::getOrCreateRoom(const std::string& room_path) -> std::shared_ptr<Room
// Construye el tilemap extendido de la room actual con los bordes de las adyacentes
void Game::buildCollisionBorders() {
// NOLINTBEGIN(readability-identifier-naming) -- lambdas locales: se leen como llamadas a función, no son constantes.
// Helper: obtiene el collision tilemap de una room adyacente (nullptr si no existe)
auto getAdjacentCollision = [&](Room::Border b) -> const std::vector<int>* {
auto name = room_->getRoom(b);
@@ -843,6 +844,7 @@ void Game::buildCollisionBorders() {
}
return nullptr;
};
// NOLINTEND(readability-identifier-naming)
CollisionMap::AdjacentData adj;
adj.top = getAdjacentCollision(Room::Border::TOP);
@@ -861,6 +863,7 @@ void Game::buildCollisionBorders() {
// que los sweeps del Player vean AABBs dinámicos (puertas, plataformas)
// de la room vecina cuando está cerca del borde, sin tener que esperar
// a una transición completa de room.
// NOLINTNEXTLINE(readability-identifier-naming) -- lambda local: se lee como función, no es constante.
auto getAdjacentSolidActors = [&](Room::Border b) -> SolidActorManager* {
auto name = room_->getRoom(b);
if (name == "0") { return nullptr; }

View File

@@ -2,6 +2,7 @@
#include <SDL3/SDL.h>
#include <cstdint> // Para uint8_t
#include <initializer_list> // Para initializer_list
#include <memory> // Para shared_ptr
#include <string> // Para string
@@ -18,12 +19,12 @@ class Surface;
class Game {
public:
// --- Estructuras ---
enum class Mode {
enum class Mode : std::uint8_t {
DEMO,
GAME
};
enum class State {
enum class State : std::uint8_t {
PLAYING, // Normal gameplay
BLACK_SCREEN, // Black screen after death (0.30s)
GAME_OVER, // Intermediate state before changing scene

View File

@@ -2,6 +2,7 @@
#include <SDL3/SDL.h>
#include <cstdint>
#include <functional> // Para std::function
#include <memory> // Para shared_ptr
#include <vector> // Para vector
@@ -16,7 +17,7 @@ class Logo {
using EasingFunction = std::function<float(float)>; // Función de easing (permite lambdas)
// --- Enumeraciones ---
enum class State {
enum class State : std::uint8_t {
INITIAL, // Espera inicial
JAILGAMES_SLIDE_IN, // Las líneas de JAILGAMES se deslizan hacia el centro
SINCE_1998_FADE_IN, // Aparición gradual del texto "Since 1998"

View File

@@ -271,7 +271,7 @@ void Title::renderMainMenu() {
const int TOTAL_HEIGHT = 2 * SPACING; // 2 espacios entre 3 items
const int START_Y = MENU_CENTER_Y - (TOTAL_HEIGHT / 2);
auto* loc = Locale::get();
const auto* loc = Locale::get();
menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, PlayArea::CENTER_X, START_Y, loc->get("title.menu.play"), 1, COLOR);
menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, PlayArea::CENTER_X, START_Y + SPACING, loc->get("title.menu.keyboard"), 1, COLOR);
menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, PlayArea::CENTER_X, START_Y + (2 * SPACING), loc->get("title.menu.joystick"), 1, COLOR);
@@ -360,20 +360,6 @@ auto Title::isKeyDuplicate(SDL_Scancode scancode, int current_step) -> bool { /
return false;
}
// Retorna el nombre de la accion para el paso actual
auto Title::getActionName(int step) -> std::string { // NOLINT(readability-convert-member-functions-to-static)
switch (step) {
case 0:
return "LEFT";
case 1:
return "RIGHT";
case 2:
return "JUMP";
default:
return "UNKNOWN";
}
}
// Aplica y guarda las teclas redefinidas
void Title::applyKeyboardRemap() { // NOLINT(readability-convert-member-functions-to-static)
// Guardar las nuevas teclas en Options::controls
@@ -402,7 +388,7 @@ void Title::renderKeyboardRemap() const {
const int START_Y = MENU_CENTER_Y - (2 * TEXT_SIZE); // Centrado aproximado
// Mensaje principal: "PRESS KEY FOR [ACTION]" o "KEYS DEFINED" si completado
auto* loc = Locale::get();
const auto* loc = Locale::get();
if (remap_step_ >= 3) {
menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, PlayArea::CENTER_X, START_Y, loc->get("title.keys.defined"), 1, COLOR);
} else {
@@ -446,7 +432,7 @@ void Title::renderJoystickRemap() const {
const int START_Y = MENU_CENTER_Y - (2 * TEXT_SIZE); // Centrado aproximado
// Mensaje principal: "PRESS BUTTON FOR [ACTION]" o "BUTTONS DEFINED" si completado
auto* loc = Locale::get();
const auto* loc = Locale::get();
if (remap_step_ >= 3) {
menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, PlayArea::CENTER_X, START_Y, loc->get("title.buttons.defined"), 1, COLOR);
} else {

View File

@@ -2,7 +2,8 @@
#include <SDL3/SDL.h>
#include <array> // Para std::array
#include <array> // Para std::array
#include <cstdint>
#include <memory> // Para shared_ptr
#include <string> // Para string
@@ -24,7 +25,7 @@ class Title {
private:
// --- Estructuras y enumeraciones ---
enum class State {
enum class State : std::uint8_t {
MAIN_MENU,
FADE_MENU,
POST_FADE_MENU,
@@ -58,7 +59,6 @@ class Title {
auto isButtonDuplicate(int button, int current_step) -> bool; // Valida si un boton esta duplicado
void applyKeyboardRemap(); // Aplica y guarda las teclas redefinidas
void applyJoystickRemap(); // Aplica y guarda los botones del gamepad redefinidos
static auto getActionName(int step) -> std::string; // Retorna el nombre de la accion (LEFT/RIGHT/JUMP)
static auto getButtonName(int button) -> std::string; // Retorna el nombre amigable del boton del gamepad
void fillTitleSurface(); // Dibuja los elementos en la surface

View File

@@ -64,9 +64,16 @@ auto Console::wrapText(const std::string& text) const -> std::vector<std::string
std::istringstream word_stream(segment);
std::string word;
while (word_stream >> word) {
const std::string TEST = current_line.empty() ? word : (current_line + ' ' + word);
if (text_->length(TEST) <= MAX_PX) {
current_line = TEST;
std::string test;
if (current_line.empty()) {
test = word;
} else {
test = current_line;
test += ' ';
test += word;
}
if (text_->length(test) <= MAX_PX) {
current_line = test;
} else {
if (!current_line.empty()) { result.push_back(current_line); }
current_line = word;
@@ -381,7 +388,10 @@ void Console::handleEvent(const SDL_Event& event) { // NOLINT(readability-funct
const auto OPTS = registry_.getCompletions(BASE_CMD);
for (const auto& arg : OPTS) {
if (SUB_PREFIX.empty() || std::string_view{arg}.starts_with(SUB_PREFIX)) {
tab_matches_.emplace_back(BASE_CMD + " " + arg);
std::string match = BASE_CMD;
match += ' ';
match += arg;
tab_matches_.emplace_back(std::move(match));
}
}
}

View File

@@ -2,6 +2,7 @@
#include <SDL3/SDL.h>
#include <cstdint>
#include <deque> // Para deque (historial)
#include <functional> // Para function
#include <memory> // Para shared_ptr
@@ -46,7 +47,7 @@ class Console {
std::function<void(bool)> on_toggle;
private:
enum class Status {
enum class Status : std::uint8_t {
HIDDEN,
RISING,
ACTIVE,

View File

@@ -32,7 +32,7 @@
// Toggle genérico para comandos booleanos ON/OFF (reemplaza macro BOOL_TOGGLE_CMD)
static auto boolToggle(
const std::string& label,
bool& option,
const bool& option,
const std::function<void()>& toggle_fn,
const std::vector<std::string>& args) -> std::string {
if (args.empty()) {
@@ -259,7 +259,9 @@ static auto cmdZoom(const std::vector<std::string>& args) -> std::string {
if (N == Options::window.zoom) { return "Zoom already " + std::to_string(N); }
Screen::get()->setWindowZoom(N);
return "Zoom " + std::to_string(Options::window.zoom);
} catch (...) {}
} catch (...) {
// @INTENTIONAL: argumento no numérico → mostrar usage
}
return "usage: zoom [up|down|<1-" + std::to_string(Screen::getMaxZoom()) + ">]";
}
@@ -704,7 +706,7 @@ static auto cmdEdit(const std::vector<std::string>& args) -> std::string { // N
if ((args[0] == "SHOW" || args[0] == "HIDE") && args.size() >= 2) {
if ((MapEditor::get() == nullptr) || !MapEditor::get()->isActive()) { return "Editor not active"; }
bool show = (args[0] == "SHOW");
if (args[1] == "INFO") { return MapEditor::get()->showInfo(show); }
if (args[1] == "INFO") { return MapEditor::showInfo(show); }
if (args[1] == "GRID") { return MapEditor::get()->showGrid(show); }
}
// EDIT DRAW / EDIT COLLISION
@@ -878,6 +880,7 @@ static auto cmdCheat(const std::vector<std::string>& args) -> std::string { //
auto& cheat = Options::cheats.infinite_lives;
using State = Options::Cheat::State;
const std::vector<std::string> REST(args.begin() + 2, args.end());
// cppcheck-suppress knownConditionTrueFalse -- cppcheck no infiere que REST puede estar vacío cuando args.size() == 2.
if (REST.empty()) {
cheat = (cheat == State::ENABLED) ? State::DISABLED : State::ENABLED;
} else if (REST[0] == "ON") {
@@ -1113,7 +1116,7 @@ void CommandRegistry::registerHandlers() { // NOLINT(readability-function-cogni
if (path.find("tilesets") == std::string::npos) { continue; }
std::string name = getFileName(path);
auto dot = name.rfind('.');
if (dot != std::string::npos) { name = name.substr(0, dot); }
if (dot != std::string::npos) { name.resize(dot); }
result.push_back(toUpper(name));
}
return result;
@@ -1362,7 +1365,7 @@ auto CommandRegistry::getCompletions(const std::string& path) const -> std::vect
if (!active_scope_.empty()) {
std::string root = path;
auto space = root.find(' ');
if (space != std::string::npos) { root = root.substr(0, space); }
if (space != std::string::npos) { root.resize(space); }
const auto* cmd = findCommand(root);
if (cmd != nullptr && !isCommandVisible(*cmd)) { return {}; }
}

View File

@@ -116,8 +116,6 @@ void Notifier::update(float delta_time) {
}
case Status::FINISHED:
break;
default:
break;
}

View File

@@ -2,6 +2,7 @@
#include <SDL3/SDL.h>
#include <cstdint>
#include <memory> // Para shared_ptr
#include <string> // Para string, basic_string
#include <vector> // Para vector
@@ -13,13 +14,13 @@ class DeltaTimer; // lines 11-11
class Notifier {
public:
// Justificado para las notificaciones
enum class TextAlign {
enum class TextAlign : std::uint8_t {
LEFT,
CENTER,
};
// Forma de las notificaciones
enum class Shape {
enum class Shape : std::uint8_t {
ROUNDED,
SQUARED,
};
@@ -65,7 +66,7 @@ class Notifier {
private:
// Tipos anidados
enum class Status {
enum class Status : std::uint8_t {
RISING,
STAY,
VANISHING,