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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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