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

@@ -2,20 +2,12 @@ Checks:
- readability-* - readability-*
- modernize-* - modernize-*
- performance-* - performance-*
- bugprone-unchecked-optional-access - bugprone-*
- bugprone-sizeof-expression
- bugprone-suspicious-missing-comma
- bugprone-suspicious-index
- bugprone-undefined-memory-manipulation
- bugprone-use-after-move
- bugprone-out-of-bound-access
- -readability-identifier-length - -readability-identifier-length
- -readability-magic-numbers - -readability-magic-numbers
- -bugprone-narrowing-conversions
- -performance-enum-size
- -performance-inefficient-string-concatenation
- -bugprone-integer-division - -bugprone-integer-division
- -bugprone-easily-swappable-parameters - -bugprone-easily-swappable-parameters
- -bugprone-narrowing-conversions
- -modernize-avoid-c-arrays,-warnings-as-errors - -modernize-avoid-c-arrays,-warnings-as-errors
WarningsAsErrors: '*' WarningsAsErrors: '*'
@@ -25,6 +17,9 @@ HeaderFilterRegex: 'source/(?!core/audio/jail_audio\.hpp|core/rendering/sdl3gpu/
FormatStyle: file FormatStyle: file
CheckOptions: CheckOptions:
# bugprone-empty-catch: aceptar catches vacíos marcados con @INTENTIONAL en un comentario
- { key: bugprone-empty-catch.IgnoreCatchWithKeywords, value: '@INTENTIONAL' }
# Variables locales en snake_case # Variables locales en snake_case
- { key: readability-identifier-naming.VariableCase, value: lower_case } - { key: readability-identifier-naming.VariableCase, value: lower_case }

View File

@@ -117,82 +117,37 @@ xvfb-run -a -s "-screen 0 1280x720x24" ./projecte_2026
--- ---
## Static Analysis Tools (Linters) ## Static Analysis & Formatting (CMake targets)
This project uses two complementary static analysis tools for code quality: Los linters y el formateador están integrados como targets de CMake. Todos excluyen `source/external/` y los headers SPIR-V generados (`*_spv.h`).
### clang-tidy (Modernization & Best Practices)
**Purpose:** Detects style issues, modernization opportunities, and potential bugs. Best for refactoring and applying C++20 best practices.
**IMPORTANT:** Always run on specific files, NOT on the entire project, to avoid long execution times and errors in unrelated files.
```bash ```bash
# Analyze a specific file (RECOMMENDED) # Desde el directorio build/
tools/linter/run_clang-tidy.sh source/game/entities/player.cpp cmake --build . --target tidy # clang-tidy sobre todo el código (sin fixes)
cmake --build . --target tidy-fix # clang-tidy aplicando fixes automáticos
# Analyze multiple specific files cmake --build . --target cppcheck # cppcheck (warning/style/performance/portability, --std=c++20)
tools/linter/run_clang-tidy.sh source/game/entities/player.cpp source/game/entities/enemy.cpp cmake --build . --target format # clang-format -i sobre todo el código
cmake --build . --target format-check # clang-format en modo dry-run (CI)
# Apply fixes automatically to a specific file
tools/linter/run_clang-tidy.sh --fix source/game/entities/player.cpp
# Analyze entire project (SLOW, may have errors in defaults.hpp)
tools/linter/run_clang-tidy.sh
``` ```
**Known False Positives:** Los targets se definen en [CMakeLists.txt](CMakeLists.txt) (sección *STATIC ANALYSIS TARGETS*) y se generan únicamente si las herramientas correspondientes (`clang-tidy`, `clang-format`, `cppcheck`) están instaladas en el sistema.
- `defaults.hpp`: May report errors in constant definitions
- `readability-magic-numbers` / `cppcoreguidelines-avoid-magic-numbers`: Acceptable for game constants (block sizes, speeds, etc.)
### cppcheck (Bug Detection & Memory Safety) **Falsos positivos conocidos de clang-tidy:**
- `defaults.hpp`: puede reportar errores en definiciones de constantes.
- `readability-magic-numbers` / `cppcoreguidelines-avoid-magic-numbers`: aceptables para constantes de juego (tamaños de bloque, velocidades, etc).
**Purpose:** Detects bugs, memory leaks, undefined behavior, and style issues. Complementary to clang-tidy. **Notas sobre cppcheck:**
- Avisos `unusedStructMember` son habituales en estructuras de datos accedidas vía serialización/yaml o por código `#ifdef _DEBUG`; revisar antes de borrar nada.
- El target usa la lista explícita de fuentes (excluyendo `source/external/` y `*_spv.h`), no `--project=compile_commands.json`. Si añades flags nuevos, actualiza el target en [CMakeLists.txt](CMakeLists.txt).
- Por defecto cppcheck solo evalúa 12 combinaciones de `#ifdef`. Para analizar todas, añadir `--force` (mucho más lento).
**Para análisis puntual de un solo fichero**, llamar la herramienta directamente:
```bash ```bash
# Warning, style, and performance analysis (RECOMMENDED for daily use) clang-tidy -p build source/game/entities/player.cpp
tools/linter/run_cppcheck.sh -w --path source/game/entities/player.cpp cppcheck --enable=warning,style --std=c++20 -I source source/game/entities/player.cpp
# Exhaustive analysis (slower, more thorough)
tools/linter/run_cppcheck.sh -a --path source/game/entities/
# Detect unused functions (whole project analysis)
tools/linter/run_cppcheck.sh -u
# Analyze entire project with warning level
tools/linter/run_cppcheck.sh -w
``` ```
**Output:** Results are saved in `tools/linter/cppcheck-result-*.txt`
**Known False Positives:**
- `unusedFunction`: May report false positives for callback functions or public API methods
- `passedByValue`: Acceptable for small types like SDL_FRect in game code
- `constParameter`: Not always applicable for API consistency
### When to Use Each Tool
| Scenario | Tool | Reason |
|----------|------|--------|
| **Daily refactoring** | clang-tidy | Fast, auto-fixable issues |
| **Before committing** | Both | Comprehensive quality check |
| **After major changes** | cppcheck -a | Deep bug analysis |
| **Hunting memory leaks** | cppcheck | Specialized detection |
| **Code modernization** | clang-tidy --fix | Automatic C++20 upgrades |
### Integration with Development Workflow
When refactoring code (especially with `/refactor-class` command):
1. Make structural changes
2. **Compile** to verify syntax
3. **Run clang-tidy** (without --fix) to identify issues
4. **Run cppcheck -w** to catch bugs
5. Review and fix legitimate issues
6. Apply automatic fixes if appropriate
7. Recompile and test
**Note:** The `/refactor-class` command automatically runs both linters after compilation and provides intelligent analysis of results.
--- ---
## Important Code Consistency Rules ## Important Code Consistency Rules

View File

@@ -262,6 +262,7 @@ set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAK
# Buscar herramientas de análisis estático # Buscar herramientas de análisis estático
find_program(CLANG_TIDY_EXE NAMES clang-tidy) find_program(CLANG_TIDY_EXE NAMES clang-tidy)
find_program(CLANG_FORMAT_EXE NAMES clang-format) find_program(CLANG_FORMAT_EXE NAMES clang-format)
find_program(CPPCHECK_EXE NAMES cppcheck)
# Recopilar todos los archivos fuente para formateo # Recopilar todos los archivos fuente para formateo
file(GLOB_RECURSE ALL_SOURCE_FILES file(GLOB_RECURSE ALL_SOURCE_FILES
@@ -278,6 +279,13 @@ set(CLANG_TIDY_SOURCES ${ALL_SOURCE_FILES})
list(FILTER CLANG_TIDY_SOURCES EXCLUDE REGEX ".*jail_audio\\.hpp$") list(FILTER CLANG_TIDY_SOURCES EXCLUDE REGEX ".*jail_audio\\.hpp$")
list(FILTER CLANG_TIDY_SOURCES EXCLUDE REGEX ".*_spv\\.h$") list(FILTER CLANG_TIDY_SOURCES EXCLUDE REGEX ".*_spv\\.h$")
# Para cppcheck, pasar solo .cpp (los headers se procesan transitivamente).
# Si pasamos .hpp como TUs independientes, cppcheck reporta falsos positivos de
# 'unusedStructMember' porque no hace análisis cross-TU y ve miembros de clase
# cuyo uso vive en un .cpp distinto.
set(CPPCHECK_SOURCES ${ALL_SOURCE_FILES})
list(FILTER CPPCHECK_SOURCES INCLUDE REGEX ".*\\.cpp$")
# Targets de clang-tidy # Targets de clang-tidy
if(CLANG_TIDY_EXE) if(CLANG_TIDY_EXE)
add_custom_target(tidy add_custom_target(tidy
@@ -322,6 +330,28 @@ else()
message(STATUS "clang-format no encontrado - targets 'format' y 'format-check' no disponibles") message(STATUS "clang-format no encontrado - targets 'format' y 'format-check' no disponibles")
endif() endif()
# Targets de cppcheck
if(CPPCHECK_EXE)
add_custom_target(cppcheck
COMMAND ${CPPCHECK_EXE}
--enable=warning,style,performance,portability
--std=c++20
--language=c++
--inline-suppr
--suppress=missingIncludeSystem
--suppress=toomanyconfigs
-D_DEBUG
-DLINUX_BUILD
--quiet
-I ${CMAKE_SOURCE_DIR}/source
${CPPCHECK_SOURCES}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Running cppcheck..."
)
else()
message(STATUS "cppcheck no encontrado - target 'cppcheck' no disponible")
endif()
# --- 6. PACK RESOURCES TARGETS --- # --- 6. PACK RESOURCES TARGETS ---
set(PACK_TOOL_SOURCES set(PACK_TOOL_SOURCES
${CMAKE_SOURCE_DIR}/tools/pack_resources/pack_resources.cpp ${CMAKE_SOURCE_DIR}/tools/pack_resources/pack_resources.cpp

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <cstdint> // Para int8_t, uint8_t
#include <string> // Para string #include <string> // Para string
#include <utility> // Para move #include <utility> // Para move
@@ -7,13 +8,13 @@
class Audio { class Audio {
public: public:
// --- Enums --- // --- Enums ---
enum class Group : int { enum class Group : std::int8_t {
ALL = -1, // Todos los grupos ALL = -1, // Todos los grupos
GAME = 0, // Sonidos del juego GAME = 0, // Sonidos del juego
INTERFACE = 1 // Sonidos de la interfaz INTERFACE = 1 // Sonidos de la interfaz
}; };
enum class MusicState { enum class MusicState : std::uint8_t {
PLAYING, // Reproduciendo música PLAYING, // Reproduciendo música
PAUSED, // Música pausada PAUSED, // Música pausada
STOPPED, // Música detenida 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); return addGamepad(event.gdevice.which);
case SDL_EVENT_GAMEPAD_REMOVED: case SDL_EVENT_GAMEPAD_REMOVED:
return removeGamepad(event.gdevice.which); return removeGamepad(event.gdevice.which);
default:
return {};
} }
return {};
} }
auto Input::addGamepad(int device_index) -> std::string { // NOLINT(readability-convert-member-functions-to-static) 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 <SDL3/SDL.h>
#include <cstdint>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
// --- Enums --- // --- 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 // Inputs de movimiento
LEFT, LEFT,
RIGHT, RIGHT,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -292,12 +292,12 @@ void Screen::adjustWindowSize() {
// Reservamos memoria una sola vez. // Reservamos memoria una sola vez.
// Si el buffer es más pequeño que la superficie, crash asegurado. // 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_)); 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 * Options::game.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). // border_pixel_buffer_ es el buffer que se sube a la GPU (tamaño total ventana).
if (Options::video.border.enabled) { 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 // 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. // 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. // 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) // 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_.data(), static_cast<size_t>(OFF_Y) * BORDER_W_SZ, border_argb_color_);
std::fill_n(&border_pixel_buffer_[(OFF_Y + GAME_H) * BORDER_W], std::fill_n(&border_pixel_buffer_[(static_cast<size_t>(OFF_Y) + GAME_H) * BORDER_W_SZ],
(BORDER_H - OFF_Y - GAME_H) * BORDER_W, (static_cast<size_t>(BORDER_H - OFF_Y - GAME_H)) * BORDER_W_SZ,
border_argb_color_); border_argb_color_);
// Columnas laterales en las filas del área de juego // Columnas laterales en las filas del área de juego
for (int y = OFF_Y; y < OFF_Y + GAME_H; ++y) { 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_[static_cast<size_t>(y) * BORDER_W_SZ], 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 + GAME_W],
BORDER_W - OFF_X - GAME_W, BORDER_W - OFF_X - GAME_W,
border_argb_color_); border_argb_color_);
} }
@@ -413,9 +415,9 @@ void Screen::textureToRenderer() {
// Overlay del juego sobre el centro del buffer (ambos paths) // Overlay del juego sobre el centro del buffer (ambos paths)
game_surface_->toARGBBuffer(game_pixel_buffer_.data()); game_surface_->toARGBBuffer(game_pixel_buffer_.data());
for (int y = 0; y < GAME_H; ++y) { for (int y = 0; y < GAME_H; ++y) {
const Uint32* src = &game_pixel_buffer_[y * GAME_W]; const Uint32* src = &game_pixel_buffer_[static_cast<size_t>(y) * GAME_W_SZ];
Uint32* dst = &border_pixel_buffer_[((OFF_Y + y) * BORDER_W) + OFF_X]; Uint32* dst = &border_pixel_buffer_[((static_cast<size_t>(OFF_Y) + y) * BORDER_W_SZ) + OFF_X];
std::memcpy(dst, src, GAME_W * sizeof(Uint32)); std::memcpy(dst, src, GAME_W_SZ * sizeof(Uint32));
} }
shader_backend_->uploadPixels(border_pixel_buffer_.data(), BORDER_W, BORDER_H); 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 <SDL3/SDL_pixels.h> // Para Uint32
#include <cstddef> // Para size_t #include <cstddef> // Para size_t
#include <cstdint> // Para uint8_t
#include <memory> // Para shared_ptr, __shared_ptr_access #include <memory> // Para shared_ptr, __shared_ptr_access
#include <string> // Para string #include <string> // Para string
#include <utility> // Para std::pair #include <utility> // Para std::pair
@@ -18,7 +19,7 @@ class Text;
class Screen { class Screen {
public: public:
// Tipos de filtro // Tipos de filtro
enum class Filter : Uint32 { enum class Filter : std::uint8_t {
NEAREST = 0, NEAREST = 0,
LINEAR = 1, LINEAR = 1,
}; };

View File

@@ -762,7 +762,7 @@ namespace Rendering {
} }
// Copia directa — el upscale lo hace la GPU en el primer render pass // 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_); SDL_UnmapGPUTransferBuffer(device_, upload_buffer_);
} }

View File

@@ -2,13 +2,14 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <cstdint>
#include <string> #include <string>
#include <utility> #include <utility>
namespace Rendering { namespace Rendering {
/** @brief Identificador del shader de post-procesado activo */ /** @brief Identificador del shader de post-procesado activo */
enum class ShaderType { POSTFX, enum class ShaderType : std::uint8_t { POSTFX,
CRTPI }; 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>()); animation.speeds.push_back(s.get_value<float>());
} }
} else { } else {
float spd = speed_node.get_value<float>(); auto spd = speed_node.get_value<float>();
if (spd > 0.0F) { if (spd > 0.0F) {
animation.speeds.assign(animation.frames.size(), spd); animation.speeds.assign(animation.frames.size(), spd);
} }

View File

@@ -2,6 +2,7 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <cstdint>
#include <memory> // Para shared_ptr #include <memory> // Para shared_ptr
#include "core/rendering/sprite/animated_sprite.hpp" // Para SurfaceAnimatedSprite #include "core/rendering/sprite/animated_sprite.hpp" // Para SurfaceAnimatedSprite
@@ -9,7 +10,7 @@
class Surface; class Surface;
// Direcció de la dissolució // Direcció de la dissolució
enum class DissolveDirection { NONE, enum class DissolveDirection : std::uint8_t { NONE,
DOWN, DOWN,
UP }; UP };
@@ -41,7 +42,7 @@ class DissolveSprite : public AnimatedSprite {
void setColorReplace(Uint8 source, Uint8 target); void setColorReplace(Uint8 source, Uint8 target);
private: private:
enum class TransitionMode { NONE, enum class TransitionMode : std::uint8_t { NONE,
DISSOLVING, DISSOLVING,
GENERATING }; 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) // Rellenar fila a fila con memset (memoria contigua por fila)
Uint8* data_ptr = surface_data_->data.get(); Uint8* data_ptr = surface_data_->data.get();
const int SURF_WIDTH = static_cast<int>(surface_data_->width); const auto SURF_WIDTH = static_cast<size_t>(surface_data_->width);
const int ROW_WIDTH = static_cast<int>(x_end) - static_cast<int>(x_start); 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) { 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) // Dibujar bordes horizontales con memset (líneas contiguas en memoria)
Uint8* data_ptr = surface_data_->data.get(); Uint8* data_ptr = surface_data_->data.get();
const int SURF_WIDTH = static_cast<int>(surface_data_->width); const auto SURF_WIDTH = static_cast<size_t>(surface_data_->width);
const int ROW_WIDTH = static_cast<int>(x_end) - static_cast<int>(x_start); const auto ROW_WIDTH = static_cast<size_t>(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<size_t>(y_start) * SURF_WIDTH) + static_cast<size_t>(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); 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 // Dibujar bordes verticales
for (int y = y_start; y < y_end; ++y) { 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; float h = (src_rect != nullptr) ? src_rect->h : surface_data_->height;
// Guardar dimensiones originales antes del clipping (necesarias para flip) // 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_w = (src_rect != nullptr) ? src_rect->w : surface_data_->width;
float orig_h = (src_rect != nullptr) ? src_rect->h : static_cast<float>(surface_data_->height); float orig_h = (src_rect != nullptr) ? src_rect->h : surface_data_->height;
// Limitar la región para evitar accesos fuera de rango en origen // Limitar la región para evitar accesos fuera de rango en origen
w = std::min(w, surface_data_->width - sx); 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) // 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 // Aplicar render offset
x += Screen::get()->getRenderOffsetX(); x += Screen::get()->getRenderOffsetX();
y += Screen::get()->getRenderOffsetY(); 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 // 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 // Aplicar render offset
x += Screen::get()->getRenderOffsetX(); x += Screen::get()->getRenderOffsetX();
y += Screen::get()->getRenderOffsetY(); 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 WIDTH = surface_data_->width;
const int HEIGHT = surface_data_->height; const int HEIGHT = surface_data_->height;
for (int y = 0; y < HEIGHT; ++y) { for (int y = 0; y < HEIGHT; ++y) {
const Uint8* src_row = src + (y * WIDTH); const Uint8* src_row = src + (static_cast<size_t>(y) * static_cast<size_t>(WIDTH));
Uint32* dst_row = pixels + (y * row_stride); Uint32* dst_row = pixels + (static_cast<size_t>(y) * static_cast<size_t>(row_stride));
for (int x = 0; x < WIDTH; ++x) { for (int x = 0; x < WIDTH; ++x) {
dst_row[x] = pal[src_row[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 WIDTH = surface_data_->width;
const int HEIGHT = surface_data_->height; const int HEIGHT = surface_data_->height;
for (int y = 0; y < HEIGHT; ++y) { for (int y = 0; y < HEIGHT; ++y) {
const Uint8* src_row = src + (y * WIDTH); const Uint8* src_row = src + (static_cast<size_t>(y) * static_cast<size_t>(WIDTH));
Uint32* dst_row = pixels + (y * row_stride); Uint32* dst_row = pixels + (static_cast<size_t>(y) * static_cast<size_t>(row_stride));
for (int x = 0; x < WIDTH; ++x) { for (int x = 0; x < WIDTH; ++x) {
dst_row[x] = pal[src_row[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; 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) // 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) // 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 // Establece un color en la paleta
void setColor(int index, Uint32 color); 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) { if (c < 0x80) {
cp = c; cp = c;
extra = 0; extra = 0;
} else if (c < 0xC0) { } else if (c < 0xC0 || c >= 0xF8) {
// Byte de continuación suelto o lead byte inválido
pos++; pos++;
return 0xFFFD; return 0xFFFD;
} // byte de continuación suelto } else if (c < 0xE0) {
else if (c < 0xE0) {
cp = c & 0x1F; cp = c & 0x1F;
extra = 1; extra = 1;
} else if (c < 0xF0) { } else if (c < 0xF0) {
cp = c & 0x0F; cp = c & 0x0F;
extra = 2; extra = 2;
} else if (c < 0xF8) { } else {
cp = c & 0x07; cp = c & 0x07;
extra = 3; extra = 3;
} else {
pos++;
return 0xFFFD;
} }
pos++; pos++;
@@ -291,7 +288,7 @@ void Text::writeDX(Uint8 flags, int x, int y, const std::string& text, int kerni
if (COLORED) { if (COLORED) {
writeColored(x, y, text, text_color, kerning, lenght); writeColored(x, y, text, text_color, kerning, lenght);
} else { } 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 // Obtiene todas las habitaciones
auto Cache::getRooms() -> std::vector<RoomResource>& { auto Cache::getRooms() -> std::vector<RoomResource>& {
if (loading_mode_ == LoadingMode::LAZY) { if (loading_mode_ == LoadingMode::LAZY) {
for (auto& r : rooms_) { for (const auto& r : rooms_) {
if (r.room == nullptr) { loadRoomByName(r.name); } if (r.room == nullptr) { loadRoomByName(r.name); }
} }
} }
@@ -248,6 +248,7 @@ namespace Resource {
std::cerr << "[ ERROR ] Path: " << file_path << '\n'; std::cerr << "[ ERROR ] Path: " << file_path << '\n';
std::cerr << "[ ERROR ] Reason: " << e.what() << '\n'; std::cerr << "[ ERROR ] Reason: " << e.what() << '\n';
std::cerr << "[ ERROR ] Check config/assets.yaml configuration\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; throw;
} }
@@ -548,6 +549,8 @@ namespace Resource {
exit(0); exit(0);
} }
break; break;
default:
break;
} }
} }
} }

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <cstdint>
#include <memory> // Para shared_ptr #include <memory> // Para shared_ptr
#include <string> // Para string #include <string> // Para string
#include <utility> #include <utility>
@@ -11,7 +12,7 @@ namespace Resource {
class Cache { class Cache {
public: public:
enum class LoadingMode { enum class LoadingMode : std::uint8_t {
EAGER, // Carga todos los recursos en init() (comportamiento por defecto, producción) 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) 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 // 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('/'))); auto last_pos = content.rfind(var_path.substr(0, var_path.rfind('/')));
if (last_pos != std::string::npos) { if (last_pos != std::string::npos) {
auto end_of_line = content.find('\n', last_pos); auto end_of_line = content.find('\n', last_pos);
if (end_of_line != std::string::npos) { if (end_of_line != std::string::npos) {
std::string entry = " - " + var_path + "\n";
content.insert(end_of_line + 1, entry); content.insert(end_of_line + 1, entry);
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -92,7 +92,7 @@ void MapEditor::loadSettings() {
} }
} }
} catch (...) { } 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 painting_ = false; // Siempre dejar de pintar al cambiar de room
// Asegurar que collision_tile_map tiene el tamaño correcto // 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)) { 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(Map::WIDTH * Map::HEIGHT, 0); room_data_.collision_tile_map.resize(static_cast<size_t>(Map::WIDTH) * static_cast<size_t>(Map::HEIGHT), 0);
} }
active_ = true; active_ = true;
@@ -361,30 +361,31 @@ void MapEditor::update(float delta_time) {
} }
// Renderiza el editor // 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() { void MapEditor::render() {
// El tilemap ya ha sido renderizado por Game::renderPlaying() antes de llamar aquí // El tilemap ya ha sido renderizado por Game::renderPlaying() antes de llamar aquí
// Si estamos editando colisiones, superponer el mapa de colisiones // Si estamos editando colisiones, superponer el mapa de colisiones
if (editing_collision_) { if (editing_collision_) { renderCollisionOverlay(); }
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);
}
}
}
}
// Grid (debajo de todo) // Grid (debajo de todo)
if (settings_.grid) { if (settings_.grid) {
@@ -526,7 +527,7 @@ void MapEditor::handleEvent(const SDL_Event& event) { // NOLINT(readability-fun
// Deseleccionar entidades // Deseleccionar entidades
selection_.clear(); 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 tile_index = (mouse_tile_y_ * 32) + mouse_tile_x_;
int current = 0; int current = 0;
if (editing_collision_) { if (editing_collision_) {
@@ -539,10 +540,10 @@ void MapEditor::handleEvent(const SDL_Event& event) { // NOLINT(readability-fun
: -1; : -1;
} }
tile_picker_.on_select = [this, tileset_name](int col, int row, int width, int height) { tile_picker_.on_select = [this, TILESET_NAME](int col, int row, int width, int height) {
brush_ = buildPatternFromTileset(tileset_name, col, row, width, 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; return;
} }
@@ -664,7 +665,7 @@ void MapEditor::handleMouseUp() {
if (selection_.is(drag_.entity_type) && selection_.index == drag_.index) { if (selection_.is(drag_.entity_type) && selection_.index == drag_.index) {
selection_.clear(); // deselect selection_.clear(); // deselect
} else { } else {
selection_ = {drag_.entity_type, drag_.index}; // select selection_ = {.type = drag_.entity_type, .index = drag_.index}; // select
} }
} else { } else {
selection_.clear(); selection_.clear();
@@ -696,7 +697,8 @@ void MapEditor::handleMouseUp() {
drag_ = {}; 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 { auto MapEditor::commitEntityDrag() -> bool {
const int IDX = drag_.index; const int IDX = drag_.index;
const int SNAP_X = static_cast<int>(drag_.snap_x); 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].x = drag_.snap_x;
room_data_.enemies[IDX].y = drag_.snap_y; room_data_.enemies[IDX].y = drag_.snap_y;
room_->getEnemyManager()->getEnemy(IDX)->resetToInitialPosition(room_data_.enemies[IDX]); room_->getEnemyManager()->getEnemy(IDX)->resetToInitialPosition(room_data_.enemies[IDX]);
selection_ = {EntityType::ENEMY, IDX}; selection_ = {.type = EntityType::ENEMY, .index = IDX};
return true; return true;
} }
break; break;
case EntityType::ITEM: case EntityType::ITEM:
if (IDX >= 0 && IDX < room_->getItemManager()->getCount()) { if (IDX >= 0 && IDX < room_->getItemManager()->getCount()) {
room_->getItemManager()->getItem(IDX)->setPosition(drag_.snap_x, drag_.snap_y); room_->getItemManager()->getItem(IDX)->setPosition(drag_.snap_x, drag_.snap_y);
selection_ = {EntityType::ITEM, IDX}; selection_ = {.type = EntityType::ITEM, .index = IDX};
return true; return true;
} }
break; break;
@@ -733,7 +735,7 @@ auto MapEditor::commitEntityDrag() -> bool {
} }
} }
room_->getPlatformManager()->getPlatform(IDX)->resetToInitialPosition(plat); room_->getPlatformManager()->getPlatform(IDX)->resetToInitialPosition(plat);
selection_ = {EntityType::PLATFORM, IDX}; selection_ = {.type = EntityType::PLATFORM, .index = IDX};
return true; return true;
} }
break; break;
@@ -744,7 +746,7 @@ auto MapEditor::commitEntityDrag() -> bool {
// sprite→data igual que con items. // sprite→data igual que con items.
room_data_.keys[IDX].x = drag_.snap_x; room_data_.keys[IDX].x = drag_.snap_x;
room_data_.keys[IDX].y = drag_.snap_y; room_data_.keys[IDX].y = drag_.snap_y;
selection_ = {EntityType::KEY, IDX}; selection_ = {.type = EntityType::KEY, .index = IDX};
return true; return true;
} }
break; break;
@@ -756,7 +758,7 @@ auto MapEditor::commitEntityDrag() -> bool {
room_data_.doors[IDX].x = drag_.snap_x; room_data_.doors[IDX].x = drag_.snap_x;
room_data_.doors[IDX].y = drag_.snap_y; room_data_.doors[IDX].y = drag_.snap_y;
room_->getDoorManager()->moveDoor(IDX, drag_.snap_x, 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; return true;
} }
break; break;
@@ -772,7 +774,7 @@ auto MapEditor::commitEntityDrag() -> bool {
room_data_.enemies[IDX].x1 = SNAP_X; room_data_.enemies[IDX].x1 = SNAP_X;
room_data_.enemies[IDX].y1 = SNAP_Y; room_data_.enemies[IDX].y1 = SNAP_Y;
room_->getEnemyManager()->getEnemy(IDX)->resetToInitialPosition(room_data_.enemies[IDX]); room_->getEnemyManager()->getEnemy(IDX)->resetToInitialPosition(room_data_.enemies[IDX]);
selection_ = {EntityType::ENEMY, IDX}; selection_ = {.type = EntityType::ENEMY, .index = IDX};
return true; return true;
} }
break; break;
@@ -788,7 +790,7 @@ auto MapEditor::commitEntityDrag() -> bool {
room_data_.enemies[IDX].x2 = SNAP_X; room_data_.enemies[IDX].x2 = SNAP_X;
room_data_.enemies[IDX].y2 = SNAP_Y; room_data_.enemies[IDX].y2 = SNAP_Y;
room_->getEnemyManager()->getEnemy(IDX)->resetToInitialPosition(room_data_.enemies[IDX]); room_->getEnemyManager()->getEnemy(IDX)->resetToInitialPosition(room_data_.enemies[IDX]);
selection_ = {EntityType::ENEMY, IDX}; selection_ = {.type = EntityType::ENEMY, .index = IDX};
return true; return true;
} }
break; break;
@@ -803,7 +805,8 @@ auto MapEditor::commitEntityDrag() -> bool {
return false; 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() { void MapEditor::moveEntityVisual() {
switch (drag_.target) { switch (drag_.target) {
case DragTarget::ENTITY_INITIAL: case DragTarget::ENTITY_INITIAL:
@@ -857,8 +860,6 @@ void MapEditor::moveEntityVisual() {
case DragTarget::ENTITY_BOUND1: case DragTarget::ENTITY_BOUND1:
case DragTarget::ENTITY_BOUND2: case DragTarget::ENTITY_BOUND2:
// Los boundaries se actualizan visualmente en renderEntityBoundaries() via drag_.snap // Los boundaries se actualizan visualmente en renderEntityBoundaries() via drag_.snap
break;
default: default:
break; break;
} }
@@ -946,7 +947,8 @@ void MapEditor::renderSelectionHighlight() {
game_surface->drawRectBorder(&border, DRAG_COLOR); 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) { void MapEditor::stampBrushAt(int tile_x, int tile_y) {
if (brush_.isEmpty()) { return; } if (brush_.isEmpty()) { return; }
for (int dy = 0; dy < brush_.height; ++dy) { 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; BrushPattern p;
p.width = (x2 - x1) + 1; p.width = (x2 - x1) + 1;
p.height = (y2 - y1) + 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; const auto& src = editing_collision_ ? room_data_.collision_tile_map : room_data_.tile_map;
for (int y = y1; y <= y2; ++y) { for (int y = y1; y <= y2; ++y) {
for (int x = x1; x <= x2; ++x) { 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. // Construye un BrushPattern leyendo tiles consecutivos de un tileset.
// Usado por el TilePicker cuando se hace selección rectangular. // 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; BrushPattern p;
auto surface = Resource::Cache::get()->getSurface(tileset_name); auto surface = Resource::Cache::get()->getSurface(tileset_name);
if (!surface || width <= 0 || height <= 0) { return p; } 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; } if (cols <= 0) { return p; }
p.width = width; p.width = width;
p.height = height; 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 dy = 0; dy < height; ++dy) {
for (int dx = 0; dx < width; ++dx) { for (int dx = 0; dx < width; ++dx) {
int tile = ((row + dy) * cols) + (col + dx); int tile = ((row + dy) * cols) + (col + dx);
@@ -1036,8 +1038,8 @@ void MapEditor::renderBrushPreview() {
auto game_surface = Screen::get()->getRendererSurface(); auto game_surface = Screen::get()->getRendererSurface();
if (!game_surface) { return; } if (!game_surface) { return; }
const std::string tileset_name = editing_collision_ ? std::string("collision.gif") : room_->getTileSetFile(); const std::string TILESET_NAME = editing_collision_ ? std::string("collision.gif") : room_->getTileSetFile();
auto tileset = Resource::Cache::get()->getSurface(tileset_name); auto tileset = Resource::Cache::get()->getSurface(TILESET_NAME);
int cols = (tileset) ? (static_cast<int>(tileset->getWidth()) / Tile::SIZE) : 0; int cols = (tileset) ? (static_cast<int>(tileset->getWidth()) / Tile::SIZE) : 0;
constexpr auto TS = static_cast<float>(Tile::SIZE); constexpr auto TS = static_cast<float>(Tile::SIZE);
@@ -1052,7 +1054,7 @@ void MapEditor::renderBrushPreview() {
float dst_y = static_cast<float>(ty) * TS; float dst_y = static_cast<float>(ty) * TS;
if (value == BrushPattern::ERASE) { if (value == BrushPattern::ERASE) {
SDL_FRect erase_cell = {.x = dst_x, .y = dst_y, .w = TS, .h = TS}; 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) { } else if (tileset && cols > 0) {
SDL_FRect src = { SDL_FRect src = {
.x = static_cast<float>(value % cols) * TS, .x = static_cast<float>(value % cols) * TS,
@@ -1077,7 +1079,7 @@ void MapEditor::renderBrushPreview() {
} }
// Renderiza el rectángulo del eyedropper en progreso (cyan brillante) // Renderiza el rectángulo del eyedropper en progreso (cyan brillante)
void MapEditor::renderEyedropperRect() { void MapEditor::renderEyedropperRect() const {
auto game_surface = Screen::get()->getRendererSurface(); auto game_surface = Screen::get()->getRendererSurface();
if (!game_surface) { return; } if (!game_surface) { return; }
int x1 = std::clamp(eyedropper_.start_tile_x, 0, Map::WIDTH - 1); 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) { switch (type) {
case EntityType::ENEMY: { case EntityType::ENEMY: {
const auto& e = room_data_.enemies[index]; 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: default:
return {}; 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() { void MapEditor::renderEntityBoundaries() {
auto game_surface = Screen::get()->getRendererSurface(); auto game_surface = Screen::get()->getRendererSurface();
if (!game_surface) { return; } if (!game_surface) { return; }
@@ -1395,7 +1398,7 @@ void MapEditor::updateStatusBarInfo() { // NOLINT(readability-function-cognitiv
const auto& e = room_data_.enemies[selection_.index]; const auto& e = room_data_.enemies[selection_.index];
std::string anim = e.animation_path; std::string anim = e.animation_path;
auto dot = anim.rfind('.'); 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; line2 = "enemy " + std::to_string(selection_.index) + ": " + anim;
line3 = "vx:" + std::to_string(static_cast<int>(e.vx)) + 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())) { if (selection_.index < static_cast<int>(room_data_.platforms.size())) {
const auto& p = room_data_.platforms[selection_.index]; const auto& p = room_data_.platforms[selection_.index];
std::string anim = p.animation_path; 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; line2 = "platform " + std::to_string(selection_.index) + ": " + anim;
line3 = "speed:" + std::to_string(static_cast<int>(p.speed)) + " " + (p.loop == LoopMode::CIRCULAR ? "circular" : "pingpong"); line3 = "speed:" + std::to_string(static_cast<int>(p.speed)) + " " + (p.loop == LoopMode::CIRCULAR ? "circular" : "pingpong");
if (p.easing != "linear") { line3 += " " + p.easing; } 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]; const auto& k = room_data_.keys[selection_.index];
std::string anim = k.animation_path; std::string anim = k.animation_path;
auto dot = anim.rfind('.'); 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; line2 = "key " + std::to_string(selection_.index) + ": " + anim;
line3 = "id: " + k.id; line3 = "id: " + k.id;
} }
@@ -1443,7 +1446,7 @@ void MapEditor::updateStatusBarInfo() { // NOLINT(readability-function-cognitiv
const auto& d = room_data_.doors[selection_.index]; const auto& d = room_data_.doors[selection_.index];
std::string anim = d.animation_path; std::string anim = d.animation_path;
auto dot = anim.rfind('.'); 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; line2 = "door " + std::to_string(selection_.index) + ": " + anim;
line3 = "id: " + d.id; line3 = "id: " + d.id;
} }
@@ -1495,7 +1498,6 @@ auto MapEditor::getSetCompletions() const -> std::vector<std::string> {
case EntityType::PLATFORM: case EntityType::PLATFORM:
return {"ANIMATION", "SPEED", "LOOP", "EASING"}; return {"ANIMATION", "SPEED", "LOOP", "EASING"};
case EntityType::KEY: case EntityType::KEY:
return {"ID", "ANIMATION"};
case EntityType::DOOR: case EntityType::DOOR:
return {"ID", "ANIMATION"}; return {"ID", "ANIMATION"};
default: default:
@@ -1534,7 +1536,7 @@ auto MapEditor::getAnimationCompletions() const -> std::vector<std::string> {
if (path.extension() != ".yaml") { continue; } if (path.extension() != ".yaml") { continue; }
result.push_back(toUpper(path.stem().string())); result.push_back(toUpper(path.stem().string()));
} }
std::sort(result.begin(), result.end()); std::ranges::sort(result);
return result; return result;
} }
@@ -1652,7 +1654,7 @@ auto MapEditor::addEnemy() -> std::string {
// Seleccionar el nuevo enemigo // Seleccionar el nuevo enemigo
int new_index = static_cast<int>(room_data_.enemies.size()) - 1; int new_index = static_cast<int>(room_data_.enemies.size()) - 1;
selection_ = {EntityType::ENEMY, new_index}; selection_ = {.type = EntityType::ENEMY, .index = new_index};
autosave(); autosave();
return "Added enemy " + std::to_string(new_index); return "Added enemy " + std::to_string(new_index);
@@ -1699,7 +1701,7 @@ auto MapEditor::duplicateEnemy() -> std::string {
// Seleccionar el nuevo enemigo // Seleccionar el nuevo enemigo
int new_index = static_cast<int>(room_data_.enemies.size()) - 1; int new_index = static_cast<int>(room_data_.enemies.size()) - 1;
selection_ = {EntityType::ENEMY, new_index}; selection_ = {.type = EntityType::ENEMY, .index = new_index};
autosave(); autosave();
return "Duplicated as enemy " + std::to_string(new_index); 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 // Comprobar que no hay ya una room en esa dirección
if (!direction.empty()) { if (!direction.empty()) {
std::string* existing = nullptr; const std::string* existing = nullptr;
if (direction == "UP") { if (direction == "UP") {
existing = &room_data_.upper_room; existing = &room_data_.upper_room;
} else if (direction == "DOWN") { } 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) // 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; std::set<int> used;
for (const auto& r : rooms) { for (const auto& r : rooms) {
try { try {
used.insert(std::stoi(r.name.substr(0, r.name.find('.')))); 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; int new_num = 1;
while (used.contains(new_num)) { ++new_num; } 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) // Persistir vía la autoridad del formato (no más std::ofstream a pelo)
auto save_result = RoomFormat::saveYAML(new_path, new_room); 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 // Registrar en Resource::List (mapa + assets.yaml) y cache
Resource::List::get()->addAsset(new_path, Resource::List::Type::ROOM); 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)); room_->getItemManager()->addItem(std::make_shared<Item>(new_item));
int new_index = static_cast<int>(room_data_.items.size()) - 1; int new_index = static_cast<int>(room_data_.items.size()) - 1;
selection_ = {EntityType::ITEM, new_index}; selection_ = {.type = EntityType::ITEM, .index = new_index};
autosave(); autosave();
return "Added item " + std::to_string(new_index); 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)); room_->getItemManager()->addItem(std::make_shared<Item>(copy));
int new_index = static_cast<int>(room_data_.items.size()) - 1; int new_index = static_cast<int>(room_data_.items.size()) - 1;
selection_ = {EntityType::ITEM, new_index}; selection_ = {.type = EntityType::ITEM, .index = new_index};
autosave(); autosave();
return "Duplicated as item " + std::to_string(new_index); 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 CENTER_Y = PlayArea::CENTER_Y;
constexpr float ROUTE_HALF = 2.0F * Tile::SIZE; constexpr float ROUTE_HALF = 2.0F * Tile::SIZE;
new_platform.path = { new_platform.path = {
{CENTER_X - ROUTE_HALF, CENTER_Y, 0.0F}, {.x = CENTER_X - ROUTE_HALF, .y = CENTER_Y, .wait = 0.0F},
{CENTER_X + ROUTE_HALF, CENTER_Y, 0.0F}}; {.x = CENTER_X + ROUTE_HALF, .y = CENTER_Y, .wait = 0.0F}};
room_data_.platforms.push_back(new_platform); room_data_.platforms.push_back(new_platform);
room_->getPlatformManager()->addPlatform(std::make_shared<MovingPlatform>(new_platform)); room_->getPlatformManager()->addPlatform(std::make_shared<MovingPlatform>(new_platform));
int new_index = static_cast<int>(room_data_.platforms.size()) - 1; int new_index = static_cast<int>(room_data_.platforms.size()) - 1;
selection_ = {EntityType::PLATFORM, new_index}; selection_ = {.type = EntityType::PLATFORM, .index = new_index};
autosave(); autosave();
return "Added platform " + std::to_string(new_index); 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)); room_->getPlatformManager()->addPlatform(std::make_shared<MovingPlatform>(copy));
int new_index = static_cast<int>(room_data_.platforms.size()) - 1; int new_index = static_cast<int>(room_data_.platforms.size()) - 1;
selection_ = {EntityType::PLATFORM, new_index}; selection_ = {.type = EntityType::PLATFORM, .index = new_index};
autosave(); autosave();
return "Duplicated as platform " + std::to_string(new_index); 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; int new_index = static_cast<int>(room_data_.keys.size()) - 1;
selection_ = {EntityType::KEY, new_index}; selection_ = {.type = EntityType::KEY, .index = new_index};
autosave(); autosave();
return "Added key " + std::to_string(new_index); 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; int new_index = static_cast<int>(room_data_.keys.size()) - 1;
selection_ = {EntityType::KEY, new_index}; selection_ = {.type = EntityType::KEY, .index = new_index};
autosave(); autosave();
return "Duplicated as key " + std::to_string(new_index); 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; int new_index = static_cast<int>(room_data_.doors.size()) - 1;
selection_ = {EntityType::DOOR, new_index}; selection_ = {.type = EntityType::DOOR, .index = new_index};
autosave(); autosave();
return "Added door " + std::to_string(new_index); 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; int new_index = static_cast<int>(room_data_.doors.size()) - 1;
selection_ = {EntityType::DOOR, new_index}; selection_ = {.type = EntityType::DOOR, .index = new_index};
autosave(); autosave();
return "Duplicated as door " + std::to_string(new_index); 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) // 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(); auto game_surface = Screen::get()->getRendererSurface();
if (!game_surface) { return; } if (!game_surface) { return; }

View File

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

View File

@@ -166,8 +166,8 @@ void TilePicker::render() {
int cells_h = row_max - row_min + 1; int cells_h = row_max - row_min + 1;
float rx = tileset_screen_x + static_cast<float>(col_min * out_cell); 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 ry = tileset_screen_y + static_cast<float>(row_min * out_cell);
float rw = static_cast<float>((cells_w * out_cell) - spacing_out_); auto rw = static_cast<float>((cells_w * out_cell) - spacing_out_);
float rh = static_cast<float>((cells_h * 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_)) { if (ry + rh > 0 && ry < static_cast<float>(visible_height_)) {
SDL_FRect rect_box = {.x = rx, .y = ry, .w = rw, .h = rh}; SDL_FRect rect_box = {.x = rx, .y = ry, .w = rw, .h = rh};
game_surface->drawRectBorder(&rect_box, 15); 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 // Maneja eventos del picker
void TilePicker::handleEvent(const SDL_Event& event) { void TilePicker::handleEvent(const SDL_Event& event) {
if (!open_) { return; } 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_) { 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_; commitRectSelection();
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();
} }
if (event.type == SDL_EVENT_MOUSE_WHEEL) { if (event.type == SDL_EVENT_MOUSE_WHEEL) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@
CollisionMap::CollisionMap(std::vector<int> collision_tile_map) CollisionMap::CollisionMap(std::vector<int> collision_tile_map)
: collision_tile_map_(std::move(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) { tile_collider_(extended_tile_map_, EW, EH, CollisionBorder::PX) {
buildExtendedCenter(); buildExtendedCenter();
} }

View File

@@ -2,6 +2,7 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <cstdint>
#include <memory> // Para shared_ptr #include <memory> // Para shared_ptr
#include <string> // Para string #include <string> // Para string
#include <vector> // Para vector #include <vector> // Para vector
@@ -28,7 +29,7 @@ class TilemapRenderer;
class Room { class Room {
public: public:
// -- Enumeraciones y estructuras --- // -- Enumeraciones y estructuras ---
enum class Border : int { enum class Border : std::uint8_t {
TOP = 0, TOP = 0,
RIGHT = 1, RIGHT = 1,
BOTTOM = 2, 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> { auto RoomFormat::flattenTilemap(const std::vector<std::vector<int>>& tilemap_2d) -> std::vector<int> {
std::vector<int> tilemap_flat; 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 (const auto& row : tilemap_2d) {
for (int tile : row) { for (int tile : row) {
@@ -535,8 +535,8 @@ auto RoomFormat::createDefault() -> Room::Data {
data.right_room = "0"; data.right_room = "0";
// Tilemaps del tamaño correcto, vacíos // Tilemaps del tamaño correcto, vacíos
data.tile_map.resize(Map::WIDTH * Map::HEIGHT, -1); data.tile_map.resize(static_cast<size_t>(Map::WIDTH) * static_cast<size_t>(Map::HEIGHT), -1);
data.collision_tile_map.resize(Map::WIDTH * Map::HEIGHT, 0); data.collision_tile_map.resize(static_cast<size_t>(Map::WIDTH) * static_cast<size_t>(Map::HEIGHT), 0);
return data; return data;
} }

View File

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

View File

@@ -39,6 +39,7 @@ class Scoreboard {
// Constantes de tiempo // Constantes de tiempo
// Posición de los elementos (2 líneas centradas verticalmente en surface de 24px) // 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 LINE1_Y = 5;
static constexpr int LINE2_Y = 13; static constexpr int LINE2_Y = 13;

View File

@@ -1,12 +1,13 @@
#pragma once #pragma once
#include <cstdint>
#include <vector> #include <vector>
#include "utils/defines.hpp" #include "utils/defines.hpp"
class TileCollider { class TileCollider {
public: public:
enum class Tile : int { enum class Tile : std::uint8_t {
EMPTY = 0, EMPTY = 0,
WALL = 1, WALL = 1,
PASSABLE = 2, 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_map_(std::move(tile_map)),
tile_set_width_(tile_set_width), tile_set_width_(tile_set_width),
tileset_surface_(std::move(tileset_surface)), tileset_surface_(std::move(tileset_surface)),
bg_color_(bg_color) { bg_color_(bg_color),
map_surface_ = std::make_shared<Surface>(PlayArea::WIDTH, PlayArea::HEIGHT); map_surface_(std::make_shared<Surface>(PlayArea::WIDTH, PlayArea::HEIGHT)) {}
}
void TilemapRenderer::initialize(const std::vector<int>& collision_tile_map) { void TilemapRenderer::initialize(const std::vector<int>& collision_tile_map) {
fillMapTexture(collision_tile_map); fillMapTexture(collision_tile_map);

View File

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

View File

@@ -1,5 +1,7 @@
#pragma once #pragma once
#include <cstdint>
/* /*
Namespace SceneManager: gestiona el flujo entre las diferentes escenas del juego. Namespace SceneManager: gestiona el flujo entre las diferentes escenas del juego.
@@ -10,7 +12,7 @@
namespace SceneManager { namespace SceneManager {
// --- Escenas del programa --- // --- Escenas del programa ---
enum class Scene { enum class Scene : std::uint8_t {
LOGO, // Pantalla del logo LOGO, // Pantalla del logo
TITLE, // Pantalla de título/menú principal TITLE, // Pantalla de título/menú principal
GAME, // Juego principal GAME, // Juego principal
@@ -19,7 +21,7 @@ namespace SceneManager {
}; };
// --- Opciones para transiciones entre escenas --- // --- Opciones para transiciones entre escenas ---
enum class Options { enum class Options : std::uint8_t {
NONE, // Sin opciones especiales NONE, // Sin opciones especiales
LOGO_TO_TITLE, // Del logo al título 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 // Construye el tilemap extendido de la room actual con los bordes de las adyacentes
void Game::buildCollisionBorders() { 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) // Helper: obtiene el collision tilemap de una room adyacente (nullptr si no existe)
auto getAdjacentCollision = [&](Room::Border b) -> const std::vector<int>* { auto getAdjacentCollision = [&](Room::Border b) -> const std::vector<int>* {
auto name = room_->getRoom(b); auto name = room_->getRoom(b);
@@ -843,6 +844,7 @@ void Game::buildCollisionBorders() {
} }
return nullptr; return nullptr;
}; };
// NOLINTEND(readability-identifier-naming)
CollisionMap::AdjacentData adj; CollisionMap::AdjacentData adj;
adj.top = getAdjacentCollision(Room::Border::TOP); adj.top = getAdjacentCollision(Room::Border::TOP);
@@ -861,6 +863,7 @@ void Game::buildCollisionBorders() {
// que los sweeps del Player vean AABBs dinámicos (puertas, plataformas) // que los sweeps del Player vean AABBs dinámicos (puertas, plataformas)
// de la room vecina cuando está cerca del borde, sin tener que esperar // de la room vecina cuando está cerca del borde, sin tener que esperar
// a una transición completa de room. // 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 getAdjacentSolidActors = [&](Room::Border b) -> SolidActorManager* {
auto name = room_->getRoom(b); auto name = room_->getRoom(b);
if (name == "0") { return nullptr; } if (name == "0") { return nullptr; }

View File

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

View File

@@ -2,6 +2,7 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <cstdint>
#include <functional> // Para std::function #include <functional> // Para std::function
#include <memory> // Para shared_ptr #include <memory> // Para shared_ptr
#include <vector> // Para vector #include <vector> // Para vector
@@ -16,7 +17,7 @@ class Logo {
using EasingFunction = std::function<float(float)>; // Función de easing (permite lambdas) using EasingFunction = std::function<float(float)>; // Función de easing (permite lambdas)
// --- Enumeraciones --- // --- Enumeraciones ---
enum class State { enum class State : std::uint8_t {
INITIAL, // Espera inicial INITIAL, // Espera inicial
JAILGAMES_SLIDE_IN, // Las líneas de JAILGAMES se deslizan hacia el centro JAILGAMES_SLIDE_IN, // Las líneas de JAILGAMES se deslizan hacia el centro
SINCE_1998_FADE_IN, // Aparición gradual del texto "Since 1998" 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 TOTAL_HEIGHT = 2 * SPACING; // 2 espacios entre 3 items
const int START_Y = MENU_CENTER_Y - (TOTAL_HEIGHT / 2); 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, 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 + 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); 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; 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 // Aplica y guarda las teclas redefinidas
void Title::applyKeyboardRemap() { // NOLINT(readability-convert-member-functions-to-static) void Title::applyKeyboardRemap() { // NOLINT(readability-convert-member-functions-to-static)
// Guardar las nuevas teclas en Options::controls // 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 const int START_Y = MENU_CENTER_Y - (2 * TEXT_SIZE); // Centrado aproximado
// Mensaje principal: "PRESS KEY FOR [ACTION]" o "KEYS DEFINED" si completado // Mensaje principal: "PRESS KEY FOR [ACTION]" o "KEYS DEFINED" si completado
auto* loc = Locale::get(); const auto* loc = Locale::get();
if (remap_step_ >= 3) { 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); menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, PlayArea::CENTER_X, START_Y, loc->get("title.keys.defined"), 1, COLOR);
} else { } else {
@@ -446,7 +432,7 @@ void Title::renderJoystickRemap() const {
const int START_Y = MENU_CENTER_Y - (2 * TEXT_SIZE); // Centrado aproximado const int START_Y = MENU_CENTER_Y - (2 * TEXT_SIZE); // Centrado aproximado
// Mensaje principal: "PRESS BUTTON FOR [ACTION]" o "BUTTONS DEFINED" si completado // Mensaje principal: "PRESS BUTTON FOR [ACTION]" o "BUTTONS DEFINED" si completado
auto* loc = Locale::get(); const auto* loc = Locale::get();
if (remap_step_ >= 3) { 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); menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, PlayArea::CENTER_X, START_Y, loc->get("title.buttons.defined"), 1, COLOR);
} else { } else {

View File

@@ -2,7 +2,8 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <array> // Para std::array #include <array> // Para std::array
#include <cstdint>
#include <memory> // Para shared_ptr #include <memory> // Para shared_ptr
#include <string> // Para string #include <string> // Para string
@@ -24,7 +25,7 @@ class Title {
private: private:
// --- Estructuras y enumeraciones --- // --- Estructuras y enumeraciones ---
enum class State { enum class State : std::uint8_t {
MAIN_MENU, MAIN_MENU,
FADE_MENU, FADE_MENU,
POST_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 auto isButtonDuplicate(int button, int current_step) -> bool; // Valida si un boton esta duplicado
void applyKeyboardRemap(); // Aplica y guarda las teclas redefinidas void applyKeyboardRemap(); // Aplica y guarda las teclas redefinidas
void applyJoystickRemap(); // Aplica y guarda los botones del gamepad redefinidos 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 static auto getButtonName(int button) -> std::string; // Retorna el nombre amigable del boton del gamepad
void fillTitleSurface(); // Dibuja los elementos en la surface 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::istringstream word_stream(segment);
std::string word; std::string word;
while (word_stream >> word) { while (word_stream >> word) {
const std::string TEST = current_line.empty() ? word : (current_line + ' ' + word); std::string test;
if (text_->length(TEST) <= MAX_PX) { if (current_line.empty()) {
current_line = TEST; test = word;
} else {
test = current_line;
test += ' ';
test += word;
}
if (text_->length(test) <= MAX_PX) {
current_line = test;
} else { } else {
if (!current_line.empty()) { result.push_back(current_line); } if (!current_line.empty()) { result.push_back(current_line); }
current_line = word; current_line = word;
@@ -381,7 +388,10 @@ void Console::handleEvent(const SDL_Event& event) { // NOLINT(readability-funct
const auto OPTS = registry_.getCompletions(BASE_CMD); const auto OPTS = registry_.getCompletions(BASE_CMD);
for (const auto& arg : OPTS) { for (const auto& arg : OPTS) {
if (SUB_PREFIX.empty() || std::string_view{arg}.starts_with(SUB_PREFIX)) { 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 <SDL3/SDL.h>
#include <cstdint>
#include <deque> // Para deque (historial) #include <deque> // Para deque (historial)
#include <functional> // Para function #include <functional> // Para function
#include <memory> // Para shared_ptr #include <memory> // Para shared_ptr
@@ -46,7 +47,7 @@ class Console {
std::function<void(bool)> on_toggle; std::function<void(bool)> on_toggle;
private: private:
enum class Status { enum class Status : std::uint8_t {
HIDDEN, HIDDEN,
RISING, RISING,
ACTIVE, ACTIVE,

View File

@@ -32,7 +32,7 @@
// Toggle genérico para comandos booleanos ON/OFF (reemplaza macro BOOL_TOGGLE_CMD) // Toggle genérico para comandos booleanos ON/OFF (reemplaza macro BOOL_TOGGLE_CMD)
static auto boolToggle( static auto boolToggle(
const std::string& label, const std::string& label,
bool& option, const bool& option,
const std::function<void()>& toggle_fn, const std::function<void()>& toggle_fn,
const std::vector<std::string>& args) -> std::string { const std::vector<std::string>& args) -> std::string {
if (args.empty()) { 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); } if (N == Options::window.zoom) { return "Zoom already " + std::to_string(N); }
Screen::get()->setWindowZoom(N); Screen::get()->setWindowZoom(N);
return "Zoom " + std::to_string(Options::window.zoom); 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()) + ">]"; 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 ((args[0] == "SHOW" || args[0] == "HIDE") && args.size() >= 2) {
if ((MapEditor::get() == nullptr) || !MapEditor::get()->isActive()) { return "Editor not active"; } if ((MapEditor::get() == nullptr) || !MapEditor::get()->isActive()) { return "Editor not active"; }
bool show = (args[0] == "SHOW"); 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); } if (args[1] == "GRID") { return MapEditor::get()->showGrid(show); }
} }
// EDIT DRAW / EDIT COLLISION // 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; auto& cheat = Options::cheats.infinite_lives;
using State = Options::Cheat::State; using State = Options::Cheat::State;
const std::vector<std::string> REST(args.begin() + 2, args.end()); 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()) { if (REST.empty()) {
cheat = (cheat == State::ENABLED) ? State::DISABLED : State::ENABLED; cheat = (cheat == State::ENABLED) ? State::DISABLED : State::ENABLED;
} else if (REST[0] == "ON") { } else if (REST[0] == "ON") {
@@ -1113,7 +1116,7 @@ void CommandRegistry::registerHandlers() { // NOLINT(readability-function-cogni
if (path.find("tilesets") == std::string::npos) { continue; } if (path.find("tilesets") == std::string::npos) { continue; }
std::string name = getFileName(path); std::string name = getFileName(path);
auto dot = name.rfind('.'); 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)); result.push_back(toUpper(name));
} }
return result; return result;
@@ -1362,7 +1365,7 @@ auto CommandRegistry::getCompletions(const std::string& path) const -> std::vect
if (!active_scope_.empty()) { if (!active_scope_.empty()) {
std::string root = path; std::string root = path;
auto space = root.find(' '); 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); const auto* cmd = findCommand(root);
if (cmd != nullptr && !isCommandVisible(*cmd)) { return {}; } if (cmd != nullptr && !isCommandVisible(*cmd)) { return {}; }
} }

View File

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

View File

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