linters
This commit is contained in:
15
.clang-tidy
15
.clang-tidy
@@ -2,20 +2,12 @@ Checks:
|
||||
- readability-*
|
||||
- modernize-*
|
||||
- performance-*
|
||||
- bugprone-unchecked-optional-access
|
||||
- bugprone-sizeof-expression
|
||||
- bugprone-suspicious-missing-comma
|
||||
- bugprone-suspicious-index
|
||||
- bugprone-undefined-memory-manipulation
|
||||
- bugprone-use-after-move
|
||||
- bugprone-out-of-bound-access
|
||||
- bugprone-*
|
||||
- -readability-identifier-length
|
||||
- -readability-magic-numbers
|
||||
- -bugprone-narrowing-conversions
|
||||
- -performance-enum-size
|
||||
- -performance-inefficient-string-concatenation
|
||||
- -bugprone-integer-division
|
||||
- -bugprone-easily-swappable-parameters
|
||||
- -bugprone-narrowing-conversions
|
||||
- -modernize-avoid-c-arrays,-warnings-as-errors
|
||||
|
||||
WarningsAsErrors: '*'
|
||||
@@ -25,6 +17,9 @@ HeaderFilterRegex: 'source/(?!core/audio/jail_audio\.hpp|core/rendering/sdl3gpu/
|
||||
FormatStyle: file
|
||||
|
||||
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
|
||||
- { key: readability-identifier-naming.VariableCase, value: lower_case }
|
||||
|
||||
|
||||
85
CLAUDE.md
85
CLAUDE.md
@@ -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:
|
||||
|
||||
### 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.
|
||||
Los linters y el formateador están integrados como targets de CMake. Todos excluyen `source/external/` y los headers SPIR-V generados (`*_spv.h`).
|
||||
|
||||
```bash
|
||||
# Analyze a specific file (RECOMMENDED)
|
||||
tools/linter/run_clang-tidy.sh source/game/entities/player.cpp
|
||||
|
||||
# Analyze multiple specific files
|
||||
tools/linter/run_clang-tidy.sh source/game/entities/player.cpp source/game/entities/enemy.cpp
|
||||
|
||||
# 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
|
||||
# Desde el directorio build/
|
||||
cmake --build . --target tidy # clang-tidy sobre todo el código (sin fixes)
|
||||
cmake --build . --target tidy-fix # clang-tidy aplicando fixes automáticos
|
||||
cmake --build . --target cppcheck # cppcheck (warning/style/performance/portability, --std=c++20)
|
||||
cmake --build . --target format # clang-format -i sobre todo el código
|
||||
cmake --build . --target format-check # clang-format en modo dry-run (CI)
|
||||
```
|
||||
|
||||
**Known False Positives:**
|
||||
- `defaults.hpp`: May report errors in constant definitions
|
||||
- `readability-magic-numbers` / `cppcoreguidelines-avoid-magic-numbers`: Acceptable for game constants (block sizes, speeds, etc.)
|
||||
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.
|
||||
|
||||
### 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
|
||||
# Warning, style, and performance analysis (RECOMMENDED for daily use)
|
||||
tools/linter/run_cppcheck.sh -w --path 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
|
||||
clang-tidy -p build source/game/entities/player.cpp
|
||||
cppcheck --enable=warning,style --std=c++20 -I source source/game/entities/player.cpp
|
||||
```
|
||||
|
||||
**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
|
||||
|
||||
@@ -262,6 +262,7 @@ set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAK
|
||||
# Buscar herramientas de análisis estático
|
||||
find_program(CLANG_TIDY_EXE NAMES clang-tidy)
|
||||
find_program(CLANG_FORMAT_EXE NAMES clang-format)
|
||||
find_program(CPPCHECK_EXE NAMES cppcheck)
|
||||
|
||||
# Recopilar todos los archivos fuente para formateo
|
||||
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 ".*_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
|
||||
if(CLANG_TIDY_EXE)
|
||||
add_custom_target(tidy
|
||||
@@ -322,6 +330,28 @@ else()
|
||||
message(STATUS "clang-format no encontrado - targets 'format' y 'format-check' no disponibles")
|
||||
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 ---
|
||||
set(PACK_TOOL_SOURCES
|
||||
${CMAKE_SOURCE_DIR}/tools/pack_resources/pack_resources.cpp
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint> // Para int8_t, uint8_t
|
||||
#include <string> // Para string
|
||||
#include <utility> // Para move
|
||||
|
||||
@@ -7,13 +8,13 @@
|
||||
class Audio {
|
||||
public:
|
||||
// --- Enums ---
|
||||
enum class Group : int {
|
||||
enum class Group : std::int8_t {
|
||||
ALL = -1, // Todos los grupos
|
||||
GAME = 0, // Sonidos del juego
|
||||
INTERFACE = 1 // Sonidos de la interfaz
|
||||
};
|
||||
|
||||
enum class MusicState {
|
||||
enum class MusicState : std::uint8_t {
|
||||
PLAYING, // Reproduciendo música
|
||||
PAUSED, // Música pausada
|
||||
STOPPED, // Música detenida
|
||||
|
||||
@@ -407,8 +407,9 @@ auto Input::handleEvent(const SDL_Event& event) -> std::string { // NOLINT(read
|
||||
return addGamepad(event.gdevice.which);
|
||||
case SDL_EVENT_GAMEPAD_REMOVED:
|
||||
return removeGamepad(event.gdevice.which);
|
||||
}
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
auto Input::addGamepad(int device_index) -> std::string { // NOLINT(readability-convert-member-functions-to-static)
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
// --- Enums ---
|
||||
enum class InputAction : int { // Acciones de entrada posibles en el juego
|
||||
enum class InputAction : std::uint8_t { // Acciones de entrada posibles en el juego
|
||||
// Inputs de movimiento
|
||||
LEFT,
|
||||
RIGHT,
|
||||
|
||||
@@ -224,8 +224,8 @@ namespace GIF {
|
||||
if ((screen_descriptor.fields & 0x80) != 0) {
|
||||
int global_color_table_size = 1 << ((screen_descriptor.fields & 0x07) + 1);
|
||||
global_color_table.resize(global_color_table_size);
|
||||
std::memcpy(global_color_table.data(), buffer, 3 * global_color_table_size);
|
||||
buffer += 3 * global_color_table_size;
|
||||
std::memcpy(global_color_table.data(), buffer, static_cast<size_t>(3) * static_cast<size_t>(global_color_table_size));
|
||||
buffer += static_cast<ptrdiff_t>(3) * global_color_table_size;
|
||||
}
|
||||
|
||||
// Supongamos que 'buffer' es el puntero actual y TRAILER es 0x3B
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace {
|
||||
if (SZ == 0) { return palette; }
|
||||
|
||||
// Matriz de coste NxM (ampliada a SZxSZ con ceros para hacerla cuadrada)
|
||||
std::vector<int> cost(SZ * SZ, 0);
|
||||
std::vector<int> cost(static_cast<size_t>(SZ) * static_cast<size_t>(SZ), 0);
|
||||
for (int i = 0; i < N; ++i) {
|
||||
for (int j = 0; j < M; ++j) {
|
||||
cost[(i * SZ) + j] = rgbDistanceSq(palette[i], reference[j]);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -14,7 +15,7 @@ using Palette = std::array<Uint32, 256>;
|
||||
class Surface;
|
||||
|
||||
// Modo de ordenación de paletas
|
||||
enum class PaletteSortMode : int {
|
||||
enum class PaletteSortMode : std::uint8_t {
|
||||
ORIGINAL = 0, // Paleta tal cual viene del fichero
|
||||
OPTIMAL = 1, // Asignación óptima a la paleta por defecto (Hungarian algorithm)
|
||||
REFERENCE = 2, // Asignación greedy a la paleta por defecto
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <vector> // Para vector
|
||||
|
||||
@@ -10,7 +11,7 @@ class Surface;
|
||||
class PixelReveal {
|
||||
public:
|
||||
// Modo de revelado: aleatorio por fila o en orden de bisección (dithering ordenado 1D)
|
||||
enum class RevealMode { RANDOM,
|
||||
enum class RevealMode : std::uint8_t { RANDOM,
|
||||
ORDERED };
|
||||
|
||||
// Constructor
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class RenderInfo {
|
||||
public:
|
||||
// Singleton
|
||||
@@ -20,7 +22,7 @@ class RenderInfo {
|
||||
static constexpr float SLIDE_SPEED = 120.0F;
|
||||
|
||||
private:
|
||||
enum class Status { HIDDEN,
|
||||
enum class Status : std::uint8_t { HIDDEN,
|
||||
RISING,
|
||||
ACTIVE,
|
||||
VANISHING };
|
||||
|
||||
@@ -292,12 +292,12 @@ void Screen::adjustWindowSize() {
|
||||
|
||||
// Reservamos memoria una sola vez.
|
||||
// Si el buffer es más pequeño que la superficie, crash asegurado.
|
||||
border_pixel_buffer_.resize(static_cast<size_t>(window_width_ * window_height_));
|
||||
game_pixel_buffer_.resize(static_cast<size_t>(Options::game.width * Options::game.height));
|
||||
border_pixel_buffer_.resize(static_cast<size_t>(window_width_) * static_cast<size_t>(window_height_));
|
||||
game_pixel_buffer_.resize(static_cast<size_t>(Options::game.width) * static_cast<size_t>(Options::game.height));
|
||||
|
||||
// border_pixel_buffer_ es el buffer que se sube a la GPU (tamaño total ventana).
|
||||
if (Options::video.border.enabled) {
|
||||
border_pixel_buffer_.resize(static_cast<size_t>(window_width_ * window_height_));
|
||||
border_pixel_buffer_.resize(static_cast<size_t>(window_width_) * static_cast<size_t>(window_height_));
|
||||
}
|
||||
|
||||
// Lógica de centrado y redimensionado de ventana SDL
|
||||
@@ -397,15 +397,17 @@ void Screen::textureToRenderer() {
|
||||
// Rellena solo el marco con el color cacheado — sin lookups de paleta.
|
||||
// El área central (juego) se deja sin tocar; el overlay la sobreescribe igualmente.
|
||||
|
||||
const auto BORDER_W_SZ = static_cast<size_t>(BORDER_W);
|
||||
const auto GAME_W_SZ = static_cast<size_t>(GAME_W);
|
||||
// Franjas superior e inferior (ancho completo)
|
||||
std::fill_n(border_pixel_buffer_.data(), OFF_Y * BORDER_W, border_argb_color_);
|
||||
std::fill_n(&border_pixel_buffer_[(OFF_Y + GAME_H) * BORDER_W],
|
||||
(BORDER_H - OFF_Y - GAME_H) * BORDER_W,
|
||||
std::fill_n(border_pixel_buffer_.data(), static_cast<size_t>(OFF_Y) * BORDER_W_SZ, border_argb_color_);
|
||||
std::fill_n(&border_pixel_buffer_[(static_cast<size_t>(OFF_Y) + GAME_H) * BORDER_W_SZ],
|
||||
(static_cast<size_t>(BORDER_H - OFF_Y - GAME_H)) * BORDER_W_SZ,
|
||||
border_argb_color_);
|
||||
// Columnas laterales en las filas del área de juego
|
||||
for (int y = OFF_Y; y < OFF_Y + GAME_H; ++y) {
|
||||
std::fill_n(&border_pixel_buffer_[y * BORDER_W], OFF_X, border_argb_color_);
|
||||
std::fill_n(&border_pixel_buffer_[(y * BORDER_W) + OFF_X + GAME_W],
|
||||
std::fill_n(&border_pixel_buffer_[static_cast<size_t>(y) * BORDER_W_SZ], OFF_X, border_argb_color_);
|
||||
std::fill_n(&border_pixel_buffer_[(static_cast<size_t>(y) * BORDER_W_SZ) + OFF_X + GAME_W],
|
||||
BORDER_W - OFF_X - GAME_W,
|
||||
border_argb_color_);
|
||||
}
|
||||
@@ -413,9 +415,9 @@ void Screen::textureToRenderer() {
|
||||
// Overlay del juego sobre el centro del buffer (ambos paths)
|
||||
game_surface_->toARGBBuffer(game_pixel_buffer_.data());
|
||||
for (int y = 0; y < GAME_H; ++y) {
|
||||
const Uint32* src = &game_pixel_buffer_[y * GAME_W];
|
||||
Uint32* dst = &border_pixel_buffer_[((OFF_Y + y) * BORDER_W) + OFF_X];
|
||||
std::memcpy(dst, src, GAME_W * sizeof(Uint32));
|
||||
const Uint32* src = &game_pixel_buffer_[static_cast<size_t>(y) * GAME_W_SZ];
|
||||
Uint32* dst = &border_pixel_buffer_[((static_cast<size_t>(OFF_Y) + y) * BORDER_W_SZ) + OFF_X];
|
||||
std::memcpy(dst, src, GAME_W_SZ * sizeof(Uint32));
|
||||
}
|
||||
|
||||
shader_backend_->uploadPixels(border_pixel_buffer_.data(), BORDER_W, BORDER_H);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <SDL3/SDL_pixels.h> // Para Uint32
|
||||
|
||||
#include <cstddef> // Para size_t
|
||||
#include <cstdint> // Para uint8_t
|
||||
#include <memory> // Para shared_ptr, __shared_ptr_access
|
||||
#include <string> // Para string
|
||||
#include <utility> // Para std::pair
|
||||
@@ -18,7 +19,7 @@ class Text;
|
||||
class Screen {
|
||||
public:
|
||||
// Tipos de filtro
|
||||
enum class Filter : Uint32 {
|
||||
enum class Filter : std::uint8_t {
|
||||
NEAREST = 0,
|
||||
LINEAR = 1,
|
||||
};
|
||||
|
||||
@@ -762,7 +762,7 @@ namespace Rendering {
|
||||
}
|
||||
|
||||
// Copia directa — el upscale lo hace la GPU en el primer render pass
|
||||
std::memcpy(mapped, pixels, static_cast<size_t>(width * height * 4));
|
||||
std::memcpy(mapped, pixels, static_cast<size_t>(width) * static_cast<size_t>(height) * 4U);
|
||||
|
||||
SDL_UnmapGPUTransferBuffer(device_, upload_buffer_);
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
/** @brief Identificador del shader de post-procesado activo */
|
||||
enum class ShaderType { POSTFX,
|
||||
enum class ShaderType : std::uint8_t { POSTFX,
|
||||
CRTPI };
|
||||
|
||||
/**
|
||||
|
||||
@@ -61,7 +61,7 @@ static auto parseAnimations(const fkyaml::node& yaml, float frame_width, float f
|
||||
animation.speeds.push_back(s.get_value<float>());
|
||||
}
|
||||
} else {
|
||||
float spd = speed_node.get_value<float>();
|
||||
auto spd = speed_node.get_value<float>();
|
||||
if (spd > 0.0F) {
|
||||
animation.speeds.assign(animation.frames.size(), spd);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory> // Para shared_ptr
|
||||
|
||||
#include "core/rendering/sprite/animated_sprite.hpp" // Para SurfaceAnimatedSprite
|
||||
@@ -9,7 +10,7 @@
|
||||
class Surface;
|
||||
|
||||
// Direcció de la dissolució
|
||||
enum class DissolveDirection { NONE,
|
||||
enum class DissolveDirection : std::uint8_t { NONE,
|
||||
DOWN,
|
||||
UP };
|
||||
|
||||
@@ -41,7 +42,7 @@ class DissolveSprite : public AnimatedSprite {
|
||||
void setColorReplace(Uint8 source, Uint8 target);
|
||||
|
||||
private:
|
||||
enum class TransitionMode { NONE,
|
||||
enum class TransitionMode : std::uint8_t { NONE,
|
||||
DISSOLVING,
|
||||
GENERATING };
|
||||
|
||||
|
||||
@@ -177,10 +177,10 @@ void Surface::fillRect(const SDL_FRect* rect, Uint8 color) { // NOLINT(readabil
|
||||
|
||||
// Rellenar fila a fila con memset (memoria contigua por fila)
|
||||
Uint8* data_ptr = surface_data_->data.get();
|
||||
const int SURF_WIDTH = static_cast<int>(surface_data_->width);
|
||||
const int ROW_WIDTH = static_cast<int>(x_end) - static_cast<int>(x_start);
|
||||
const auto SURF_WIDTH = static_cast<size_t>(surface_data_->width);
|
||||
const auto ROW_WIDTH = static_cast<size_t>(static_cast<int>(x_end) - static_cast<int>(x_start));
|
||||
for (int y = static_cast<int>(y_start); y < static_cast<int>(y_end); ++y) {
|
||||
std::memset(data_ptr + (y * SURF_WIDTH) + static_cast<int>(x_start), color, ROW_WIDTH);
|
||||
std::memset(data_ptr + (static_cast<size_t>(y) * SURF_WIDTH) + static_cast<size_t>(x_start), color, ROW_WIDTH);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,10 +194,10 @@ void Surface::drawRectBorder(const SDL_FRect* rect, Uint8 color) { // NOLINT(re
|
||||
|
||||
// Dibujar bordes horizontales con memset (líneas contiguas en memoria)
|
||||
Uint8* data_ptr = surface_data_->data.get();
|
||||
const int SURF_WIDTH = static_cast<int>(surface_data_->width);
|
||||
const int ROW_WIDTH = static_cast<int>(x_end) - static_cast<int>(x_start);
|
||||
std::memset(data_ptr + (static_cast<int>(y_start) * SURF_WIDTH) + static_cast<int>(x_start), color, ROW_WIDTH);
|
||||
std::memset(data_ptr + ((static_cast<int>(y_end) - 1) * SURF_WIDTH) + static_cast<int>(x_start), color, ROW_WIDTH);
|
||||
const auto SURF_WIDTH = static_cast<size_t>(surface_data_->width);
|
||||
const auto ROW_WIDTH = static_cast<size_t>(static_cast<int>(x_end) - static_cast<int>(x_start));
|
||||
std::memset(data_ptr + (static_cast<size_t>(y_start) * SURF_WIDTH) + static_cast<size_t>(x_start), color, ROW_WIDTH);
|
||||
std::memset(data_ptr + ((static_cast<size_t>(y_end) - 1) * SURF_WIDTH) + static_cast<size_t>(x_start), color, ROW_WIDTH);
|
||||
|
||||
// Dibujar bordes verticales
|
||||
for (int y = y_start; y < y_end; ++y) {
|
||||
@@ -295,8 +295,8 @@ void Surface::render(int x, int y, SDL_FRect* src_rect, SDL_FlipMode flip) { //
|
||||
float h = (src_rect != nullptr) ? src_rect->h : surface_data_->height;
|
||||
|
||||
// Guardar dimensiones originales antes del clipping (necesarias para flip)
|
||||
float orig_w = (src_rect != nullptr) ? src_rect->w : static_cast<float>(surface_data_->width);
|
||||
float orig_h = (src_rect != nullptr) ? src_rect->h : static_cast<float>(surface_data_->height);
|
||||
float orig_w = (src_rect != nullptr) ? src_rect->w : surface_data_->width;
|
||||
float orig_h = (src_rect != nullptr) ? src_rect->h : surface_data_->height;
|
||||
|
||||
// Limitar la región para evitar accesos fuera de rango en origen
|
||||
w = std::min(w, surface_data_->width - sx);
|
||||
@@ -467,7 +467,7 @@ static auto computeFadeDensity(int screen_y, int fade_h, int canvas_height) -> f
|
||||
}
|
||||
|
||||
// Render amb dissolució als cantons superior/inferior (hash 2D, sense parpelleig)
|
||||
void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, SDL_FRect* src_rect) const {
|
||||
void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, const SDL_FRect* src_rect) const {
|
||||
// Aplicar render offset
|
||||
x += Screen::get()->getRenderOffsetX();
|
||||
y += Screen::get()->getRenderOffsetY();
|
||||
@@ -508,7 +508,7 @@ void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height
|
||||
}
|
||||
|
||||
// Idem però reemplaçant un color índex
|
||||
void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color, SDL_FRect* src_rect) const {
|
||||
void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color, const SDL_FRect* src_rect) const {
|
||||
// Aplicar render offset
|
||||
x += Screen::get()->getRenderOffsetX();
|
||||
y += Screen::get()->getRenderOffsetY();
|
||||
@@ -599,8 +599,8 @@ void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture) { //
|
||||
const int WIDTH = surface_data_->width;
|
||||
const int HEIGHT = surface_data_->height;
|
||||
for (int y = 0; y < HEIGHT; ++y) {
|
||||
const Uint8* src_row = src + (y * WIDTH);
|
||||
Uint32* dst_row = pixels + (y * row_stride);
|
||||
const Uint8* src_row = src + (static_cast<size_t>(y) * static_cast<size_t>(WIDTH));
|
||||
Uint32* dst_row = pixels + (static_cast<size_t>(y) * static_cast<size_t>(row_stride));
|
||||
for (int x = 0; x < WIDTH; ++x) {
|
||||
dst_row[x] = pal[src_row[x]];
|
||||
}
|
||||
@@ -648,8 +648,8 @@ void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FR
|
||||
const int WIDTH = surface_data_->width;
|
||||
const int HEIGHT = surface_data_->height;
|
||||
for (int y = 0; y < HEIGHT; ++y) {
|
||||
const Uint8* src_row = src + (y * WIDTH);
|
||||
Uint32* dst_row = pixels + (y * row_stride);
|
||||
const Uint8* src_row = src + (static_cast<size_t>(y) * static_cast<size_t>(WIDTH));
|
||||
Uint32* dst_row = pixels + (static_cast<size_t>(y) * static_cast<size_t>(row_stride));
|
||||
for (int x = 0; x < WIDTH; ++x) {
|
||||
dst_row[x] = pal[src_row[x]];
|
||||
}
|
||||
|
||||
@@ -85,10 +85,10 @@ class Surface {
|
||||
void renderWithColorReplace(int x, int y, Uint8 source_color = 0, Uint8 target_color = 0, SDL_FRect* src_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE) const;
|
||||
|
||||
// Render amb dissolució als cantons superior/inferior (hash 2D, sense parpelleig)
|
||||
void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, SDL_FRect* src_rect = nullptr) const;
|
||||
void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, const SDL_FRect* src_rect = nullptr) const;
|
||||
|
||||
// Idem però reemplaçant un color índex (per a sprites sobre fons del mateix color)
|
||||
void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color, SDL_FRect* src_rect = nullptr) const;
|
||||
void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color, const SDL_FRect* src_rect = nullptr) const;
|
||||
|
||||
// Establece un color en la paleta
|
||||
void setColor(int index, Uint32 color);
|
||||
|
||||
@@ -22,22 +22,19 @@ auto Text::nextCodepoint(const std::string& s, size_t& pos) -> uint32_t { // NO
|
||||
if (c < 0x80) {
|
||||
cp = c;
|
||||
extra = 0;
|
||||
} else if (c < 0xC0) {
|
||||
} else if (c < 0xC0 || c >= 0xF8) {
|
||||
// Byte de continuación suelto o lead byte inválido
|
||||
pos++;
|
||||
return 0xFFFD;
|
||||
} // byte de continuación suelto
|
||||
else if (c < 0xE0) {
|
||||
} else if (c < 0xE0) {
|
||||
cp = c & 0x1F;
|
||||
extra = 1;
|
||||
} else if (c < 0xF0) {
|
||||
cp = c & 0x0F;
|
||||
extra = 2;
|
||||
} else if (c < 0xF8) {
|
||||
} else {
|
||||
cp = c & 0x07;
|
||||
extra = 3;
|
||||
} else {
|
||||
pos++;
|
||||
return 0xFFFD;
|
||||
}
|
||||
|
||||
pos++;
|
||||
@@ -291,7 +288,7 @@ void Text::writeDX(Uint8 flags, int x, int y, const std::string& text, int kerni
|
||||
if (COLORED) {
|
||||
writeColored(x, y, text, text_color, kerning, lenght);
|
||||
} else {
|
||||
writeColored(x, y, text, text_color, kerning, lenght);
|
||||
write(x, y, text, kerning, lenght);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ namespace Resource {
|
||||
// Obtiene todas las habitaciones
|
||||
auto Cache::getRooms() -> std::vector<RoomResource>& {
|
||||
if (loading_mode_ == LoadingMode::LAZY) {
|
||||
for (auto& r : rooms_) {
|
||||
for (const auto& r : rooms_) {
|
||||
if (r.room == nullptr) { loadRoomByName(r.name); }
|
||||
}
|
||||
}
|
||||
@@ -248,6 +248,7 @@ namespace Resource {
|
||||
std::cerr << "[ ERROR ] Path: " << file_path << '\n';
|
||||
std::cerr << "[ ERROR ] Reason: " << e.what() << '\n';
|
||||
std::cerr << "[ ERROR ] Check config/assets.yaml configuration\n";
|
||||
// cppcheck-suppress rethrowNoCurrentException -- helper [[noreturn]] invocado desde dentro de un catch; cppcheck no puede ver que el rethrow es válido a través de la llamada.
|
||||
throw;
|
||||
}
|
||||
|
||||
@@ -548,6 +549,8 @@ namespace Resource {
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <utility>
|
||||
@@ -11,7 +12,7 @@ namespace Resource {
|
||||
|
||||
class Cache {
|
||||
public:
|
||||
enum class LoadingMode {
|
||||
enum class LoadingMode : std::uint8_t {
|
||||
EAGER, // Carga todos los recursos en init() (comportamiento por defecto, producción)
|
||||
LAZY // Sólo registra nombres; carga cada recurso la primera vez que se pide (desarrollo)
|
||||
};
|
||||
|
||||
@@ -75,11 +75,11 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Buscar la última entrada con el mismo prefijo de ruta e insertar después
|
||||
std::string entry = " - " + var_path + "\n";
|
||||
auto last_pos = content.rfind(var_path.substr(0, var_path.rfind('/')));
|
||||
if (last_pos != std::string::npos) {
|
||||
auto end_of_line = content.find('\n', last_pos);
|
||||
if (end_of_line != std::string::npos) {
|
||||
std::string entry = " - " + var_path + "\n";
|
||||
content.insert(end_of_line + 1, entry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Resource {
|
||||
class List {
|
||||
public:
|
||||
// --- Enums ---
|
||||
enum class Type : int {
|
||||
enum class Type : std::uint8_t {
|
||||
DATA, // Datos
|
||||
BITMAP, // Imágenes
|
||||
ANIMATION, // Animaciones
|
||||
|
||||
@@ -41,9 +41,11 @@ void Debug::render() { // NOLINT(readability-make-member-function-const)
|
||||
|
||||
// Watch window: valores persistentes (key: value)
|
||||
for (const auto& [key, value] : watches_) {
|
||||
const std::string LINE = key + ": " + value;
|
||||
text->write(x_, y, LINE);
|
||||
w = std::max(w, text->length(LINE));
|
||||
std::string line = key;
|
||||
line += ": ";
|
||||
line += value;
|
||||
text->write(x_, y, line);
|
||||
w = std::max(w, text->length(line));
|
||||
y += DESP_Y;
|
||||
if (y > 192 - CHAR_SIZE) {
|
||||
y = y_;
|
||||
|
||||
@@ -14,13 +14,9 @@
|
||||
|
||||
// Constructor
|
||||
EditorStatusBar::EditorStatusBar(std::string room_number)
|
||||
: room_number_(std::move(room_number)) {
|
||||
const float SURFACE_WIDTH = Options::game.width;
|
||||
constexpr float SURFACE_HEIGHT = 24.0F; // 3 líneas de 8px
|
||||
|
||||
surface_ = std::make_shared<Surface>(SURFACE_WIDTH, SURFACE_HEIGHT);
|
||||
surface_dest_ = {.x = 0, .y = Options::game.height - SURFACE_HEIGHT, .w = SURFACE_WIDTH, .h = SURFACE_HEIGHT};
|
||||
}
|
||||
: surface_(std::make_shared<Surface>(Options::game.width, SURFACE_HEIGHT)),
|
||||
surface_dest_{.x = 0, .y = Options::game.height - SURFACE_HEIGHT, .w = Options::game.width, .h = SURFACE_HEIGHT},
|
||||
room_number_(std::move(room_number)) {}
|
||||
|
||||
// Pinta la barra de estado en pantalla
|
||||
void EditorStatusBar::render() {
|
||||
|
||||
@@ -27,6 +27,7 @@ class EditorStatusBar {
|
||||
|
||||
// Constantes de posición (en pixels dentro de la surface de 256x24)
|
||||
// Font 8bithud lowercase = 6px alto → 3 líneas con 8px de separación
|
||||
static constexpr float SURFACE_HEIGHT = 24.0F; // 3 líneas de 8px
|
||||
static constexpr int LINE1_Y = 1; // Room number + tile coords + extra
|
||||
static constexpr int LINE2_Y = 9; // Propiedades de room / enemy info
|
||||
static constexpr int LINE3_Y = 17; // Conexiones+items / enemy detail
|
||||
|
||||
@@ -92,7 +92,7 @@ void MapEditor::loadSettings() {
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
// Fichero corrupto o vacío, usar defaults
|
||||
// @INTENTIONAL: fichero corrupto o vacío → usar defaults
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,8 +231,8 @@ void MapEditor::enter(std::shared_ptr<Room> room, std::shared_ptr<Player> player
|
||||
painting_ = false; // Siempre dejar de pintar al cambiar de room
|
||||
|
||||
// Asegurar que collision_tile_map tiene el tamaño correcto
|
||||
if (room_data_.collision_tile_map.size() != static_cast<size_t>(Map::WIDTH * Map::HEIGHT)) {
|
||||
room_data_.collision_tile_map.resize(Map::WIDTH * Map::HEIGHT, 0);
|
||||
if (room_data_.collision_tile_map.size() != static_cast<size_t>(Map::WIDTH) * static_cast<size_t>(Map::HEIGHT)) {
|
||||
room_data_.collision_tile_map.resize(static_cast<size_t>(Map::WIDTH) * static_cast<size_t>(Map::HEIGHT), 0);
|
||||
}
|
||||
|
||||
active_ = true;
|
||||
@@ -361,13 +361,9 @@ void MapEditor::update(float delta_time) {
|
||||
}
|
||||
|
||||
// Renderiza el editor
|
||||
void MapEditor::render() {
|
||||
// El tilemap ya ha sido renderizado por Game::renderPlaying() antes de llamar aquí
|
||||
|
||||
// Si estamos editando colisiones, superponer el mapa de colisiones
|
||||
if (editing_collision_) {
|
||||
void MapEditor::renderCollisionOverlay() const {
|
||||
auto collision_surface = Resource::Cache::get()->getSurface("collision.gif");
|
||||
if (collision_surface) {
|
||||
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) {
|
||||
@@ -383,8 +379,13 @@ void MapEditor::render() {
|
||||
collision_surface->render(x * TILE_W, y * TILE_W, &clip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MapEditor::render() {
|
||||
// El tilemap ya ha sido renderizado por Game::renderPlaying() antes de llamar aquí
|
||||
|
||||
// Si estamos editando colisiones, superponer el mapa de colisiones
|
||||
if (editing_collision_) { renderCollisionOverlay(); }
|
||||
|
||||
// Grid (debajo de todo)
|
||||
if (settings_.grid) {
|
||||
@@ -526,7 +527,7 @@ void MapEditor::handleEvent(const SDL_Event& event) { // NOLINT(readability-fun
|
||||
// Deseleccionar entidades
|
||||
selection_.clear();
|
||||
|
||||
const std::string tileset_name = editing_collision_ ? "collision.gif" : room_->getTileSetFile();
|
||||
const std::string TILESET_NAME = editing_collision_ ? "collision.gif" : room_->getTileSetFile();
|
||||
int tile_index = (mouse_tile_y_ * 32) + mouse_tile_x_;
|
||||
int current = 0;
|
||||
if (editing_collision_) {
|
||||
@@ -539,10 +540,10 @@ void MapEditor::handleEvent(const SDL_Event& event) { // NOLINT(readability-fun
|
||||
: -1;
|
||||
}
|
||||
|
||||
tile_picker_.on_select = [this, tileset_name](int col, int row, int width, int height) {
|
||||
brush_ = buildPatternFromTileset(tileset_name, col, row, width, height);
|
||||
tile_picker_.on_select = [this, TILESET_NAME](int col, int row, int width, int height) {
|
||||
brush_ = buildPatternFromTileset(TILESET_NAME, col, row, width, height);
|
||||
};
|
||||
tile_picker_.open(tileset_name, current, 0, -1, -1, 0, 1, true);
|
||||
tile_picker_.open(TILESET_NAME, current, 0, -1, -1, 0, 1, true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -664,7 +665,7 @@ void MapEditor::handleMouseUp() {
|
||||
if (selection_.is(drag_.entity_type) && selection_.index == drag_.index) {
|
||||
selection_.clear(); // deselect
|
||||
} else {
|
||||
selection_ = {drag_.entity_type, drag_.index}; // select
|
||||
selection_ = {.type = drag_.entity_type, .index = drag_.index}; // select
|
||||
}
|
||||
} else {
|
||||
selection_.clear();
|
||||
@@ -696,7 +697,8 @@ void MapEditor::handleMouseUp() {
|
||||
drag_ = {};
|
||||
}
|
||||
|
||||
// Commit de un drag de entidad (initial, bound1, bound2) para cualquier EntityType
|
||||
// Commit de un drag de entidad (initial, bound1, bound2) para cualquier EntityType.
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity) -- switch sobre EntityType con una rama por tipo; refactor a visitor requiere cambio de diseño.
|
||||
auto MapEditor::commitEntityDrag() -> bool {
|
||||
const int IDX = drag_.index;
|
||||
const int SNAP_X = static_cast<int>(drag_.snap_x);
|
||||
@@ -710,14 +712,14 @@ auto MapEditor::commitEntityDrag() -> bool {
|
||||
room_data_.enemies[IDX].x = drag_.snap_x;
|
||||
room_data_.enemies[IDX].y = drag_.snap_y;
|
||||
room_->getEnemyManager()->getEnemy(IDX)->resetToInitialPosition(room_data_.enemies[IDX]);
|
||||
selection_ = {EntityType::ENEMY, IDX};
|
||||
selection_ = {.type = EntityType::ENEMY, .index = IDX};
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case EntityType::ITEM:
|
||||
if (IDX >= 0 && IDX < room_->getItemManager()->getCount()) {
|
||||
room_->getItemManager()->getItem(IDX)->setPosition(drag_.snap_x, drag_.snap_y);
|
||||
selection_ = {EntityType::ITEM, IDX};
|
||||
selection_ = {.type = EntityType::ITEM, .index = IDX};
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
@@ -733,7 +735,7 @@ auto MapEditor::commitEntityDrag() -> bool {
|
||||
}
|
||||
}
|
||||
room_->getPlatformManager()->getPlatform(IDX)->resetToInitialPosition(plat);
|
||||
selection_ = {EntityType::PLATFORM, IDX};
|
||||
selection_ = {.type = EntityType::PLATFORM, .index = IDX};
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
@@ -744,7 +746,7 @@ auto MapEditor::commitEntityDrag() -> bool {
|
||||
// sprite→data igual que con items.
|
||||
room_data_.keys[IDX].x = drag_.snap_x;
|
||||
room_data_.keys[IDX].y = drag_.snap_y;
|
||||
selection_ = {EntityType::KEY, IDX};
|
||||
selection_ = {.type = EntityType::KEY, .index = IDX};
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
@@ -756,7 +758,7 @@ auto MapEditor::commitEntityDrag() -> bool {
|
||||
room_data_.doors[IDX].x = drag_.snap_x;
|
||||
room_data_.doors[IDX].y = drag_.snap_y;
|
||||
room_->getDoorManager()->moveDoor(IDX, drag_.snap_x, drag_.snap_y);
|
||||
selection_ = {EntityType::DOOR, IDX};
|
||||
selection_ = {.type = EntityType::DOOR, .index = IDX};
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
@@ -772,7 +774,7 @@ auto MapEditor::commitEntityDrag() -> bool {
|
||||
room_data_.enemies[IDX].x1 = SNAP_X;
|
||||
room_data_.enemies[IDX].y1 = SNAP_Y;
|
||||
room_->getEnemyManager()->getEnemy(IDX)->resetToInitialPosition(room_data_.enemies[IDX]);
|
||||
selection_ = {EntityType::ENEMY, IDX};
|
||||
selection_ = {.type = EntityType::ENEMY, .index = IDX};
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
@@ -788,7 +790,7 @@ auto MapEditor::commitEntityDrag() -> bool {
|
||||
room_data_.enemies[IDX].x2 = SNAP_X;
|
||||
room_data_.enemies[IDX].y2 = SNAP_Y;
|
||||
room_->getEnemyManager()->getEnemy(IDX)->resetToInitialPosition(room_data_.enemies[IDX]);
|
||||
selection_ = {EntityType::ENEMY, IDX};
|
||||
selection_ = {.type = EntityType::ENEMY, .index = IDX};
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
@@ -803,7 +805,8 @@ auto MapEditor::commitEntityDrag() -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mueve visualmente la entidad arrastrada a la posición snapped
|
||||
// Mueve visualmente la entidad arrastrada a la posición snapped.
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity) -- switch sobre EntityType con cases paralelos para cada tipo.
|
||||
void MapEditor::moveEntityVisual() {
|
||||
switch (drag_.target) {
|
||||
case DragTarget::ENTITY_INITIAL:
|
||||
@@ -857,8 +860,6 @@ void MapEditor::moveEntityVisual() {
|
||||
case DragTarget::ENTITY_BOUND1:
|
||||
case DragTarget::ENTITY_BOUND2:
|
||||
// Los boundaries se actualizan visualmente en renderEntityBoundaries() via drag_.snap
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -946,7 +947,8 @@ void MapEditor::renderSelectionHighlight() {
|
||||
game_surface->drawRectBorder(&border, DRAG_COLOR);
|
||||
}
|
||||
|
||||
// Estampa el patrón del brush en la posición indicada (anclaje top-left)
|
||||
// Estampa el patrón del brush en la posición indicada (anclaje top-left).
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity) -- nested loops + casos TRANSPARENT/ERASE/tile normal y ramas collision vs normal.
|
||||
void MapEditor::stampBrushAt(int tile_x, int tile_y) {
|
||||
if (brush_.isEmpty()) { return; }
|
||||
for (int dy = 0; dy < brush_.height; ++dy) {
|
||||
@@ -997,7 +999,7 @@ auto MapEditor::sampleBrush(int x1, int y1, int x2, int y2) const -> BrushPatter
|
||||
BrushPattern p;
|
||||
p.width = (x2 - x1) + 1;
|
||||
p.height = (y2 - y1) + 1;
|
||||
p.tiles.reserve(static_cast<size_t>(p.width * p.height));
|
||||
p.tiles.reserve(static_cast<size_t>(p.width) * static_cast<size_t>(p.height));
|
||||
const auto& src = editing_collision_ ? room_data_.collision_tile_map : room_data_.tile_map;
|
||||
for (int y = y1; y <= y2; ++y) {
|
||||
for (int x = x1; x <= x2; ++x) {
|
||||
@@ -1012,7 +1014,7 @@ auto MapEditor::sampleBrush(int x1, int y1, int x2, int y2) const -> BrushPatter
|
||||
|
||||
// Construye un BrushPattern leyendo tiles consecutivos de un tileset.
|
||||
// Usado por el TilePicker cuando se hace selección rectangular.
|
||||
auto MapEditor::buildPatternFromTileset(const std::string& tileset_name, int col, int row, int width, int height) const -> BrushPattern {
|
||||
auto MapEditor::buildPatternFromTileset(const std::string& tileset_name, int col, int row, int width, int height) -> BrushPattern {
|
||||
BrushPattern p;
|
||||
auto surface = Resource::Cache::get()->getSurface(tileset_name);
|
||||
if (!surface || width <= 0 || height <= 0) { return p; }
|
||||
@@ -1020,7 +1022,7 @@ auto MapEditor::buildPatternFromTileset(const std::string& tileset_name, int col
|
||||
if (cols <= 0) { return p; }
|
||||
p.width = width;
|
||||
p.height = height;
|
||||
p.tiles.reserve(static_cast<size_t>(width * height));
|
||||
p.tiles.reserve(static_cast<size_t>(width) * static_cast<size_t>(height));
|
||||
for (int dy = 0; dy < height; ++dy) {
|
||||
for (int dx = 0; dx < width; ++dx) {
|
||||
int tile = ((row + dy) * cols) + (col + dx);
|
||||
@@ -1036,8 +1038,8 @@ void MapEditor::renderBrushPreview() {
|
||||
auto game_surface = Screen::get()->getRendererSurface();
|
||||
if (!game_surface) { return; }
|
||||
|
||||
const std::string tileset_name = editing_collision_ ? std::string("collision.gif") : room_->getTileSetFile();
|
||||
auto tileset = Resource::Cache::get()->getSurface(tileset_name);
|
||||
const std::string TILESET_NAME = editing_collision_ ? std::string("collision.gif") : room_->getTileSetFile();
|
||||
auto tileset = Resource::Cache::get()->getSurface(TILESET_NAME);
|
||||
int cols = (tileset) ? (static_cast<int>(tileset->getWidth()) / Tile::SIZE) : 0;
|
||||
|
||||
constexpr auto TS = static_cast<float>(Tile::SIZE);
|
||||
@@ -1052,7 +1054,7 @@ void MapEditor::renderBrushPreview() {
|
||||
float dst_y = static_cast<float>(ty) * TS;
|
||||
if (value == BrushPattern::ERASE) {
|
||||
SDL_FRect erase_cell = {.x = dst_x, .y = dst_y, .w = TS, .h = TS};
|
||||
game_surface->fillRect(&erase_cell, static_cast<Uint8>(room_data_.bg_color));
|
||||
game_surface->fillRect(&erase_cell, room_data_.bg_color);
|
||||
} else if (tileset && cols > 0) {
|
||||
SDL_FRect src = {
|
||||
.x = static_cast<float>(value % cols) * TS,
|
||||
@@ -1077,7 +1079,7 @@ void MapEditor::renderBrushPreview() {
|
||||
}
|
||||
|
||||
// Renderiza el rectángulo del eyedropper en progreso (cyan brillante)
|
||||
void MapEditor::renderEyedropperRect() {
|
||||
void MapEditor::renderEyedropperRect() const {
|
||||
auto game_surface = Screen::get()->getRendererSurface();
|
||||
if (!game_surface) { return; }
|
||||
int x1 = std::clamp(eyedropper_.start_tile_x, 0, Map::WIDTH - 1);
|
||||
@@ -1152,7 +1154,7 @@ auto MapEditor::entityBoundaries(EntityType type, int index) const -> BoundaryDa
|
||||
switch (type) {
|
||||
case EntityType::ENEMY: {
|
||||
const auto& e = room_data_.enemies[index];
|
||||
return {e.x1, e.y1, e.x2, e.y2};
|
||||
return {.x1 = e.x1, .y1 = e.y1, .x2 = e.x2, .y2 = e.y2};
|
||||
}
|
||||
default:
|
||||
return {};
|
||||
@@ -1212,7 +1214,8 @@ auto MapEditor::entityLabel(EntityType type) -> const char* {
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja marcadores de boundaries y líneas de ruta para enemigos y plataformas
|
||||
// Dibuja marcadores de boundaries y líneas de ruta para enemigos y plataformas.
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity) -- switch sobre EntityType con ramas de enemigo patrullante vs plataforma con waypoints.
|
||||
void MapEditor::renderEntityBoundaries() {
|
||||
auto game_surface = Screen::get()->getRendererSurface();
|
||||
if (!game_surface) { return; }
|
||||
@@ -1395,7 +1398,7 @@ void MapEditor::updateStatusBarInfo() { // NOLINT(readability-function-cognitiv
|
||||
const auto& e = room_data_.enemies[selection_.index];
|
||||
std::string anim = e.animation_path;
|
||||
auto dot = anim.rfind('.');
|
||||
if (dot != std::string::npos) { anim = anim.substr(0, dot); }
|
||||
if (dot != std::string::npos) { anim.resize(dot); }
|
||||
|
||||
line2 = "enemy " + std::to_string(selection_.index) + ": " + anim;
|
||||
line3 = "vx:" + std::to_string(static_cast<int>(e.vx)) +
|
||||
@@ -1420,7 +1423,7 @@ void MapEditor::updateStatusBarInfo() { // NOLINT(readability-function-cognitiv
|
||||
if (selection_.index < static_cast<int>(room_data_.platforms.size())) {
|
||||
const auto& p = room_data_.platforms[selection_.index];
|
||||
std::string anim = p.animation_path;
|
||||
if (anim.size() > 5 && anim.substr(anim.size() - 5) == ".yaml") { anim = anim.substr(0, anim.size() - 5); }
|
||||
if (anim.ends_with(".yaml")) { anim.resize(anim.size() - 5); }
|
||||
line2 = "platform " + std::to_string(selection_.index) + ": " + anim;
|
||||
line3 = "speed:" + std::to_string(static_cast<int>(p.speed)) + " " + (p.loop == LoopMode::CIRCULAR ? "circular" : "pingpong");
|
||||
if (p.easing != "linear") { line3 += " " + p.easing; }
|
||||
@@ -1432,7 +1435,7 @@ void MapEditor::updateStatusBarInfo() { // NOLINT(readability-function-cognitiv
|
||||
const auto& k = room_data_.keys[selection_.index];
|
||||
std::string anim = k.animation_path;
|
||||
auto dot = anim.rfind('.');
|
||||
if (dot != std::string::npos) { anim = anim.substr(0, dot); }
|
||||
if (dot != std::string::npos) { anim.resize(dot); }
|
||||
line2 = "key " + std::to_string(selection_.index) + ": " + anim;
|
||||
line3 = "id: " + k.id;
|
||||
}
|
||||
@@ -1443,7 +1446,7 @@ void MapEditor::updateStatusBarInfo() { // NOLINT(readability-function-cognitiv
|
||||
const auto& d = room_data_.doors[selection_.index];
|
||||
std::string anim = d.animation_path;
|
||||
auto dot = anim.rfind('.');
|
||||
if (dot != std::string::npos) { anim = anim.substr(0, dot); }
|
||||
if (dot != std::string::npos) { anim.resize(dot); }
|
||||
line2 = "door " + std::to_string(selection_.index) + ": " + anim;
|
||||
line3 = "id: " + d.id;
|
||||
}
|
||||
@@ -1495,7 +1498,6 @@ auto MapEditor::getSetCompletions() const -> std::vector<std::string> {
|
||||
case EntityType::PLATFORM:
|
||||
return {"ANIMATION", "SPEED", "LOOP", "EASING"};
|
||||
case EntityType::KEY:
|
||||
return {"ID", "ANIMATION"};
|
||||
case EntityType::DOOR:
|
||||
return {"ID", "ANIMATION"};
|
||||
default:
|
||||
@@ -1534,7 +1536,7 @@ auto MapEditor::getAnimationCompletions() const -> std::vector<std::string> {
|
||||
if (path.extension() != ".yaml") { continue; }
|
||||
result.push_back(toUpper(path.stem().string()));
|
||||
}
|
||||
std::sort(result.begin(), result.end());
|
||||
std::ranges::sort(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1652,7 +1654,7 @@ auto MapEditor::addEnemy() -> std::string {
|
||||
|
||||
// Seleccionar el nuevo enemigo
|
||||
int new_index = static_cast<int>(room_data_.enemies.size()) - 1;
|
||||
selection_ = {EntityType::ENEMY, new_index};
|
||||
selection_ = {.type = EntityType::ENEMY, .index = new_index};
|
||||
|
||||
autosave();
|
||||
return "Added enemy " + std::to_string(new_index);
|
||||
@@ -1699,7 +1701,7 @@ auto MapEditor::duplicateEnemy() -> std::string {
|
||||
|
||||
// Seleccionar el nuevo enemigo
|
||||
int new_index = static_cast<int>(room_data_.enemies.size()) - 1;
|
||||
selection_ = {EntityType::ENEMY, new_index};
|
||||
selection_ = {.type = EntityType::ENEMY, .index = new_index};
|
||||
|
||||
autosave();
|
||||
return "Duplicated as enemy " + std::to_string(new_index);
|
||||
@@ -1907,7 +1909,7 @@ auto MapEditor::createNewRoom(const std::string& direction) -> std::string { //
|
||||
|
||||
// Comprobar que no hay ya una room en esa dirección
|
||||
if (!direction.empty()) {
|
||||
std::string* existing = nullptr;
|
||||
const std::string* existing = nullptr;
|
||||
if (direction == "UP") {
|
||||
existing = &room_data_.upper_room;
|
||||
} else if (direction == "DOWN") {
|
||||
@@ -1923,12 +1925,14 @@ auto MapEditor::createNewRoom(const std::string& direction) -> std::string { //
|
||||
}
|
||||
|
||||
// Encontrar el primer número libre (reutiliza huecos)
|
||||
auto& rooms = Resource::Cache::get()->getRooms();
|
||||
const auto& rooms = Resource::Cache::get()->getRooms();
|
||||
std::set<int> used;
|
||||
for (const auto& r : rooms) {
|
||||
try {
|
||||
used.insert(std::stoi(r.name.substr(0, r.name.find('.'))));
|
||||
} catch (...) {}
|
||||
} catch (...) {
|
||||
// @INTENTIONAL: nombre de room no es numérico → saltar
|
||||
}
|
||||
}
|
||||
int new_num = 1;
|
||||
while (used.contains(new_num)) { ++new_num; }
|
||||
@@ -1970,7 +1974,7 @@ auto MapEditor::createNewRoom(const std::string& direction) -> std::string { //
|
||||
|
||||
// Persistir vía la autoridad del formato (no más std::ofstream a pelo)
|
||||
auto save_result = RoomFormat::saveYAML(new_path, new_room);
|
||||
if (save_result.find("Error") == 0) { return save_result; }
|
||||
if (save_result.starts_with("Error")) { return save_result; }
|
||||
|
||||
// Registrar en Resource::List (mapa + assets.yaml) y cache
|
||||
Resource::List::get()->addAsset(new_path, Resource::List::Type::ROOM);
|
||||
@@ -2212,7 +2216,7 @@ auto MapEditor::addItem() -> std::string {
|
||||
room_->getItemManager()->addItem(std::make_shared<Item>(new_item));
|
||||
|
||||
int new_index = static_cast<int>(room_data_.items.size()) - 1;
|
||||
selection_ = {EntityType::ITEM, new_index};
|
||||
selection_ = {.type = EntityType::ITEM, .index = new_index};
|
||||
|
||||
autosave();
|
||||
return "Added item " + std::to_string(new_index);
|
||||
@@ -2250,7 +2254,7 @@ auto MapEditor::duplicateItem() -> std::string {
|
||||
room_->getItemManager()->addItem(std::make_shared<Item>(copy));
|
||||
|
||||
int new_index = static_cast<int>(room_data_.items.size()) - 1;
|
||||
selection_ = {EntityType::ITEM, new_index};
|
||||
selection_ = {.type = EntityType::ITEM, .index = new_index};
|
||||
|
||||
autosave();
|
||||
return "Duplicated as item " + std::to_string(new_index);
|
||||
@@ -2328,14 +2332,14 @@ auto MapEditor::addPlatform() -> std::string {
|
||||
constexpr float CENTER_Y = PlayArea::CENTER_Y;
|
||||
constexpr float ROUTE_HALF = 2.0F * Tile::SIZE;
|
||||
new_platform.path = {
|
||||
{CENTER_X - ROUTE_HALF, CENTER_Y, 0.0F},
|
||||
{CENTER_X + ROUTE_HALF, CENTER_Y, 0.0F}};
|
||||
{.x = CENTER_X - ROUTE_HALF, .y = CENTER_Y, .wait = 0.0F},
|
||||
{.x = CENTER_X + ROUTE_HALF, .y = CENTER_Y, .wait = 0.0F}};
|
||||
|
||||
room_data_.platforms.push_back(new_platform);
|
||||
room_->getPlatformManager()->addPlatform(std::make_shared<MovingPlatform>(new_platform));
|
||||
|
||||
int new_index = static_cast<int>(room_data_.platforms.size()) - 1;
|
||||
selection_ = {EntityType::PLATFORM, new_index};
|
||||
selection_ = {.type = EntityType::PLATFORM, .index = new_index};
|
||||
|
||||
autosave();
|
||||
return "Added platform " + std::to_string(new_index);
|
||||
@@ -2375,7 +2379,7 @@ auto MapEditor::duplicatePlatform() -> std::string {
|
||||
room_->getPlatformManager()->addPlatform(std::make_shared<MovingPlatform>(copy));
|
||||
|
||||
int new_index = static_cast<int>(room_data_.platforms.size()) - 1;
|
||||
selection_ = {EntityType::PLATFORM, new_index};
|
||||
selection_ = {.type = EntityType::PLATFORM, .index = new_index};
|
||||
|
||||
autosave();
|
||||
return "Duplicated as platform " + std::to_string(new_index);
|
||||
@@ -2441,7 +2445,7 @@ auto MapEditor::addKey() -> std::string {
|
||||
}
|
||||
|
||||
int new_index = static_cast<int>(room_data_.keys.size()) - 1;
|
||||
selection_ = {EntityType::KEY, new_index};
|
||||
selection_ = {.type = EntityType::KEY, .index = new_index};
|
||||
|
||||
autosave();
|
||||
return "Added key " + std::to_string(new_index);
|
||||
@@ -2484,7 +2488,7 @@ auto MapEditor::duplicateKey() -> std::string {
|
||||
}
|
||||
|
||||
int new_index = static_cast<int>(room_data_.keys.size()) - 1;
|
||||
selection_ = {EntityType::KEY, new_index};
|
||||
selection_ = {.type = EntityType::KEY, .index = new_index};
|
||||
|
||||
autosave();
|
||||
return "Duplicated as key " + std::to_string(new_index);
|
||||
@@ -2568,7 +2572,7 @@ auto MapEditor::addDoor() -> std::string {
|
||||
}
|
||||
|
||||
int new_index = static_cast<int>(room_data_.doors.size()) - 1;
|
||||
selection_ = {EntityType::DOOR, new_index};
|
||||
selection_ = {.type = EntityType::DOOR, .index = new_index};
|
||||
|
||||
autosave();
|
||||
return "Added door " + std::to_string(new_index);
|
||||
@@ -2608,7 +2612,7 @@ auto MapEditor::duplicateDoor() -> std::string {
|
||||
}
|
||||
|
||||
int new_index = static_cast<int>(room_data_.doors.size()) - 1;
|
||||
selection_ = {EntityType::DOOR, new_index};
|
||||
selection_ = {.type = EntityType::DOOR, .index = new_index};
|
||||
|
||||
autosave();
|
||||
return "Duplicated as door " + std::to_string(new_index);
|
||||
@@ -2629,7 +2633,7 @@ static auto pickGridColor(Uint8 bg, const std::shared_ptr<Surface>& surface) ->
|
||||
}
|
||||
|
||||
// Dibuja la cuadrícula de tiles (líneas de puntos, 1 pixel sí / 1 no)
|
||||
void MapEditor::renderGrid() const {
|
||||
void MapEditor::renderGrid() {
|
||||
auto game_surface = Screen::get()->getRendererSurface();
|
||||
if (!game_surface) { return; }
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint> // Para uint8_t
|
||||
#include <memory> // Para shared_ptr, unique_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
@@ -23,7 +24,7 @@
|
||||
class EditorStatusBar;
|
||||
|
||||
// Tipo de entidad editable en el editor
|
||||
enum class EntityType { NONE,
|
||||
enum class EntityType : std::uint8_t { NONE,
|
||||
ENEMY,
|
||||
ITEM,
|
||||
PLATFORM,
|
||||
@@ -106,7 +107,7 @@ class MapEditor {
|
||||
auto deleteRoom() -> std::string;
|
||||
|
||||
// Opciones del editor (llamados desde console_commands / teclas)
|
||||
auto showInfo(bool show) -> std::string;
|
||||
static auto showInfo(bool show) -> std::string;
|
||||
auto showGrid(bool show) -> std::string;
|
||||
auto setEditingCollision(bool collision) -> std::string;
|
||||
[[nodiscard]] auto isGridEnabled() const -> bool { return settings_.grid; }
|
||||
@@ -164,7 +165,7 @@ class MapEditor {
|
||||
void saveSettings() const;
|
||||
|
||||
// Tipos para drag & drop
|
||||
enum class DragTarget { NONE,
|
||||
enum class DragTarget : std::uint8_t { NONE,
|
||||
PLAYER,
|
||||
ENTITY_INITIAL,
|
||||
ENTITY_BOUND1,
|
||||
@@ -183,18 +184,19 @@ class MapEditor {
|
||||
|
||||
// Métodos internos
|
||||
void updateMousePosition();
|
||||
void renderCollisionOverlay() const;
|
||||
void renderEntityBoundaries();
|
||||
static void renderBoundaryMarker(float x, float y, Uint8 color);
|
||||
void renderSelectionHighlight();
|
||||
void renderBrushPreview();
|
||||
void renderEyedropperRect();
|
||||
void renderGrid() const;
|
||||
void renderEyedropperRect() const;
|
||||
static void renderGrid();
|
||||
void handleMouseDown(float game_x, float game_y);
|
||||
void handleMouseUp();
|
||||
void stampBrushAt(int tile_x, int tile_y);
|
||||
void commitEyedropper();
|
||||
[[nodiscard]] auto sampleBrush(int x1, int y1, int x2, int y2) const -> BrushPattern;
|
||||
[[nodiscard]] auto buildPatternFromTileset(const std::string& tileset_name, int col, int row, int width, int height) const -> BrushPattern;
|
||||
[[nodiscard]] static auto buildPatternFromTileset(const std::string& tileset_name, int col, int row, int width, int height) -> BrushPattern;
|
||||
|
||||
// Reconstruye todas las puertas vivas desde room_data_, limpiando primero
|
||||
// los WALLs antiguos del CollisionMap. Lo usa setDoorProperty cuando un
|
||||
@@ -213,12 +215,12 @@ class MapEditor {
|
||||
struct BoundaryData {
|
||||
int x1, y1, x2, y2;
|
||||
};
|
||||
auto entityCount(EntityType type) const -> int;
|
||||
[[nodiscard]] auto entityCount(EntityType type) const -> int;
|
||||
auto entityRect(EntityType type, int index) -> SDL_FRect;
|
||||
static auto entityHasBoundaries(EntityType type) -> bool;
|
||||
auto entityBoundaries(EntityType type, int index) const -> BoundaryData;
|
||||
auto entityPosition(EntityType type, int index) const -> std::pair<float, float>;
|
||||
auto entityDataCount(EntityType type) const -> int;
|
||||
[[nodiscard]] auto entityBoundaries(EntityType type, int index) const -> BoundaryData;
|
||||
[[nodiscard]] auto entityPosition(EntityType type, int index) const -> std::pair<float, float>;
|
||||
[[nodiscard]] auto entityDataCount(EntityType type) const -> int;
|
||||
static auto entityLabel(EntityType type) -> const char*;
|
||||
|
||||
// Estado del editor
|
||||
|
||||
@@ -85,7 +85,7 @@ auto MiniMap::getOrBuildTileColorTable(const std::string& tileset_name) -> const
|
||||
|
||||
// Posiciona las rooms en un grid usando BFS desde las conexiones
|
||||
void MiniMap::layoutRooms() {
|
||||
auto& rooms = Resource::Cache::get()->getRooms();
|
||||
const auto& rooms = Resource::Cache::get()->getRooms();
|
||||
if (rooms.empty()) { return; }
|
||||
|
||||
// Mapa de nombre → Room::Data
|
||||
|
||||
@@ -166,8 +166,8 @@ void TilePicker::render() {
|
||||
int cells_h = row_max - row_min + 1;
|
||||
float rx = tileset_screen_x + static_cast<float>(col_min * out_cell);
|
||||
float ry = tileset_screen_y + static_cast<float>(row_min * out_cell);
|
||||
float rw = static_cast<float>((cells_w * out_cell) - spacing_out_);
|
||||
float rh = static_cast<float>((cells_h * out_cell) - spacing_out_);
|
||||
auto rw = static_cast<float>((cells_w * out_cell) - spacing_out_);
|
||||
auto rh = static_cast<float>((cells_h * out_cell) - spacing_out_);
|
||||
if (ry + rh > 0 && ry < static_cast<float>(visible_height_)) {
|
||||
SDL_FRect rect_box = {.x = rx, .y = ry, .w = rw, .h = rh};
|
||||
game_surface->drawRectBorder(&rect_box, 15);
|
||||
@@ -198,6 +198,27 @@ void TilePicker::render() {
|
||||
}
|
||||
}
|
||||
|
||||
// Invoca el callback con el rect formado por rect_start_tile_ y el hover actual, luego cierra.
|
||||
void TilePicker::commitRectSelection() {
|
||||
int end_tile = (hover_tile_ >= 0) ? hover_tile_ : last_valid_hover_;
|
||||
if (end_tile >= 0 && rect_start_tile_ >= 0) {
|
||||
int c1 = rect_start_tile_ % tileset_width_;
|
||||
int r1 = rect_start_tile_ / tileset_width_;
|
||||
int c2 = end_tile % tileset_width_;
|
||||
int r2 = end_tile / tileset_width_;
|
||||
int col_min = std::min(c1, c2);
|
||||
int col_max = std::max(c1, c2);
|
||||
int row_min = std::min(r1, r2);
|
||||
int row_max = std::max(r1, r2);
|
||||
int width = col_max - col_min + 1;
|
||||
int height = row_max - row_min + 1;
|
||||
if (on_select) { on_select(col_min, row_min, width, height); }
|
||||
}
|
||||
selecting_rect_ = false;
|
||||
rect_start_tile_ = -1;
|
||||
close();
|
||||
}
|
||||
|
||||
// Maneja eventos del picker
|
||||
void TilePicker::handleEvent(const SDL_Event& event) {
|
||||
if (!open_) { return; }
|
||||
@@ -226,23 +247,7 @@ void TilePicker::handleEvent(const SDL_Event& event) {
|
||||
}
|
||||
|
||||
if (event.type == SDL_EVENT_MOUSE_BUTTON_UP && event.button.button == SDL_BUTTON_LEFT && selecting_rect_) {
|
||||
int end_tile = (hover_tile_ >= 0) ? hover_tile_ : last_valid_hover_;
|
||||
if (end_tile >= 0 && rect_start_tile_ >= 0) {
|
||||
int c1 = rect_start_tile_ % tileset_width_;
|
||||
int r1 = rect_start_tile_ / tileset_width_;
|
||||
int c2 = end_tile % tileset_width_;
|
||||
int r2 = end_tile / tileset_width_;
|
||||
int col_min = std::min(c1, c2);
|
||||
int col_max = std::max(c1, c2);
|
||||
int row_min = std::min(r1, r2);
|
||||
int row_max = std::max(r1, r2);
|
||||
int width = col_max - col_min + 1;
|
||||
int height = row_max - row_min + 1;
|
||||
if (on_select) { on_select(col_min, row_min, width, height); }
|
||||
}
|
||||
selecting_rect_ = false;
|
||||
rect_start_tile_ = -1;
|
||||
close();
|
||||
commitRectSelection();
|
||||
}
|
||||
|
||||
if (event.type == SDL_EVENT_MOUSE_WHEEL) {
|
||||
|
||||
@@ -44,6 +44,7 @@ class TilePicker {
|
||||
|
||||
private:
|
||||
void updateMousePosition();
|
||||
void commitRectSelection();
|
||||
|
||||
bool open_{false};
|
||||
std::shared_ptr<Surface> tileset_; // Surface del tileset original
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
|
||||
@@ -24,7 +25,7 @@ class AnimatedSprite;
|
||||
*/
|
||||
class Door : public SolidActor {
|
||||
public:
|
||||
enum class State : int {
|
||||
enum class State : std::uint8_t {
|
||||
CLOSED = 0,
|
||||
OPENING = 1,
|
||||
OPENED = 2
|
||||
|
||||
@@ -82,7 +82,7 @@ void MovingPlatform::recalcSegmentLength() {
|
||||
|
||||
float dx = path_[to].x - path_[from].x;
|
||||
float dy = path_[to].y - path_[from].y;
|
||||
segment_length_ = std::sqrt(dx * dx + dy * dy);
|
||||
segment_length_ = std::sqrt((dx * dx) + (dy * dy));
|
||||
}
|
||||
|
||||
// Avanza al siguiente segmento
|
||||
@@ -174,8 +174,8 @@ void MovingPlatform::update(float delta_time) {
|
||||
int from = getSegmentFrom();
|
||||
int to = getSegmentTo();
|
||||
|
||||
float new_x = path_[from].x + (path_[to].x - path_[from].x) * t;
|
||||
float new_y = path_[from].y + (path_[to].y - path_[from].y) * t;
|
||||
float new_x = path_[from].x + ((path_[to].x - path_[from].x) * t);
|
||||
float new_y = path_[from].y + ((path_[to].y - path_[from].y) * t);
|
||||
sprite_->setPosX(new_x);
|
||||
sprite_->setPosY(new_y);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
@@ -18,7 +19,7 @@ struct Waypoint {
|
||||
};
|
||||
|
||||
// Modo de recorrido de la ruta
|
||||
enum class LoopMode { PINGPONG,
|
||||
enum class LoopMode : std::uint8_t { PINGPONG,
|
||||
CIRCULAR };
|
||||
|
||||
// Tipo de función de easing
|
||||
|
||||
@@ -540,18 +540,10 @@ void Player::transitionToState(State state) {
|
||||
|
||||
switch (state) {
|
||||
case State::ON_GROUND:
|
||||
vy_ = 0;
|
||||
// Clamp vx al aterrizar (el salto puede dar un boost extra)
|
||||
if (vx_ > HORIZONTAL_VELOCITY) { vx_ = HORIZONTAL_VELOCITY; }
|
||||
if (vx_ < -HORIZONTAL_VELOCITY) { vx_ = -HORIZONTAL_VELOCITY; }
|
||||
if (previous_state_ == State::ON_AIR) {
|
||||
Audio::get()->playSound(land_sound_, Audio::Group::GAME);
|
||||
}
|
||||
break;
|
||||
case State::ON_SLOPE:
|
||||
vy_ = 0;
|
||||
if (vx_ > HORIZONTAL_VELOCITY) { vx_ = HORIZONTAL_VELOCITY; }
|
||||
if (vx_ < -HORIZONTAL_VELOCITY) { vx_ = -HORIZONTAL_VELOCITY; }
|
||||
// Clamp vx al aterrizar (el salto puede dar un boost extra)
|
||||
vx_ = std::clamp(vx_, -HORIZONTAL_VELOCITY, HORIZONTAL_VELOCITY);
|
||||
if (previous_state_ == State::ON_AIR) {
|
||||
Audio::get()->playSound(land_sound_, Audio::Group::GAME);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <utility>
|
||||
@@ -17,13 +18,13 @@ class SolidActor;
|
||||
class Player {
|
||||
public:
|
||||
// --- Enums y Structs ---
|
||||
enum class State {
|
||||
enum class State : std::uint8_t {
|
||||
ON_GROUND,
|
||||
ON_SLOPE,
|
||||
ON_AIR,
|
||||
};
|
||||
|
||||
enum class Direction {
|
||||
enum class Direction : std::uint8_t {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
UP,
|
||||
@@ -151,7 +152,7 @@ class Player {
|
||||
void syncSpriteAndCollider();
|
||||
void placeSprite();
|
||||
void animate(float delta_time);
|
||||
auto handleBorders() const -> Room::Border;
|
||||
[[nodiscard]] auto handleBorders() const -> Room::Border;
|
||||
|
||||
// --- Inicialización ---
|
||||
void initSprite(const std::string& animations_path);
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
*/
|
||||
class SolidActor {
|
||||
public:
|
||||
// NOLINTNEXTLINE(performance-enum-size) -- bitmask con margen para crecer
|
||||
enum Flags : uint32_t {
|
||||
BLOCKS_PLAYER = 1U << 0U,
|
||||
CARRY_ON_TOP = 1U << 1U,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
CollisionMap::CollisionMap(std::vector<int> collision_tile_map)
|
||||
: collision_tile_map_(std::move(collision_tile_map)),
|
||||
extended_tile_map_(EW * EH, 0),
|
||||
extended_tile_map_(static_cast<size_t>(EW) * static_cast<size_t>(EH), 0),
|
||||
tile_collider_(extended_tile_map_, EW, EH, CollisionBorder::PX) {
|
||||
buildExtendedCenter();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
@@ -28,7 +29,7 @@ class TilemapRenderer;
|
||||
class Room {
|
||||
public:
|
||||
// -- Enumeraciones y estructuras ---
|
||||
enum class Border : int {
|
||||
enum class Border : std::uint8_t {
|
||||
TOP = 0,
|
||||
RIGHT = 1,
|
||||
BOTTOM = 2,
|
||||
|
||||
@@ -78,7 +78,7 @@ auto RoomFormat::convertAutoSurface(const fkyaml::node& node) -> int {
|
||||
|
||||
auto RoomFormat::flattenTilemap(const std::vector<std::vector<int>>& tilemap_2d) -> std::vector<int> {
|
||||
std::vector<int> tilemap_flat;
|
||||
tilemap_flat.reserve(Map::WIDTH * Map::HEIGHT);
|
||||
tilemap_flat.reserve(static_cast<size_t>(Map::WIDTH) * static_cast<size_t>(Map::HEIGHT));
|
||||
|
||||
for (const auto& row : tilemap_2d) {
|
||||
for (int tile : row) {
|
||||
@@ -535,8 +535,8 @@ auto RoomFormat::createDefault() -> Room::Data {
|
||||
data.right_room = "0";
|
||||
|
||||
// Tilemaps del tamaño correcto, vacíos
|
||||
data.tile_map.resize(Map::WIDTH * Map::HEIGHT, -1);
|
||||
data.collision_tile_map.resize(Map::WIDTH * Map::HEIGHT, 0);
|
||||
data.tile_map.resize(static_cast<size_t>(Map::WIDTH) * static_cast<size_t>(Map::HEIGHT), -1);
|
||||
data.collision_tile_map.resize(static_cast<size_t>(Map::WIDTH) * static_cast<size_t>(Map::HEIGHT), 0);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -16,13 +16,9 @@
|
||||
|
||||
// Constructor
|
||||
Scoreboard::Scoreboard(std::shared_ptr<Data> data)
|
||||
: data_(std::move(data)) {
|
||||
const float SURFACE_WIDTH = Options::game.width;
|
||||
constexpr float SURFACE_HEIGHT = 24.0F; // 3 líneas de 8px
|
||||
|
||||
surface_ = std::make_shared<Surface>(SURFACE_WIDTH, SURFACE_HEIGHT);
|
||||
surface_dest_ = {.x = 0, .y = Options::game.height - SURFACE_HEIGHT, .w = SURFACE_WIDTH, .h = SURFACE_HEIGHT};
|
||||
}
|
||||
: data_(std::move(data)),
|
||||
surface_(std::make_shared<Surface>(Options::game.width, SURFACE_HEIGHT)),
|
||||
surface_dest_{.x = 0, .y = Options::game.height - SURFACE_HEIGHT, .w = Options::game.width, .h = SURFACE_HEIGHT} {}
|
||||
|
||||
// Pinta el objeto en pantalla
|
||||
void Scoreboard::render() {
|
||||
@@ -91,19 +87,19 @@ void Scoreboard::fillTexture() {
|
||||
const std::string TIME_LABEL = Locale::get()->get("scoreboard.time");
|
||||
|
||||
// Ancho total: labels proporcionales + valores monoespaciados
|
||||
const int LINE1_W = text->length(LIVES_LABEL) + text->lengthMono(LIVES_STR, MONO_W) + text->length(SEP) + text->length(ITEMS_LABEL) + text->lengthMono(ITEMS_STR, MONO_W) + text->length(SEP) + text->length(TIME_LABEL) + text->lengthMono(TIME_STR, MONO_W);
|
||||
const int LINE1_W = text->length(LIVES_LABEL) + Text::lengthMono(LIVES_STR, MONO_W) + text->length(SEP) + text->length(ITEMS_LABEL) + Text::lengthMono(ITEMS_STR, MONO_W) + text->length(SEP) + text->length(TIME_LABEL) + Text::lengthMono(TIME_STR, MONO_W);
|
||||
|
||||
int x = (CANVAS_W - LINE1_W) / 2;
|
||||
text->writeColored(x, LINE1_Y, LIVES_LABEL, LABEL_COLOR);
|
||||
x += text->length(LIVES_LABEL);
|
||||
text->writeColoredMono(x, LINE1_Y, LIVES_STR, VALUE_COLOR, MONO_W);
|
||||
x += text->lengthMono(LIVES_STR, MONO_W);
|
||||
x += Text::lengthMono(LIVES_STR, MONO_W);
|
||||
text->writeColored(x, LINE1_Y, SEP, LABEL_COLOR);
|
||||
x += text->length(SEP);
|
||||
text->writeColored(x, LINE1_Y, ITEMS_LABEL, LABEL_COLOR);
|
||||
x += text->length(ITEMS_LABEL);
|
||||
text->writeColoredMono(x, LINE1_Y, ITEMS_STR, VALUE_COLOR, MONO_W);
|
||||
x += text->lengthMono(ITEMS_STR, MONO_W);
|
||||
x += Text::lengthMono(ITEMS_STR, MONO_W);
|
||||
text->writeColored(x, LINE1_Y, SEP, LABEL_COLOR);
|
||||
x += text->length(SEP);
|
||||
text->writeColored(x, LINE1_Y, TIME_LABEL, LABEL_COLOR);
|
||||
|
||||
@@ -39,6 +39,7 @@ class Scoreboard {
|
||||
|
||||
// Constantes de tiempo
|
||||
// Posición de los elementos (2 líneas centradas verticalmente en surface de 24px)
|
||||
static constexpr float SURFACE_HEIGHT = 24.0F; // 3 líneas de 8px
|
||||
static constexpr int LINE1_Y = 5;
|
||||
static constexpr int LINE2_Y = 13;
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "utils/defines.hpp"
|
||||
|
||||
class TileCollider {
|
||||
public:
|
||||
enum class Tile : int {
|
||||
enum class Tile : std::uint8_t {
|
||||
EMPTY = 0,
|
||||
WALL = 1,
|
||||
PASSABLE = 2,
|
||||
|
||||
@@ -11,9 +11,8 @@ TilemapRenderer::TilemapRenderer(std::vector<int> tile_map, int tile_set_width,
|
||||
: tile_map_(std::move(tile_map)),
|
||||
tile_set_width_(tile_set_width),
|
||||
tileset_surface_(std::move(tileset_surface)),
|
||||
bg_color_(bg_color) {
|
||||
map_surface_ = std::make_shared<Surface>(PlayArea::WIDTH, PlayArea::HEIGHT);
|
||||
}
|
||||
bg_color_(bg_color),
|
||||
map_surface_(std::make_shared<Surface>(PlayArea::WIDTH, PlayArea::HEIGHT)) {}
|
||||
|
||||
void TilemapRenderer::initialize(const std::vector<int>& collision_tile_map) {
|
||||
fillMapTexture(collision_tile_map);
|
||||
|
||||
@@ -368,12 +368,14 @@ namespace Options {
|
||||
if (sh_node.contains("current_postfx_preset")) {
|
||||
try {
|
||||
video.shader.current_postfx_preset_name = sh_node["current_postfx_preset"].get_value<std::string>();
|
||||
} catch (...) {}
|
||||
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
|
||||
}
|
||||
}
|
||||
if (sh_node.contains("current_crtpi_preset")) {
|
||||
try {
|
||||
video.shader.current_crtpi_preset_name = sh_node["current_crtpi_preset"].get_value<std::string>();
|
||||
} catch (...) {}
|
||||
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,24 +553,28 @@ namespace Options {
|
||||
if (a.contains("enabled")) {
|
||||
try {
|
||||
audio.enabled = a["enabled"].get_value<bool>();
|
||||
} catch (...) {}
|
||||
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
|
||||
}
|
||||
}
|
||||
if (a.contains("volume")) {
|
||||
try {
|
||||
audio.volume = std::clamp(a["volume"].get_value<float>(), 0.0F, 1.0F);
|
||||
} catch (...) {}
|
||||
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
|
||||
}
|
||||
}
|
||||
if (a.contains("music")) {
|
||||
const auto& m = a["music"];
|
||||
if (m.contains("enabled")) {
|
||||
try {
|
||||
audio.music.enabled = m["enabled"].get_value<bool>();
|
||||
} catch (...) {}
|
||||
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
|
||||
}
|
||||
}
|
||||
if (m.contains("volume")) {
|
||||
try {
|
||||
audio.music.volume = std::clamp(m["volume"].get_value<float>(), 0.0F, 1.0F);
|
||||
} catch (...) {}
|
||||
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
|
||||
}
|
||||
}
|
||||
}
|
||||
if (a.contains("sound")) {
|
||||
@@ -576,12 +582,14 @@ namespace Options {
|
||||
if (s.contains("enabled")) {
|
||||
try {
|
||||
audio.sound.enabled = s["enabled"].get_value<bool>();
|
||||
} catch (...) {}
|
||||
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
|
||||
}
|
||||
}
|
||||
if (s.contains("volume")) {
|
||||
try {
|
||||
audio.sound.volume = std::clamp(s["volume"].get_value<float>(), 0.0F, 1.0F);
|
||||
} catch (...) {}
|
||||
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -594,27 +602,32 @@ namespace Options {
|
||||
if (c.contains("transparent")) {
|
||||
try {
|
||||
console.transparent = c["transparent"].get_value<bool>();
|
||||
} catch (...) {}
|
||||
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
|
||||
}
|
||||
}
|
||||
if (c.contains("bg_color")) {
|
||||
try {
|
||||
console.bg_color = std::clamp(c["bg_color"].get_value<int>(), 0, 255);
|
||||
} catch (...) {}
|
||||
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
|
||||
}
|
||||
}
|
||||
if (c.contains("msg_color")) {
|
||||
try {
|
||||
console.msg_color = std::clamp(c["msg_color"].get_value<int>(), 0, 255);
|
||||
} catch (...) {}
|
||||
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
|
||||
}
|
||||
}
|
||||
if (c.contains("prompt_color")) {
|
||||
try {
|
||||
console.prompt_color = std::clamp(c["prompt_color"].get_value<int>(), 0, 255);
|
||||
} catch (...) {}
|
||||
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
|
||||
}
|
||||
}
|
||||
if (c.contains("command_color")) {
|
||||
try {
|
||||
console.command_color = std::clamp(c["command_color"].get_value<int>(), 0, 255);
|
||||
} catch (...) {}
|
||||
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -876,7 +889,8 @@ namespace Options {
|
||||
if (node.contains(key)) {
|
||||
try {
|
||||
target = node[key].get_value<float>();
|
||||
} catch (...) {}
|
||||
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1166,32 +1180,38 @@ namespace Options {
|
||||
if (p.contains("mask_type")) {
|
||||
try {
|
||||
preset.mask_type = p["mask_type"].get_value<int>();
|
||||
} catch (...) {}
|
||||
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
|
||||
}
|
||||
}
|
||||
if (p.contains("enable_scanlines")) {
|
||||
try {
|
||||
preset.enable_scanlines = p["enable_scanlines"].get_value<bool>();
|
||||
} catch (...) {}
|
||||
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
|
||||
}
|
||||
}
|
||||
if (p.contains("enable_multisample")) {
|
||||
try {
|
||||
preset.enable_multisample = p["enable_multisample"].get_value<bool>();
|
||||
} catch (...) {}
|
||||
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
|
||||
}
|
||||
}
|
||||
if (p.contains("enable_gamma")) {
|
||||
try {
|
||||
preset.enable_gamma = p["enable_gamma"].get_value<bool>();
|
||||
} catch (...) {}
|
||||
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
|
||||
}
|
||||
}
|
||||
if (p.contains("enable_curvature")) {
|
||||
try {
|
||||
preset.enable_curvature = p["enable_curvature"].get_value<bool>();
|
||||
} catch (...) {}
|
||||
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
|
||||
}
|
||||
}
|
||||
if (p.contains("enable_sharper")) {
|
||||
try {
|
||||
preset.enable_sharper = p["enable_sharper"].get_value<bool>();
|
||||
} catch (...) {}
|
||||
} catch (...) { /* @INTENTIONAL: campo yaml ausente o malformado → dejar default */
|
||||
}
|
||||
}
|
||||
crtpi_presets.push_back(preset);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
/*
|
||||
Namespace SceneManager: gestiona el flujo entre las diferentes escenas del juego.
|
||||
|
||||
@@ -10,7 +12,7 @@
|
||||
namespace SceneManager {
|
||||
|
||||
// --- Escenas del programa ---
|
||||
enum class Scene {
|
||||
enum class Scene : std::uint8_t {
|
||||
LOGO, // Pantalla del logo
|
||||
TITLE, // Pantalla de título/menú principal
|
||||
GAME, // Juego principal
|
||||
@@ -19,7 +21,7 @@ namespace SceneManager {
|
||||
};
|
||||
|
||||
// --- Opciones para transiciones entre escenas ---
|
||||
enum class Options {
|
||||
enum class Options : std::uint8_t {
|
||||
NONE, // Sin opciones especiales
|
||||
LOGO_TO_TITLE, // Del logo al título
|
||||
};
|
||||
|
||||
@@ -818,6 +818,7 @@ auto Game::getOrCreateRoom(const std::string& room_path) -> std::shared_ptr<Room
|
||||
|
||||
// Construye el tilemap extendido de la room actual con los bordes de las adyacentes
|
||||
void Game::buildCollisionBorders() {
|
||||
// NOLINTBEGIN(readability-identifier-naming) -- lambdas locales: se leen como llamadas a función, no son constantes.
|
||||
// Helper: obtiene el collision tilemap de una room adyacente (nullptr si no existe)
|
||||
auto getAdjacentCollision = [&](Room::Border b) -> const std::vector<int>* {
|
||||
auto name = room_->getRoom(b);
|
||||
@@ -843,6 +844,7 @@ void Game::buildCollisionBorders() {
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
|
||||
CollisionMap::AdjacentData adj;
|
||||
adj.top = getAdjacentCollision(Room::Border::TOP);
|
||||
@@ -861,6 +863,7 @@ void Game::buildCollisionBorders() {
|
||||
// que los sweeps del Player vean AABBs dinámicos (puertas, plataformas)
|
||||
// de la room vecina cuando está cerca del borde, sin tener que esperar
|
||||
// a una transición completa de room.
|
||||
// NOLINTNEXTLINE(readability-identifier-naming) -- lambda local: se lee como función, no es constante.
|
||||
auto getAdjacentSolidActors = [&](Room::Border b) -> SolidActorManager* {
|
||||
auto name = room_->getRoom(b);
|
||||
if (name == "0") { return nullptr; }
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint> // Para uint8_t
|
||||
#include <initializer_list> // Para initializer_list
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
@@ -18,12 +19,12 @@ class Surface;
|
||||
class Game {
|
||||
public:
|
||||
// --- Estructuras ---
|
||||
enum class Mode {
|
||||
enum class Mode : std::uint8_t {
|
||||
DEMO,
|
||||
GAME
|
||||
};
|
||||
|
||||
enum class State {
|
||||
enum class State : std::uint8_t {
|
||||
PLAYING, // Normal gameplay
|
||||
BLACK_SCREEN, // Black screen after death (0.30s)
|
||||
GAME_OVER, // Intermediate state before changing scene
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional> // Para std::function
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <vector> // Para vector
|
||||
@@ -16,7 +17,7 @@ class Logo {
|
||||
using EasingFunction = std::function<float(float)>; // Función de easing (permite lambdas)
|
||||
|
||||
// --- Enumeraciones ---
|
||||
enum class State {
|
||||
enum class State : std::uint8_t {
|
||||
INITIAL, // Espera inicial
|
||||
JAILGAMES_SLIDE_IN, // Las líneas de JAILGAMES se deslizan hacia el centro
|
||||
SINCE_1998_FADE_IN, // Aparición gradual del texto "Since 1998"
|
||||
|
||||
@@ -271,7 +271,7 @@ void Title::renderMainMenu() {
|
||||
const int TOTAL_HEIGHT = 2 * SPACING; // 2 espacios entre 3 items
|
||||
const int START_Y = MENU_CENTER_Y - (TOTAL_HEIGHT / 2);
|
||||
|
||||
auto* loc = Locale::get();
|
||||
const auto* loc = Locale::get();
|
||||
menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, PlayArea::CENTER_X, START_Y, loc->get("title.menu.play"), 1, COLOR);
|
||||
menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, PlayArea::CENTER_X, START_Y + SPACING, loc->get("title.menu.keyboard"), 1, COLOR);
|
||||
menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, PlayArea::CENTER_X, START_Y + (2 * SPACING), loc->get("title.menu.joystick"), 1, COLOR);
|
||||
@@ -360,20 +360,6 @@ auto Title::isKeyDuplicate(SDL_Scancode scancode, int current_step) -> bool { /
|
||||
return false;
|
||||
}
|
||||
|
||||
// Retorna el nombre de la accion para el paso actual
|
||||
auto Title::getActionName(int step) -> std::string { // NOLINT(readability-convert-member-functions-to-static)
|
||||
switch (step) {
|
||||
case 0:
|
||||
return "LEFT";
|
||||
case 1:
|
||||
return "RIGHT";
|
||||
case 2:
|
||||
return "JUMP";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
// Aplica y guarda las teclas redefinidas
|
||||
void Title::applyKeyboardRemap() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
// Guardar las nuevas teclas en Options::controls
|
||||
@@ -402,7 +388,7 @@ void Title::renderKeyboardRemap() const {
|
||||
const int START_Y = MENU_CENTER_Y - (2 * TEXT_SIZE); // Centrado aproximado
|
||||
|
||||
// Mensaje principal: "PRESS KEY FOR [ACTION]" o "KEYS DEFINED" si completado
|
||||
auto* loc = Locale::get();
|
||||
const auto* loc = Locale::get();
|
||||
if (remap_step_ >= 3) {
|
||||
menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, PlayArea::CENTER_X, START_Y, loc->get("title.keys.defined"), 1, COLOR);
|
||||
} else {
|
||||
@@ -446,7 +432,7 @@ void Title::renderJoystickRemap() const {
|
||||
const int START_Y = MENU_CENTER_Y - (2 * TEXT_SIZE); // Centrado aproximado
|
||||
|
||||
// Mensaje principal: "PRESS BUTTON FOR [ACTION]" o "BUTTONS DEFINED" si completado
|
||||
auto* loc = Locale::get();
|
||||
const auto* loc = Locale::get();
|
||||
if (remap_step_ >= 3) {
|
||||
menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, PlayArea::CENTER_X, START_Y, loc->get("title.buttons.defined"), 1, COLOR);
|
||||
} else {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <array> // Para std::array
|
||||
#include <cstdint>
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
|
||||
@@ -24,7 +25,7 @@ class Title {
|
||||
|
||||
private:
|
||||
// --- Estructuras y enumeraciones ---
|
||||
enum class State {
|
||||
enum class State : std::uint8_t {
|
||||
MAIN_MENU,
|
||||
FADE_MENU,
|
||||
POST_FADE_MENU,
|
||||
@@ -58,7 +59,6 @@ class Title {
|
||||
auto isButtonDuplicate(int button, int current_step) -> bool; // Valida si un boton esta duplicado
|
||||
void applyKeyboardRemap(); // Aplica y guarda las teclas redefinidas
|
||||
void applyJoystickRemap(); // Aplica y guarda los botones del gamepad redefinidos
|
||||
static auto getActionName(int step) -> std::string; // Retorna el nombre de la accion (LEFT/RIGHT/JUMP)
|
||||
static auto getButtonName(int button) -> std::string; // Retorna el nombre amigable del boton del gamepad
|
||||
void fillTitleSurface(); // Dibuja los elementos en la surface
|
||||
|
||||
|
||||
@@ -64,9 +64,16 @@ auto Console::wrapText(const std::string& text) const -> std::vector<std::string
|
||||
std::istringstream word_stream(segment);
|
||||
std::string word;
|
||||
while (word_stream >> word) {
|
||||
const std::string TEST = current_line.empty() ? word : (current_line + ' ' + word);
|
||||
if (text_->length(TEST) <= MAX_PX) {
|
||||
current_line = TEST;
|
||||
std::string test;
|
||||
if (current_line.empty()) {
|
||||
test = word;
|
||||
} else {
|
||||
test = current_line;
|
||||
test += ' ';
|
||||
test += word;
|
||||
}
|
||||
if (text_->length(test) <= MAX_PX) {
|
||||
current_line = test;
|
||||
} else {
|
||||
if (!current_line.empty()) { result.push_back(current_line); }
|
||||
current_line = word;
|
||||
@@ -381,7 +388,10 @@ void Console::handleEvent(const SDL_Event& event) { // NOLINT(readability-funct
|
||||
const auto OPTS = registry_.getCompletions(BASE_CMD);
|
||||
for (const auto& arg : OPTS) {
|
||||
if (SUB_PREFIX.empty() || std::string_view{arg}.starts_with(SUB_PREFIX)) {
|
||||
tab_matches_.emplace_back(BASE_CMD + " " + arg);
|
||||
std::string match = BASE_CMD;
|
||||
match += ' ';
|
||||
match += arg;
|
||||
tab_matches_.emplace_back(std::move(match));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <deque> // Para deque (historial)
|
||||
#include <functional> // Para function
|
||||
#include <memory> // Para shared_ptr
|
||||
@@ -46,7 +47,7 @@ class Console {
|
||||
std::function<void(bool)> on_toggle;
|
||||
|
||||
private:
|
||||
enum class Status {
|
||||
enum class Status : std::uint8_t {
|
||||
HIDDEN,
|
||||
RISING,
|
||||
ACTIVE,
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
// Toggle genérico para comandos booleanos ON/OFF (reemplaza macro BOOL_TOGGLE_CMD)
|
||||
static auto boolToggle(
|
||||
const std::string& label,
|
||||
bool& option,
|
||||
const bool& option,
|
||||
const std::function<void()>& toggle_fn,
|
||||
const std::vector<std::string>& args) -> std::string {
|
||||
if (args.empty()) {
|
||||
@@ -259,7 +259,9 @@ static auto cmdZoom(const std::vector<std::string>& args) -> std::string {
|
||||
if (N == Options::window.zoom) { return "Zoom already " + std::to_string(N); }
|
||||
Screen::get()->setWindowZoom(N);
|
||||
return "Zoom " + std::to_string(Options::window.zoom);
|
||||
} catch (...) {}
|
||||
} catch (...) {
|
||||
// @INTENTIONAL: argumento no numérico → mostrar usage
|
||||
}
|
||||
return "usage: zoom [up|down|<1-" + std::to_string(Screen::getMaxZoom()) + ">]";
|
||||
}
|
||||
|
||||
@@ -704,7 +706,7 @@ static auto cmdEdit(const std::vector<std::string>& args) -> std::string { // N
|
||||
if ((args[0] == "SHOW" || args[0] == "HIDE") && args.size() >= 2) {
|
||||
if ((MapEditor::get() == nullptr) || !MapEditor::get()->isActive()) { return "Editor not active"; }
|
||||
bool show = (args[0] == "SHOW");
|
||||
if (args[1] == "INFO") { return MapEditor::get()->showInfo(show); }
|
||||
if (args[1] == "INFO") { return MapEditor::showInfo(show); }
|
||||
if (args[1] == "GRID") { return MapEditor::get()->showGrid(show); }
|
||||
}
|
||||
// EDIT DRAW / EDIT COLLISION
|
||||
@@ -878,6 +880,7 @@ static auto cmdCheat(const std::vector<std::string>& args) -> std::string { //
|
||||
auto& cheat = Options::cheats.infinite_lives;
|
||||
using State = Options::Cheat::State;
|
||||
const std::vector<std::string> REST(args.begin() + 2, args.end());
|
||||
// cppcheck-suppress knownConditionTrueFalse -- cppcheck no infiere que REST puede estar vacío cuando args.size() == 2.
|
||||
if (REST.empty()) {
|
||||
cheat = (cheat == State::ENABLED) ? State::DISABLED : State::ENABLED;
|
||||
} else if (REST[0] == "ON") {
|
||||
@@ -1113,7 +1116,7 @@ void CommandRegistry::registerHandlers() { // NOLINT(readability-function-cogni
|
||||
if (path.find("tilesets") == std::string::npos) { continue; }
|
||||
std::string name = getFileName(path);
|
||||
auto dot = name.rfind('.');
|
||||
if (dot != std::string::npos) { name = name.substr(0, dot); }
|
||||
if (dot != std::string::npos) { name.resize(dot); }
|
||||
result.push_back(toUpper(name));
|
||||
}
|
||||
return result;
|
||||
@@ -1362,7 +1365,7 @@ auto CommandRegistry::getCompletions(const std::string& path) const -> std::vect
|
||||
if (!active_scope_.empty()) {
|
||||
std::string root = path;
|
||||
auto space = root.find(' ');
|
||||
if (space != std::string::npos) { root = root.substr(0, space); }
|
||||
if (space != std::string::npos) { root.resize(space); }
|
||||
const auto* cmd = findCommand(root);
|
||||
if (cmd != nullptr && !isCommandVisible(*cmd)) { return {}; }
|
||||
}
|
||||
|
||||
@@ -116,8 +116,6 @@ void Notifier::update(float delta_time) {
|
||||
}
|
||||
|
||||
case Status::FINISHED:
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string, basic_string
|
||||
#include <vector> // Para vector
|
||||
@@ -13,13 +14,13 @@ class DeltaTimer; // lines 11-11
|
||||
class Notifier {
|
||||
public:
|
||||
// Justificado para las notificaciones
|
||||
enum class TextAlign {
|
||||
enum class TextAlign : std::uint8_t {
|
||||
LEFT,
|
||||
CENTER,
|
||||
};
|
||||
|
||||
// Forma de las notificaciones
|
||||
enum class Shape {
|
||||
enum class Shape : std::uint8_t {
|
||||
ROUNDED,
|
||||
SQUARED,
|
||||
};
|
||||
@@ -65,7 +66,7 @@ class Notifier {
|
||||
|
||||
private:
|
||||
// Tipos anidados
|
||||
enum class Status {
|
||||
enum class Status : std::uint8_t {
|
||||
RISING,
|
||||
STAY,
|
||||
VANISHING,
|
||||
|
||||
Reference in New Issue
Block a user