Files
orni-attack/source/game/scenes/logo_scene.cpp
T
JailDesigner c45e524109 Lint: clang-tidy --fix mecánico (trailing return, default member init, auto, enum size)
Pase automático de clang-tidy --fix sobre el conjunto de checks que son
puro transform de sintaxis y no rompen API. Invocado con
--format-style=none para que clang-tidy NO arrastre clang-format sobre
las líneas tocadas (evita la regla NamespaceIndentation: All del
.clang-format reformateando solo trozos del archivo).

Checks aplicados:

- modernize-use-trailing-return-type (193 hits): 'int foo()' →
  'auto foo() -> int'. Estilo coherente con la convención del proyecto.
- modernize-use-default-member-init (36 hits): inicialización de
  miembros pasa de la lista del constructor a la declaración. Reduce
  duplicación cuando hay varios constructores con los mismos defaults.
- modernize-use-auto (6 hits): tipos largos sustituidos por auto donde
  el tipo es evidente del contexto (new T, dynamic_cast, etc).
- modernize-use-starts-ends-with (2 hits): s.rfind(x) == 0 →
  s.starts_with(x), aprovechando C++20.
- performance-enum-size (10 hits): enums pequeños declaran tipo
  subyacente (uint8_t / similar) para reducir tamaño y precisar layout.

NO aplicado en este pase (riesgo de cambios semánticos o de API):
- readability-identifier-naming (renames pueden romper callsites parciales)
- readability-convert-member-functions-to-static (cambia firma)
- readability-use-anyofallof (reescribe loops, side effects)
- readability-function-cognitive-complexity (requiere refactor manual)
- bugs reales (bugprone-*, clang-diagnostic-*) → uno a uno

Cambios manuales asociados:
- SDLManager::clear() ahora devuelve bool: propaga el resultado de
  beginFrame al caller para que Director::runFrameLoop salte
  draw+present cuando la swapchain no esté disponible (ventana
  minimizada). Antes la función ignoraba el [[nodiscard]] del
  beginFrame y los vértices se acumulaban en el batch sin nadie que
  los consumiera.
- vector_text.cpp: borrada la línea suelta "// Test pre-commit hook"
  que quedó como cruft.

clang-tidy crashea en LLVM 19.1 con performance-noexcept-move-constructor
(recursión infinita en ExceptionSpecAnalyzer al procesar std::set);
check deshabilitado en .clang-tidy con comentario explicativo.

Build limpio, smoke test OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:59:56 +02:00

367 lines
12 KiB
C++

// logo_scene.cpp - Implementació de l'escena logo
// © 2026 JailDesigner
#include "logo_scene.hpp"
#include <algorithm>
#include <cfloat>
#include <iostream>
#include <random>
#include <set>
#include "core/audio/audio.hpp"
#include "core/graphics/shape_loader.hpp"
#include "core/input/input.hpp"
#include "core/rendering/shape_renderer.hpp"
#include "core/system/scene_context.hpp"
// Using declarations per simplificar el codi
using SceneManager::SceneContext;
using SceneType = SceneContext::SceneType;
using Option = SceneContext::Option;
// Helper: calcular el progrés individual de una lletra
// en función del progrés global (efecte seqüencial)
static auto calcular_progress_letra(size_t letra_index, size_t num_letras, float global_progress, float threshold) -> float {
if (num_letras == 0) {
return 1.0F;
}
// Calcular time per lletra
float duration_per_letra = 1.0F / static_cast<float>(num_letras);
float step = threshold * duration_per_letra;
float start = static_cast<float>(letra_index) * step;
float end = start + duration_per_letra;
// Interpolar progrés
if (global_progress < start) {
return 0.0F; // Aún no ha començat
}
if (global_progress >= end) {
return 1.0F; // Completament apareguda
}
return (global_progress - start) / (end - start);
}
LogoScene::LogoScene(SDLManager& sdl, SceneContext& context)
: sdl_(sdl),
context_(context),
debris_manager_(std::make_unique<Effects::DebrisManager>(sdl.getRenderer()))
{
std::cout << "SceneType Logo: Inicialitzant...\n";
// Consumir opciones (LOGO no processa opciones actualment)
auto option = context_.consumeOption();
(void)option; // Suprimir warning
so_reproduit_.fill(false); // Inicialitzar seguiment de sons
inicialitzar_lletres();
}
LogoScene::~LogoScene() {
// Aturar todos los sons y la música
Audio::get()->stopAllSounds();
std::cout << "SceneType Logo: Sons parados\n";
}
auto LogoScene::isFinished() const -> bool {
return context_.nextScene() != SceneType::LOGO;
}
void LogoScene::handleEvent(const SDL_Event& event) {
// La lógica de skip se decide en update() consultando el estado de Input;
// aquí no hay eventos puntuales que procesar.
(void)event;
}
void LogoScene::inicialitzar_lletres() {
using namespace Graphics;
// Llista de archivos .shp (A repetida para las dues A's)
std::vector<std::string> archivos = {
"logo/letra_j.shp",
"logo/letra_a.shp",
"logo/letra_i.shp",
"logo/letra_l.shp",
"logo/letra_g.shp",
"logo/letra_a.shp",
"logo/letra_m.shp",
"logo/letra_e.shp",
"logo/letra_s.shp"};
// Pas 1: Carregar todas las formes i calcular amplades
float ancho_total = 0.0F;
for (const auto& file : archivos) {
auto shape = ShapeLoader::load(file);
if (!shape || !shape->isValid()) {
std::cerr << "[LogoScene] Error carregant " << file << '\n';
continue;
}
// Calcular bounding box de la shape (trobar ancho)
float min_x = FLT_MAX;
float max_x = -FLT_MAX;
for (const auto& prim : shape->get_primitives()) {
for (const auto& point : prim.points) {
min_x = std::min(min_x, point.x);
max_x = std::max(max_x, point.x);
}
}
float ancho_sin_escalar = max_x - min_x;
// IMPORTANT: Escalar ancho i offset con ESCALA_FINAL
// per que las posicions finals coincideixin con la mida real de las lletres
float ancho = ancho_sin_escalar * ESCALA_FINAL;
float offset_centre = (shape->getCenter().x - min_x) * ESCALA_FINAL;
lletres_.push_back({shape,
{.x = 0.0F, .y = 0.0F}, // Posición es calcularà después
ancho,
offset_centre});
ancho_total += ancho;
}
// Pas 2: Añadir espaiat entre lletres
ancho_total += ESPAI_ENTRE_LLETRES * (lletres_.size() - 1);
// Pas 3: Calcular posición inicial (centrat horitzontal)
constexpr auto PANTALLA_ANCHO = static_cast<float>(Defaults::Game::WIDTH);
constexpr auto PANTALLA_ALTO = static_cast<float>(Defaults::Game::HEIGHT);
float x_inicial = (PANTALLA_ANCHO - ancho_total) / 2.0F;
float y_centre = PANTALLA_ALTO / 2.0F;
// Pas 4: Assignar posicions a cada lletra
float x_actual = x_inicial;
for (auto& lletra : lletres_) {
// Posicionar el centro de la shape (shape_centre) en pantalla
// Usar offset_centre en lloc de ancho/2 perquè shape_centre
// pot no estar exactament al mig del bounding box
lletra.position.x = x_actual + lletra.offset_centre;
lletra.position.y = y_centre;
// Avançar para següent lletra
x_actual += lletra.ancho + ESPAI_ENTRE_LLETRES;
}
std::cout << "[LogoScene] " << lletres_.size()
<< " lletres carregades, ancho total: " << ancho_total << " px\n";
}
void LogoScene::canviar_estat(AnimationState nou_estat) {
estat_actual_ = nou_estat;
temps_estat_actual_ = 0.0F; // Reset time
// Inicialitzar state de explosión
if (nou_estat == AnimationState::EXPLOSION) {
lletra_explosio_index_ = 0;
temps_des_ultima_explosio_ = 0.0F;
// Generar ordre aleatori de explosions
ordre_explosio_.clear();
for (size_t i = 0; i < lletres_.size(); i++) {
ordre_explosio_.push_back(i);
}
std::random_device rd;
std::mt19937 g(rd());
std::shuffle(ordre_explosio_.begin(), ordre_explosio_.end(), g);
} else if (nou_estat == AnimationState::POST_EXPLOSION) {
Audio::get()->playMusic("title.ogg");
}
std::cout << "[LogoScene] Canvi a state: " << static_cast<int>(nou_estat)
<< "\n";
}
auto LogoScene::totes_lletres_completes() const -> bool {
// Cuando global_progress = 1.0, todas las lletres tenen letra_progress = 1.0
return temps_estat_actual_ >= DURACIO_ZOOM;
}
void LogoScene::actualitzar_explosions(float delta_time) {
temps_des_ultima_explosio_ += delta_time;
// Comprovar si es el moment de explode la següent lletra
if (temps_des_ultima_explosio_ >= DELAY_ENTRE_EXPLOSIONS) {
if (lletra_explosio_index_ < lletres_.size()) {
// Explotar lletra actual (en ordre aleatori)
size_t index_actual = ordre_explosio_[lletra_explosio_index_];
const auto& lletra = lletres_[index_actual];
debris_manager_->explode(
lletra.shape, // Forma a explode
lletra.position, // Posición
0.0F, // Angle (sin rotación)
ESCALA_FINAL, // Escala (lletres a scale final)
VELOCITAT_EXPLOSIO, // Velocidad base
1.0F, // Brightness màxim (per defecte)
{.x = 0.0F, .y = 0.0F} // Sin velocity (per defecte)
);
std::cout << "[LogoScene] Explota lletra " << lletra_explosio_index_ << "\n";
// Passar a la següent lletra
lletra_explosio_index_++;
temps_des_ultima_explosio_ = 0.0F;
} else {
// Todas las lletres han explotat, transición a POST_EXPLOSION
canviar_estat(AnimationState::POST_EXPLOSION);
}
}
}
void LogoScene::update(float delta_time) {
temps_estat_actual_ += delta_time;
switch (estat_actual_) {
case AnimationState::PRE_ANIMATION:
if (temps_estat_actual_ >= DURACIO_PRE) {
canviar_estat(AnimationState::ANIMATION);
}
break;
case AnimationState::ANIMATION: {
// Reproduir so per cada lletra cuando comença a aparèixer
float global_progress = std::min(temps_estat_actual_ / DURACIO_ZOOM, 1.0F);
for (size_t i = 0; i < lletres_.size() && i < so_reproduit_.size(); i++) {
if (!so_reproduit_[i]) {
float letra_progress = calcular_progress_letra(
i,
lletres_.size(),
global_progress,
THRESHOLD_LETRA);
// Reproduir so cuando la lletra comença a aparèixer (progress > 0)
if (letra_progress > 0.0F) {
Audio::get()->playSound(Defaults::Sound::LOGO, Audio::Group::GAME);
so_reproduit_[i] = true;
}
}
}
if (totes_lletres_completes()) {
canviar_estat(AnimationState::POST_ANIMATION);
}
break;
}
case AnimationState::POST_ANIMATION:
if (temps_estat_actual_ >= DURACIO_POST_ANIMATION) {
canviar_estat(AnimationState::EXPLOSION);
}
break;
case AnimationState::EXPLOSION:
actualitzar_explosions(delta_time);
break;
case AnimationState::POST_EXPLOSION:
if (temps_estat_actual_ >= DURACIO_POST_EXPLOSION) {
// Transición a pantalla de título
context_.setNextScene(SceneType::TITLE);
}
break;
}
// Verificar botones de skip (SHOOT P1/P2)
if (checkSkipButtonPressed()) {
context_.setNextScene(SceneType::TITLE, Option::JUMP_TO_TITLE_MAIN);
}
// Actualitzar animaciones de debris
debris_manager_->update(delta_time);
}
void LogoScene::draw() {
// Director ha hecho el clear; aquí solo pintamos lo de la escena.
// PRE_ANIMATION: Solo pantalla negra (no se pinta nada).
if (estat_actual_ == AnimationState::PRE_ANIMATION) {
return;
}
// ANIMATION o POST_ANIMATION: Dibuixar lletres con animación
if (estat_actual_ == AnimationState::ANIMATION ||
estat_actual_ == AnimationState::POST_ANIMATION) {
float global_progress =
(estat_actual_ == AnimationState::ANIMATION)
? std::min(temps_estat_actual_ / DURACIO_ZOOM, 1.0F)
: 1.0F; // POST: mantenir al 100%
const Vec2 ORIGEN_ZOOM = {.x = ORIGEN_ZOOM_X, .y = ORIGEN_ZOOM_Y};
for (size_t i = 0; i < lletres_.size(); i++) {
const auto& lletra = lletres_[i];
float letra_progress = calcular_progress_letra(
i,
lletres_.size(),
global_progress,
THRESHOLD_LETRA);
if (letra_progress <= 0.0F) {
continue;
}
Vec2 pos_actual;
pos_actual.x =
ORIGEN_ZOOM.x + ((lletra.position.x - ORIGEN_ZOOM.x) * letra_progress);
pos_actual.y =
ORIGEN_ZOOM.y + ((lletra.position.y - ORIGEN_ZOOM.y) * letra_progress);
float t = letra_progress;
float ease_factor = 1.0F - ((1.0F - t) * (1.0F - t));
float current_scale =
ESCALA_INICIAL + ((ESCALA_FINAL - ESCALA_INICIAL) * ease_factor);
Rendering::render_shape(
sdl_.getRenderer(),
lletra.shape,
pos_actual,
0.0F,
current_scale,
1.0F);
}
}
// EXPLOSION: Dibuixar solo lletres que aún no han explotat
if (estat_actual_ == AnimationState::EXPLOSION) {
// Crear conjunt de lletres ya explotades
std::set<size_t> explotades;
for (size_t i = 0; i < lletra_explosio_index_; i++) {
explotades.insert(ordre_explosio_[i]);
}
// Dibuixar solo lletres que NO han explotat
for (size_t i = 0; i < lletres_.size(); i++) {
if (!explotades.contains(i)) {
const auto& lletra = lletres_[i];
Rendering::render_shape(
sdl_.getRenderer(),
lletra.shape,
lletra.position,
0.0F,
ESCALA_FINAL,
1.0F);
}
}
}
// POST_EXPLOSION: No draw lletres, solo debris (a baix)
// Siempre draw debris (si n'hay de active)
debris_manager_->draw();
}
auto LogoScene::checkSkipButtonPressed() -> bool {
return Input::get()->checkAnyPlayerAction(ARCADE_BUTTONS);
}