Files
orni-attack/source/core/system/director.cpp
T

366 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "director.hpp"
#include <SDL3/SDL.h>
#include <sys/stat.h>
#include <algorithm>
#include <cerrno>
#include <cstdlib>
#include <iostream>
#include <memory>
#include "core/audio/audio.hpp"
#include "core/audio/audio_adapter.hpp"
#include "core/defaults/audio.hpp"
#include "core/defaults/window.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/system/notifier.hpp"
#include "core/utils/path_utils.hpp"
#include "debug_overlay.hpp"
#include "game/scenes/game_scene.hpp"
#include "game/scenes/logo_scene.hpp"
#include "game/scenes/title_scene.hpp"
#include "game/scenes/title_scene_3d.hpp"
#include "global_events.hpp"
#include "project.h"
#include "scene.hpp"
#include "scene_context.hpp"
#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,
Config::EngineConfig& cfg,
Config::ConfigPersistence persistence)
: cfg_(&cfg),
persistence_(std::move(persistence)) {
std::cout << "Orni Attack - Inici\n";
// Inicialitzar opciones con valors per defecte
persistence_.init_defaults();
// 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
persistence_.set_path(system_folder_ + "/config.yaml");
// Carregar o crear configuración
persistence_.load();
// Inicialitzar sistema de input
Input::init("data/gamecontrollerdb.txt");
// Aplicar configuración de controls dels jugadors
Input::get()->applyPlayer1Bindings(cfg_->player1);
Input::get()->applyPlayer2Bindings(cfg_->player2);
if (cfg_->console) {
std::cout << "Configuración carregada\n";
std::cout << " Finestra: " << cfg_->window.width << "×"
<< cfg_->window.height << '\n';
std::cout << " Input: " << Input::get()->getNumGamepads()
<< " gamepad(s) detectat(s)\n";
}
std::cout << '\n';
}
Director::~Director() {
// Guardar opciones
persistence_.save();
// 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") {
cfg_->console = true;
std::cout << "Mode consola activat\n";
} else if (argument == "--reset-config") {
persistence_.init_defaults();
persistence_.save();
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 (cfg_->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 * cfg_->window.zoom_factor));
int initial_height = static_cast<int>(std::round(
Defaults::Window::HEIGHT * cfg_->window.zoom_factor));
// Crear gestor SDL amb la engine_config + callback de persistència
// per a quan toggleVSync (F4) muti vsync. Mantenim sdl_manager agnòstic.
SDLManager sdl(initial_width, initial_height, cfg_->window.fullscreen, *cfg_, [this] { persistence_.save(); });
// 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 (!cfg_->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 (cfg_->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(), cfg_->rendering);
// Sistema de notificacions toast: singleton accessible des d'on calgui
// (F1-F5 a sdl_manager, ESC a global_events). El renderer ha de viure
// tant com el Notifier; el destruim explícitament abans de tornar.
System::Notifier::init(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;
System::Notifier::destroy();
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: {
// Env var ORNI_TITLE_3D=1 redirigeix la TITLE clàssica cap a la
// variant 3D real en proves; en qualsevol altre cas, la 2D.
const char* env = std::getenv("ORNI_TITLE_3D");
if (env != nullptr && env[0] == '1' && env[1] == '\0') {
return std::make_unique<TitleScene3D>(sdl, context);
}
return std::make_unique<TitleScene>(sdl, context);
}
case SceneType::TITLE_3D:
return std::make_unique<TitleScene3D>(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);
if (auto* notifier = System::Notifier::get(); notifier != nullptr) {
notifier->update(delta_time);
}
Audio::update();
// Si la swapchain no está disponible (ventana minimizada, etc.),
// saltarse draw+present ese frame: dibujar dejaría vértices
// colgando en el batch interno sin nadie que los presente.
if (!sdl.clear(0, 0, 0)) {
continue;
}
sdl.updateRenderingContext();
scene.draw();
debug_overlay.draw(); // sempre per damunt de l'escena
if (const auto* notifier = System::Notifier::get(); notifier != nullptr) {
notifier->draw(); // toast: per damunt de tot
}
sdl.present();
}
}