#include "director.hpp" #include #include #include #include #include #include #include #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 #include #endif // Using declarations per simplificar el codi using SceneManager::SceneContext; using SceneType = SceneContext::SceneType; // Constructor Director::Director(std::vector 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 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 struct stat st = {.st_dev = 0}; 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(std::round( Defaults::Window::WIDTH * Options::window.zoom_factor)); int initial_height = static_cast(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 = 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 { switch (type) { case SceneType::LOGO: return std::make_unique(sdl, context); case SceneType::TITLE: return std::make_unique(sdl, context); case SceneType::GAME: return std::make_unique(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(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(); } }