efbf2457a1
Primera tanda mecánica sobre el lint pendiente. Arregla la causa raíz, no
silencia diagnósticos. Detalle por categoría:
- Uninit members (cppcheck warnings) → inicializadores en declaración:
Bullet (esta_, owner_id_, grace_timer_), Enemy (drotacio_, rotacio_,
esta_, type_, tracking_timer_, ship_position_, tracking_strength_,
direction_change_timer_, timer_invulnerabilitat_), Ship (is_hit_,
invulnerable_timer_), Shape (escala_defecte_) y TitleShip (todos los
miembros del struct, que viven dentro de un std::array<,2>).
- returnByReference (cppcheck performance) → return const T&:
Shape::getName, ResourceLoader::getBasePath. De paso, Shape::get_nom
se renombra a getName y get_num_primitives a getNumPrimitives para
cumplir la convención camelBack del proyecto (lint del .clang-tidy).
- useInitializationList (cppcheck performance) →
Starfield::shape_estrella_ pasa a la lista de inicialización (reordenada
según la declaración para no disparar -Wreorder-ctor).
- noExplicitConstructor (cppcheck style) → explicit en ctores de 1 arg:
Bullet(Renderer*), Enemy(Renderer*), Ship(Renderer*,...) y VectorText(Renderer*).
- variableScope (cppcheck style) → en vector_text.cpp se elimina la
variable 'c' intermedia y se usa el literal '\\xA9' directamente en el
único punto donde se necesita.
- constParameterReference (cppcheck style) → drawScoreboardAnimated pasa
el VectorText por const ref (la API render/renderCentered es const).
- Warnings preexistentes del compilador (resueltos de paso):
- stage_config.hpp: stage_id <= 255 sobre uint8_t era siempre true; se
elimina la comparación redundante y se explica con comentario.
- director.cpp: 'struct stat st = {.st_dev = 0};' disparaba
-Wmissing-field-initializers; pasa a 'struct stat st{};' (zero-init
completo, robusto a las variantes específicas del SO).
- game_scene.cpp: stepDeathSequence devolvía un bool [[nodiscard]] que
el caller ignoraba; el valor era puramente interno. Cambiada la
firma a void.
- cppcheck: añadido --suppress=useStlAlgorithm. Las 26 sugerencias
'Consider using std::any_of/find_if/count_if' son cosméticas y no
aportan claridad sobre las raw loops actuales.
- .clang-tidy de source/core/audio/ eliminado: deshabilitaba todos los
checks en ese subdirectorio por dependencia de jail_audio.hpp, pero
impedía ejecutar 'make tidy' (clang-tidy aborta con "no checks
enabled" al primer archivo del directorio). El proyecto pasa a usar
el mismo patrón de CCAE: solo source/external/ y source/legacy/
quedan fuera del lint.
- lint-reports/ añadido a .gitignore. Carpeta donde 'make tidy' y
'make cppcheck' vuelcan su salida completa para inspección posterior.
Build limpio, cero warnings activos.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
337 lines
11 KiB
C++
337 lines
11 KiB
C++
#include "director.hpp"
|
||
|
||
#include <SDL3/SDL.h>
|
||
#include <sys/stat.h>
|
||
|
||
#include <algorithm>
|
||
#include <cerrno>
|
||
#include <cstdlib>
|
||
#include <iostream>
|
||
#include <memory>
|
||
|
||
#include "debug_overlay.hpp"
|
||
#include "scene.hpp"
|
||
#include "scene_context.hpp"
|
||
#include "global_events.hpp"
|
||
#include "core/audio/audio.hpp"
|
||
#include "core/audio/audio_adapter.hpp"
|
||
#include "core/defaults.hpp"
|
||
#include "core/input/input.hpp"
|
||
#include "core/input/mouse.hpp"
|
||
#include "core/rendering/sdl_manager.hpp"
|
||
#include "core/resources/resource_helper.hpp"
|
||
#include "core/resources/resource_loader.hpp"
|
||
#include "core/utils/path_utils.hpp"
|
||
#include "game/scenes/game_scene.hpp"
|
||
#include "game/scenes/logo_scene.hpp"
|
||
#include "game/scenes/title_scene.hpp"
|
||
#include "game/options.hpp"
|
||
#include "project.h"
|
||
|
||
#ifndef _WIN32
|
||
#include <pwd.h>
|
||
#include <unistd.h>
|
||
#endif
|
||
|
||
// Using declarations per simplificar el codi
|
||
using SceneManager::SceneContext;
|
||
using SceneType = SceneContext::SceneType;
|
||
|
||
// Constructor
|
||
Director::Director(std::vector<std::string> const& args) {
|
||
std::cout << "Orni Attack - Inici\n";
|
||
|
||
// Inicialitzar opciones con valors per defecte
|
||
Options::init();
|
||
|
||
// Comprovar arguments del programa
|
||
executable_path_ = checkProgramArguments(args);
|
||
|
||
// Inicialitzar sistema de rutes
|
||
Utils::initializePathSystem(args[0].c_str());
|
||
|
||
// Obtenir ruta base dels recursos
|
||
std::string resource_base = Utils::getResourceBasePath();
|
||
|
||
// Inicialitzar sistema de recursos
|
||
#ifdef RELEASE_BUILD
|
||
// Mode release: paquet obligatori, sin fallback
|
||
std::string pack_path = resource_base + "/resources.pack";
|
||
if (!Resource::Helper::initializeResourceSystem(pack_path, false)) {
|
||
std::cerr << "ERROR FATAL: No es pot load " << pack_path << "\n";
|
||
std::cerr << "El juego no pot continuar sin los recursos.\n";
|
||
std::exit(1);
|
||
}
|
||
|
||
// Validar integritat del paquet
|
||
if (!Resource::Loader::get().validatePack()) {
|
||
std::cerr << "ERROR FATAL: El paquet de recursos está corromput\n";
|
||
std::exit(1);
|
||
}
|
||
|
||
std::cout << "Sistema de recursos inicialitzat (mode release)\n";
|
||
#else
|
||
// Mode desenvolupament: intentar paquet con fallback a data/
|
||
std::string pack_path = resource_base + "/resources.pack";
|
||
Resource::Helper::initializeResourceSystem(pack_path, true);
|
||
|
||
if (Resource::Helper::isPackLoaded()) {
|
||
std::cout << "Sistema de recursos inicialitzat (mode dev con paquet)\n";
|
||
} else {
|
||
std::cout << "Sistema de recursos inicialitzat (mode dev, fallback a data/)\n";
|
||
}
|
||
|
||
// Establir ruta base per al fallback
|
||
Resource::Loader::get().setBasePath(resource_base);
|
||
#endif
|
||
|
||
// Crear carpetes del sistema
|
||
createSystemFolder("jailgames");
|
||
createSystemFolder(std::string("jailgames/") + Project::NAME);
|
||
|
||
// Establir ruta del file de configuración
|
||
Options::setConfigFile(system_folder_ + "/config.yaml");
|
||
|
||
// Carregar o crear configuración
|
||
Options::loadFromFile();
|
||
|
||
// Inicialitzar sistema de input
|
||
Input::init("data/gamecontrollerdb.txt");
|
||
|
||
// Aplicar configuración de controls dels jugadors
|
||
Input::get()->applyPlayer1BindingsFromOptions();
|
||
Input::get()->applyPlayer2BindingsFromOptions();
|
||
|
||
if (Options::console) {
|
||
std::cout << "Configuración carregada\n";
|
||
std::cout << " Finestra: " << Options::window.width << "×"
|
||
<< Options::window.height << '\n';
|
||
std::cout << " Física: rotation=" << Options::physics.rotation_speed
|
||
<< " rad/s\n";
|
||
std::cout << " Input: " << Input::get()->getNumGamepads()
|
||
<< " gamepad(s) detectat(s)\n";
|
||
}
|
||
|
||
std::cout << '\n';
|
||
}
|
||
|
||
Director::~Director() {
|
||
// Guardar opciones
|
||
Options::saveToFile();
|
||
|
||
// Cleanup input
|
||
Input::destroy();
|
||
|
||
// Cleanup audio
|
||
Audio::destroy();
|
||
|
||
// Cleanup SDL
|
||
SDL_Quit();
|
||
|
||
std::cout << "\nAdéu!\n";
|
||
}
|
||
|
||
// Comprovar arguments del programa
|
||
auto Director::checkProgramArguments(std::vector<std::string> const& args)
|
||
-> std::string {
|
||
for (std::size_t i = 1; i < args.size(); ++i) {
|
||
const std::string& argument = args[i];
|
||
|
||
if (argument == "--console") {
|
||
Options::console = true;
|
||
std::cout << "Mode consola activat\n";
|
||
} else if (argument == "--reset-config") {
|
||
Options::init();
|
||
Options::saveToFile();
|
||
std::cout << "Configuración restablida als valors per defecte\n";
|
||
}
|
||
}
|
||
|
||
return args[0]; // Retornar ruta de l'executable
|
||
}
|
||
|
||
// Crear carpeta del sistema (específic per plataforma)
|
||
void Director::createSystemFolder(const std::string& folder) {
|
||
#ifdef _WIN32
|
||
system_folder_ = std::string(getenv("APPDATA")) + "/" + folder;
|
||
#elif __APPLE__
|
||
struct passwd* pw = getpwuid(getuid());
|
||
const char* homedir = pw->pw_dir;
|
||
system_folder_ =
|
||
std::string(homedir) + "/Library/Application Support/" + folder;
|
||
#elif __linux__
|
||
struct passwd* pw = getpwuid(getuid());
|
||
const char* homedir = pw->pw_dir;
|
||
system_folder_ = std::string(homedir) + "/.config/" + folder;
|
||
|
||
// CRÍTIC: Crear ~/.config si no existeix
|
||
{
|
||
std::string config_base_folder = std::string(homedir) + "/.config";
|
||
int ret = mkdir(config_base_folder.c_str(), S_IRWXU);
|
||
if (ret == -1 && errno != EEXIST) {
|
||
printf("ERROR: No es pot crear la carpeta ~/.config\n");
|
||
exit(EXIT_FAILURE);
|
||
}
|
||
}
|
||
#endif
|
||
|
||
// Comprovar si la carpeta existeix. Zero-init de toda la struct stat
|
||
// para evitar -Wmissing-field-initializers (struct con muchos campos
|
||
// específicos del SO que no queremos enumerar manualmente).
|
||
struct stat st{};
|
||
if (stat(system_folder_.c_str(), &st) == -1) {
|
||
errno = 0;
|
||
|
||
#ifdef _WIN32
|
||
int ret = mkdir(system_folder_.c_str());
|
||
#else
|
||
int ret = mkdir(system_folder_.c_str(), S_IRWXU);
|
||
#endif
|
||
|
||
if (ret == -1) {
|
||
switch (errno) {
|
||
case EACCES:
|
||
printf("ERROR: Permisos denegats creant %s\n", system_folder_.c_str());
|
||
exit(EXIT_FAILURE);
|
||
|
||
case EEXIST:
|
||
// La carpeta ya existeix (race condition), continuar
|
||
break;
|
||
|
||
case ENAMETOOLONG:
|
||
printf("ERROR: Ruta massa llarga: %s\n", system_folder_.c_str());
|
||
exit(EXIT_FAILURE);
|
||
|
||
default:
|
||
perror("mkdir");
|
||
exit(EXIT_FAILURE);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (Options::console) {
|
||
std::cout << "Carpeta del sistema: " << system_folder_ << '\n';
|
||
}
|
||
}
|
||
|
||
// Bucle principal del juego
|
||
auto Director::run() -> int {
|
||
// Calculate initial size from saved zoom_factor
|
||
int initial_width = static_cast<int>(std::round(
|
||
Defaults::Window::WIDTH * Options::window.zoom_factor));
|
||
int initial_height = static_cast<int>(std::round(
|
||
Defaults::Window::HEIGHT * Options::window.zoom_factor));
|
||
|
||
// Crear gestor SDL con configuración de Options
|
||
SDLManager sdl(initial_width, initial_height, Options::window.fullscreen);
|
||
|
||
// CRÍTIC: Forçar ocultació del cursor DESPRÉS de toda la inicialización SDL
|
||
// Això evita que SDL mostre el cursor automàticament durante la creació de la finestra
|
||
if (!Options::window.fullscreen) {
|
||
Mouse::forceHide();
|
||
}
|
||
|
||
// Inicializar sistema de audio (config inyectada desde Defaults)
|
||
const Audio::Config AUDIO_CONFIG{
|
||
.enabled = Defaults::Audio::ENABLED,
|
||
.volume = Defaults::Audio::VOLUME,
|
||
.music_enabled = Defaults::Audio::MUSIC_ENABLED,
|
||
.music_volume = Defaults::Audio::MUSIC_VOLUME,
|
||
.sound_enabled = Defaults::Audio::SOUND_ENABLED,
|
||
.sound_volume = Defaults::Audio::SOUND_VOLUME,
|
||
};
|
||
Audio::init(AUDIO_CONFIG);
|
||
Audio::get()->applySettings(AUDIO_CONFIG); // Aplicar volúmenes iniciales al motor
|
||
|
||
// Precachear música para evitar lag al empezar
|
||
AudioResource::getMusic("title.ogg");
|
||
AudioResource::getMusic("game.ogg");
|
||
if (Options::console) {
|
||
std::cout << "Música precacheada\n";
|
||
}
|
||
|
||
// Crear context de escenes
|
||
SceneContext context;
|
||
#ifdef _DEBUG
|
||
context.setNextScene(SceneType::TITLE);
|
||
#else
|
||
context.setNextScene(SceneType::LOGO);
|
||
#endif
|
||
|
||
// Overlay de debug (FPS + VSync). Vive en el Director porque es global
|
||
// a todas las escenas. Toggle con F11 (visible por defecto en _DEBUG).
|
||
System::DebugOverlay debug_overlay(sdl.getRenderer());
|
||
|
||
// Bucle principal: construir escena → frame loop → destruir → siguiente.
|
||
while (context.nextScene() != SceneType::EXIT) {
|
||
SceneManager::actual = context.nextScene();
|
||
std::unique_ptr<Scene> scene = buildScene(context.nextScene(), sdl, context);
|
||
if (!scene) {
|
||
break;
|
||
}
|
||
runFrameLoop(*scene, sdl, context, debug_overlay);
|
||
}
|
||
|
||
SceneManager::actual = SceneType::EXIT;
|
||
return 0;
|
||
}
|
||
|
||
auto Director::buildScene(SceneType type, SDLManager& sdl, SceneContext& context)
|
||
-> std::unique_ptr<Scene> {
|
||
switch (type) {
|
||
case SceneType::LOGO:
|
||
return std::make_unique<LogoScene>(sdl, context);
|
||
case SceneType::TITLE:
|
||
return std::make_unique<TitleScene>(sdl, context);
|
||
case SceneType::GAME:
|
||
return std::make_unique<GameScene>(sdl, context);
|
||
case SceneType::EXIT:
|
||
default:
|
||
return nullptr;
|
||
}
|
||
}
|
||
|
||
void Director::runFrameLoop(Scene& scene, SDLManager& sdl, SceneContext& context,
|
||
System::DebugOverlay& debug_overlay) {
|
||
SDL_Event event;
|
||
Uint64 last_time = SDL_GetTicks();
|
||
|
||
while (!scene.isFinished()) {
|
||
// Delta time real, capeado a 50ms para evitar grandes saltos.
|
||
const Uint64 NOW = SDL_GetTicks();
|
||
float delta_time = static_cast<float>(NOW - last_time) / 1000.0F;
|
||
last_time = NOW;
|
||
delta_time = std::min(delta_time, 0.05F);
|
||
|
||
Mouse::updateCursorVisibility();
|
||
Input::get()->update();
|
||
|
||
// Event loop: primero ventana, después globales, después F11
|
||
// (toggle del overlay), después escena.
|
||
while (SDL_PollEvent(&event)) {
|
||
if (sdl.handleWindowEvent(event)) {
|
||
continue;
|
||
}
|
||
if (GlobalEvents::handle(event, sdl, context)) {
|
||
continue;
|
||
}
|
||
if (event.type == SDL_EVENT_KEY_DOWN
|
||
&& event.key.scancode == SDL_SCANCODE_F11) {
|
||
debug_overlay.toggle();
|
||
continue;
|
||
}
|
||
scene.handleEvent(event);
|
||
}
|
||
|
||
scene.update(delta_time);
|
||
debug_overlay.update(delta_time);
|
||
Audio::update();
|
||
|
||
sdl.clear(0, 0, 0);
|
||
sdl.updateRenderingContext();
|
||
scene.draw();
|
||
debug_overlay.draw(); // siempre on top de la escena
|
||
sdl.present();
|
||
}
|
||
}
|