Files
projecte_2026/source/core/system/director.cpp

412 lines
14 KiB
C++

#include "core/system/director.hpp"
#include <SDL3/SDL.h>
#include <sys/stat.h> // Para mkdir, stat, S_IRWXU
#include <unistd.h> // Para getuid
#include <cerrno> // Para errno, EEXIST, EACCES, ENAMETOO...
#include <cstdio> // Para printf, perror
#include <cstdlib> // Para exit, EXIT_FAILURE, srand
#include <iostream> // Para basic_ostream, operator<<, cout
#include <memory> // Para make_unique, unique_ptr
#include <string> // Para operator+, allocator, char_traits
#include "core/audio/audio.hpp" // Para Audio
#include "core/input/input.hpp" // Para Input, InputAction
#include "core/locale/locale.hpp" // Para Locale
#include "core/rendering/render_info.hpp" // Para RenderInfo
#include "core/rendering/screen.hpp" // Para Screen
#include "core/resources/resource_cache.hpp" // Para Resource
#include "core/resources/resource_helper.hpp" // Para ResourceHelper
#include "core/resources/resource_list.hpp" // Para Asset, AssetType
#include "core/resources/resource_loader.hpp" // Para ResourceLoader
#include "core/system/event_buffer.hpp" // Para EventBuffer
#include "game/gameplay/cheevos.hpp" // Para Cheevos
#include "game/gameplay/zone_manager.hpp" // Para ZoneManager
#include "game/options.hpp" // Para Options, options, OptionsVideo
#include "game/scene_manager.hpp" // Para SceneManager
#include "game/scenes/game.hpp" // Para Game, GameMode
#include "game/scenes/logo.hpp" // Para Logo
#include "game/scenes/title.hpp" // Para Title
#include "game/ui/console.hpp" // Para Console
#include "game/ui/notifier.hpp" // Para Notifier
#include "utils/defines.hpp" // Para WINDOW_CAPTION
#ifdef _DEBUG
#include "core/system/debug.hpp" // Para Debug
#include "game/editor/map_editor.hpp" // Para MapEditor
#endif
#ifndef _WIN32
#include <pwd.h>
#endif
// Constructor
Director::Director() {
std::cout << "Game start" << '\n';
// Obtiene la ruta del ejecutable
std::string base = SDL_GetBasePath();
if (!base.empty() && base.back() == '/') {
base.pop_back();
}
executable_path_ = base;
// Crea la carpeta del sistema donde guardar datos
createSystemFolder("jailgames");
createSystemFolder("jailgames/projecte_2026");
// Crea el subdirectorio shaders/ dentro de system_folder_ sin modificar system_folder_
{
std::string shaders_dir = system_folder_ + "/shaders";
struct stat st = {.st_dev = 0};
if (stat(shaders_dir.c_str(), &st) == -1) {
errno = 0;
#ifdef _WIN32
mkdir(shaders_dir.c_str());
#else
mkdir(shaders_dir.c_str(), S_IRWXU);
#endif
}
}
// Determinar el prefijo de ruta según la plataforma
#ifdef MACOS_BUNDLE
const std::string PREFIX = "/../Resources";
#else
const std::string PREFIX;
#endif
// Preparar ruta al pack (en macOS bundle está en Contents/Resources/)
std::string pack_path = executable_path_ + PREFIX + "/resources.pack";
#ifdef RELEASE_BUILD
// ============================================================
// RELEASE BUILD: Pack-first architecture
// ============================================================
std::cout << "\n** RELEASE MODE: Pack-first initialization\n";
// 1. Initialize resource pack system (required, no fallback)
std::cout << "Initializing resource pack: " << pack_path << '\n';
if (!Resource::Helper::initializeResourceSystem(pack_path, false)) {
std::cerr << "ERROR: Failed to load resources.pack (required in release builds)\n";
exit(EXIT_FAILURE);
}
// 2. Validate pack integrity
std::cout << "Validating pack integrity..." << '\n';
if (!Resource::Loader::get().validatePack()) {
std::cerr << "ERROR: Pack validation failed\n";
exit(EXIT_FAILURE);
}
// 3. Load assets.yaml from pack
std::cout << "Loading assets configuration from pack..." << '\n';
std::string assets_config = Resource::Loader::get().loadAssetsConfig();
if (assets_config.empty()) {
std::cerr << "ERROR: Failed to load assets.yaml from pack\n";
exit(EXIT_FAILURE);
}
// 4. Initialize Asset system with config from pack
// NOTE: In release, don't use executable_path or PREFIX - paths in pack are relative
// Pass empty string to avoid issues when running from different directories
Resource::List::init(""); // Empty executable_path in release
Resource::List::get()->loadFromString(assets_config, "", system_folder_); // Empty PREFIX for pack
std::cout << "Asset system initialized from pack\n";
#else
// ============================================================
// DEVELOPMENT BUILD: Filesystem-first architecture
// ============================================================
std::cout << "\n** DEVELOPMENT MODE: Filesystem-first initialization\n";
// 1. Initialize Asset system from filesystem
Resource::List::init(executable_path_);
// 2. Load asset configuration from disk
// Note: Asset verification happens during Resource::Cache::load()
setFileList();
// 3. Initialize resource pack system (optional, with fallback)
std::cout << "Initializing resource pack (development mode): " << pack_path << '\n';
Resource::Helper::initializeResourceSystem(pack_path, true);
#endif
// Configura la ruta y carga las opciones desde un fichero
Options::setConfigFile(Resource::List::get()->get("config.yaml"));
Options::loadFromFile();
// Configura la ruta y carga los presets de PostFX
Options::setPostFXFile(Resource::List::get()->get("postfx.yaml"));
Options::loadPostFXFromFile();
// Configura la ruta y carga los presets del shader CrtPi
Options::setCrtPiFile(Resource::List::get()->get("crtpi.yaml"));
Options::loadCrtPiFromFile();
// En mode quiosc, forçar pantalla completa independentment de la configuració
if (Options::kiosk.enabled) {
Options::video.fullscreen = true;
}
// Inicializa JailAudio
Audio::init();
// Crea los objetos
Screen::init();
#ifdef _DEBUG
// En debug inicializamos Debug antes de Cache para leer el flag lazy_loading
Debug::init();
Debug::get()->setDebugFile(Resource::List::get()->get("debug.yaml"));
Debug::get()->loadFromFile();
#endif
// ZoneManager debe inicializarse antes que Resource::Cache: el cache carga
// las rooms en eager loading, y RoomFormat necesita consultar las zonas para
// resolver tileSetFile/music. ZoneManager carga su yaml directamente del
// filesystem (vía Resource::Helper::loadFile) así que no depende del cache.
ZoneManager::init();
// Initialize resources (works for both release and development)
#ifdef _DEBUG
Resource::Cache::init(Debug::get()->getLazyLoading()
? Resource::Cache::LoadingMode::LAZY
: Resource::Cache::LoadingMode::EAGER);
#else
Resource::Cache::init();
#endif
Notifier::init("", "8bithud");
RenderInfo::init();
#ifdef _DEBUG
if (Debug::get()->getRenderInfoEnabled()) { RenderInfo::get()->toggle(); }
#endif
Console::init("8bithud");
Screen::get()->setNotificationsEnabled(true);
// Special handling for gamecontrollerdb.txt - SDL needs filesystem path
#ifdef RELEASE_BUILD
// In release, construct the path manually (not from Asset which has empty executable_path)
std::string gamecontroller_db = executable_path_ + PREFIX + "/gamecontrollerdb.txt";
Input::init(gamecontroller_db);
#else
// In development, use Asset as normal
Input::init(Resource::List::get()->get("gamecontrollerdb.txt")); // Carga configuración de controles
#endif
// Aplica las teclas y botones del gamepad configurados desde Options
Input::get()->applyKeyboardBindingsFromOptions();
Input::get()->applyGamepadBindingsFromOptions();
#ifdef _DEBUG
SceneManager::current = Debug::get()->getInitialScene();
MapEditor::init();
#endif
std::cout << "\n"; // Fin de inicialización de sistemas
// Inicializa el sistema de localización (antes de Cheevos que usa textos traducidos)
#ifdef RELEASE_BUILD
{
// En release el locale está en el pack, no en el filesystem
std::string locale_key = Resource::List::get()->get(Options::language + ".yaml");
auto locale_bytes = Resource::Helper::loadFile(locale_key);
std::string locale_content(locale_bytes.begin(), locale_bytes.end());
Locale::initFromContent(locale_content);
}
#else
Locale::init(Resource::List::get()->get(Options::language + ".yaml"));
#endif
// Special handling for cheevos.bin - also needs filesystem path
#ifdef RELEASE_BUILD
std::string cheevos_path = system_folder_ + "/cheevos.bin";
Cheevos::init(cheevos_path);
#else
Cheevos::init(Resource::List::get()->get("cheevos.bin"));
#endif
}
Director::~Director() {
// Destruye las escenas primero (pueden usar singletones como Audio en sus destructores)
logo_.reset();
title_.reset();
game_.reset();
// Guarda las opciones a un fichero
Options::saveToFile();
// Destruye los singletones
Cheevos::destroy();
Locale::destroy();
#ifdef _DEBUG
MapEditor::destroy();
Debug::destroy();
#endif
Input::destroy();
Console::destroy();
RenderInfo::destroy();
Notifier::destroy();
Resource::Cache::destroy();
ZoneManager::destroy();
Resource::Helper::shutdownResourceSystem(); // Shutdown resource pack system
Audio::destroy();
Screen::destroy();
Resource::List::destroy();
SDL_Quit();
std::cout << "\nBye!" << '\n';
}
// Crea la carpeta del sistema donde guardar datos
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;
{
// Intenta crear ".config", per 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 CREATING CONFIG BASE FOLDER.");
exit(EXIT_FAILURE);
}
}
#endif
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("the parent directory does not allow write");
exit(EXIT_FAILURE);
case EEXIST:
printf("pathname already exists");
exit(EXIT_FAILURE);
case ENAMETOOLONG:
printf("pathname is too long");
exit(EXIT_FAILURE);
default:
perror("mkdir");
exit(EXIT_FAILURE);
}
}
}
}
// Carga la configuración de assets desde assets.yaml
void Director::setFileList() {
// Determinar el prefijo de ruta según la plataforma
#ifdef MACOS_BUNDLE
const std::string PREFIX = "/../Resources";
#else
const std::string PREFIX;
#endif
// Construir ruta al archivo de configuración de assets
std::string config_path = executable_path_ + PREFIX + "/config/assets.yaml";
// Cargar todos los assets desde el archivo de configuración
// La verificación de existencia de archivos se realiza durante Resource::Cache::load()
Resource::List::get()->loadFromFile(config_path, PREFIX, system_folder_);
}
// Destruye la escena activa
void Director::destroyCurrentScene() {
logo_.reset();
title_.reset();
game_.reset();
active_scene_ = SceneManager::Scene::QUIT;
}
// Crea la escena indicada
void Director::createScene(SceneManager::Scene scene) {
destroyCurrentScene();
switch (scene) {
case SceneManager::Scene::LOGO:
logo_ = std::make_unique<Logo>();
break;
case SceneManager::Scene::TITLE:
title_ = std::make_unique<Title>();
break;
case SceneManager::Scene::GAME:
Audio::get()->stopMusic();
game_ = std::make_unique<Game>(Game::Mode::GAME);
break;
default:
break;
}
active_scene_ = scene;
}
// Una iteración del bucle principal (callback model)
auto Director::iterate() -> SDL_AppResult {
// Resolver RESTART_CURRENT antes de comprobar escena
if (SceneManager::current == SceneManager::Scene::RESTART_CURRENT) {
SceneManager::current = SceneManager::scene_before_restart;
}
// Salir si se ha pedido QUIT
if (SceneManager::current == SceneManager::Scene::QUIT) {
EventBuffer::events.clear();
return SDL_APP_SUCCESS;
}
// Si la escena solicitada no coincide con la activa, crear la nueva
if (SceneManager::current != active_scene_) {
createScene(SceneManager::current);
}
// Ejecutar un frame de la escena activa
switch (active_scene_) {
case SceneManager::Scene::LOGO:
logo_->update();
logo_->render();
break;
case SceneManager::Scene::TITLE:
title_->update();
title_->render();
break;
case SceneManager::Scene::GAME:
game_->update();
game_->render();
break;
default:
break;
}
// Limpiar el buffer de eventos tras cada frame
EventBuffer::events.clear();
return SDL_APP_CONTINUE;
}