#include "core/system/director.hpp" #include #include // Para mkdir, stat, S_IRWXU #include // Para getuid #include // Para errno, EEXIST, EACCES, ENAMETOO... #include // Para printf, perror #include // Para exit, EXIT_FAILURE, srand #include // Para basic_ostream, operator<<, cout #include // Para make_unique, unique_ptr #include // Para operator+, allocator, char_traits #include "core/audio/audio.hpp" // Para Audio #include "core/input/input.hpp" // Para Input, InputAction #include "core/rendering/screen.hpp" // Para Screen #include "core/resources/asset.hpp" // Para Asset, AssetType #include "core/resources/resource.hpp" // Para Resource #include "core/resources/resource_helper.hpp" // Para ResourceHelper #include "core/resources/resource_loader.hpp" // Para ResourceLoader #include "core/system/debug.hpp" // Para Debug #include "game/gameplay/cheevos.hpp" // Para Cheevos #include "game/options.hpp" // Para Options, options, OptionsVideo #include "game/scene_manager.hpp" // Para SceneManager #include "game/scenes/credits.hpp" // Para Credits #include "game/scenes/ending.hpp" // Para Ending #include "game/scenes/ending2.hpp" // Para Ending2 #include "game/scenes/game.hpp" // Para Game, GameMode #include "game/scenes/game_over.hpp" // Para GameOver #include "game/scenes/loading_screen.hpp" // Para LoadingScreen #include "game/scenes/logo.hpp" // Para Logo #include "game/scenes/title.hpp" // Para Title #include "game/ui/notifier.hpp" // Para Notifier #include "utils/defines.hpp" // Para WINDOW_CAPTION #ifndef _WIN32 #include #endif // Constructor Director::Director(std::vector const& args) { std::cout << "Game start" << '\n'; // Crea e inicializa las opciones del programa Options::init(); // Comprueba los parametros del programa executable_path_ = getPath(checkProgramArguments(args)); // Crea la carpeta del sistema donde guardar datos createSystemFolder("jailgames"); createSystemFolder("jailgames/jaildoctors_dilemma"); // 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 (!jdd::ResourceHelper::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 (!jdd::ResourceLoader::get().validatePack()) { std::cerr << "ERROR: Pack validation failed\n"; exit(EXIT_FAILURE); } // 3. Load assets.txt from pack std::cout << "Loading assets configuration from pack..." << '\n'; std::string assets_config = jdd::ResourceLoader::get().loadAssetsConfig(); if (assets_config.empty()) { std::cerr << "ERROR: Failed to load assets.txt 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 Asset::init(""); // Empty executable_path in release Asset::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 Asset::init(executable_path_); // 2. Load and verify assets from disk if (!setFileList()) { exit(EXIT_FAILURE); } // 3. Initialize resource pack system (optional, with fallback) std::cout << "Initializing resource pack (development mode): " << pack_path << '\n'; jdd::ResourceHelper::initializeResourceSystem(pack_path, true); #endif // Carga las opciones desde un fichero Options::loadFromFile(Asset::get()->get("config.txt")); // Inicializa JailAudio Audio::init(); // Crea los objetos Screen::init(); // Initialize resources (works for both release and development) Resource::init(); Notifier::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(Asset::get()->get("gamecontrollerdb.txt")); #endif initInput(); Debug::init(); // 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(Asset::get()->get("cheevos.bin")); #endif } Director::~Director() { // Guarda las opciones a un fichero Options::saveToFile(Asset::get()->get("config.txt")); // Destruye los singletones Cheevos::destroy(); Debug::destroy(); Input::destroy(); Notifier::destroy(); Resource::destroy(); jdd::ResourceHelper::shutdownResourceSystem(); // Shutdown resource pack system Audio::destroy(); Screen::destroy(); Asset::destroy(); SDL_Quit(); std::cout << "\nBye!" << '\n'; } // Comprueba los parametros del programa auto Director::checkProgramArguments(std::vector const& args) -> std::string { // Iterar sobre los argumentos del programa (saltando args[0] que es el ejecutable) for (std::size_t i = 1; i < args.size(); ++i) { const std::string& argument = args[i]; if (argument == "--console") { Options::console = true; } else if (argument == "--infiniteLives") { Options::cheats.infinite_lives = Options::Cheat::State::ENABLED; } else if (argument == "--invincible") { Options::cheats.invincible = Options::Cheat::State::ENABLED; } else if (argument == "--jailEnabled") { Options::cheats.jail_is_open = Options::Cheat::State::ENABLED; } else if (argument == "--altSkin") { Options::cheats.alternate_skin = Options::Cheat::State::ENABLED; } } return args[0]; } // 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); } } } } // Inicia las variables necesarias para arrancar el programa void Director::initInput() { // Busca si hay un mando conectado Input::get()->discoverGameControllers(); // Teclado - Movimiento if (Options::keys == Options::ControlScheme::CURSOR) { Input::get()->bindKey(InputAction::JUMP, SDL_SCANCODE_UP); Input::get()->bindKey(InputAction::LEFT, SDL_SCANCODE_LEFT); Input::get()->bindKey(InputAction::RIGHT, SDL_SCANCODE_RIGHT); Input::get()->bindKey(InputAction::UP, SDL_SCANCODE_UP); Input::get()->bindKey(InputAction::DOWN, SDL_SCANCODE_DOWN); } else if (Options::keys == Options::ControlScheme::OPQA) { Input::get()->bindKey(InputAction::JUMP, SDL_SCANCODE_Q); Input::get()->bindKey(InputAction::LEFT, SDL_SCANCODE_O); Input::get()->bindKey(InputAction::RIGHT, SDL_SCANCODE_P); Input::get()->bindKey(InputAction::UP, SDL_SCANCODE_Q); Input::get()->bindKey(InputAction::DOWN, SDL_SCANCODE_A); } else if (Options::keys == Options::ControlScheme::WASD) { Input::get()->bindKey(InputAction::JUMP, SDL_SCANCODE_W); Input::get()->bindKey(InputAction::LEFT, SDL_SCANCODE_A); Input::get()->bindKey(InputAction::RIGHT, SDL_SCANCODE_D); Input::get()->bindKey(InputAction::UP, SDL_SCANCODE_W); Input::get()->bindKey(InputAction::DOWN, SDL_SCANCODE_S); } // Teclado - Otros Input::get()->bindKey(InputAction::ACCEPT, SDL_SCANCODE_RETURN); Input::get()->bindKey(InputAction::CANCEL, SDL_SCANCODE_ESCAPE); Input::get()->bindKey(InputAction::PAUSE, SDL_SCANCODE_H); Input::get()->bindKey(InputAction::EXIT, SDL_SCANCODE_ESCAPE); Input::get()->bindKey(InputAction::WINDOW_DEC_ZOOM, SDL_SCANCODE_F1); Input::get()->bindKey(InputAction::WINDOW_INC_ZOOM, SDL_SCANCODE_F2); Input::get()->bindKey(InputAction::TOGGLE_VIDEOMODE, SDL_SCANCODE_F3); Input::get()->bindKey(InputAction::TOGGLE_SHADERS, SDL_SCANCODE_F4); Input::get()->bindKey(InputAction::NEXT_PALETTE, SDL_SCANCODE_F5); Input::get()->bindKey(InputAction::PREVIOUS_PALETTE, SDL_SCANCODE_F6); Input::get()->bindKey(InputAction::TOGGLE_INTEGER_SCALE, SDL_SCANCODE_F7); Input::get()->bindKey(InputAction::SHOW_DEBUG_INFO, SDL_SCANCODE_F12); Input::get()->bindKey(InputAction::TOGGLE_MUSIC, SDL_SCANCODE_M); Input::get()->bindKey(InputAction::TOGGLE_BORDER, SDL_SCANCODE_B); // MANDO const int NUM_GAMEPADS = Input::get()->getNumControllers(); for (int i = 0; i < NUM_GAMEPADS; ++i) { // Movimiento Input::get()->bindGameControllerButton(i, InputAction::JUMP, SDL_GAMEPAD_BUTTON_SOUTH); Input::get()->bindGameControllerButton(i, InputAction::LEFT, SDL_GAMEPAD_BUTTON_DPAD_LEFT); Input::get()->bindGameControllerButton(i, InputAction::RIGHT, SDL_GAMEPAD_BUTTON_DPAD_RIGHT); // Otros Input::get()->bindGameControllerButton(i, InputAction::ACCEPT, SDL_GAMEPAD_BUTTON_SOUTH); Input::get()->bindGameControllerButton(i, InputAction::CANCEL, SDL_GAMEPAD_BUTTON_EAST); #ifdef GAME_CONSOLE Input::get()->bindGameControllerButton(i, InputAction::input_pause, SDL_GAMEPAD_BUTTON_BACK); Input::get()->bindGameControllerButton(i, InputAction::input_exit, SDL_GAMEPAD_BUTTON_START); #else Input::get()->bindGameControllerButton(i, InputAction::PAUSE, SDL_GAMEPAD_BUTTON_START); Input::get()->bindGameControllerButton(i, InputAction::EXIT, SDL_GAMEPAD_BUTTON_BACK); #endif Input::get()->bindGameControllerButton(i, InputAction::NEXT_PALETTE, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER); Input::get()->bindGameControllerButton(i, InputAction::TOGGLE_MUSIC, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER); Input::get()->bindGameControllerButton(i, InputAction::TOGGLE_BORDER, SDL_GAMEPAD_BUTTON_NORTH); } } // Crea el indice de ficheros auto Director::setFileList() -> bool { // 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.txt"; // Cargar todos los assets desde el archivo de configuración Asset::get()->loadFromFile(config_path, PREFIX, system_folder_); // Verificar que todos los assets requeridos existen return Asset::get()->check(); } // Ejecuta la seccion de juego con el logo void Director::runLogo() { auto logo = std::make_unique(); logo->run(); } // Ejecuta la seccion de juego de la pantalla de carga void Director::runLoadingScreen() { auto loading_screen = std::make_unique(); loading_screen->run(); } // Ejecuta la seccion de juego con el titulo y los menus void Director::runTitle() { auto title = std::make_unique(); title->run(); } // Ejecuta la seccion de los creditos del juego void Director::runCredits() { auto credits = std::make_unique<Credits>(); credits->run(); } // Ejecuta la seccion de la demo, donde se ven pantallas del juego void Director::runDemo() { auto game = std::make_unique<Game>(Game::Mode::DEMO); game->run(); } // Ejecuta la seccion del final del juego void Director::runEnding() { auto ending = std::make_unique<Ending>(); ending->run(); } // Ejecuta la seccion del final del juego void Director::runEnding2() { auto ending2 = std::make_unique<Ending2>(); ending2->run(); } // Ejecuta la seccion del final de la partida void Director::runGameOver() { auto game_over = std::make_unique<GameOver>(); game_over->run(); } // Ejecuta la seccion de juego donde se juega void Director::runGame() { Audio::get()->stopMusic(); auto game = std::make_unique<Game>(Game::Mode::GAME); game->run(); } auto Director::run() -> int { // Bucle principal while (SceneManager::current != SceneManager::Scene::QUIT) { switch (SceneManager::current) { case SceneManager::Scene::LOGO: runLogo(); break; case SceneManager::Scene::LOADING_SCREEN: runLoadingScreen(); break; case SceneManager::Scene::TITLE: runTitle(); break; case SceneManager::Scene::CREDITS: runCredits(); break; case SceneManager::Scene::DEMO: runDemo(); break; case SceneManager::Scene::GAME: runGame(); break; case SceneManager::Scene::GAME_OVER: runGameOver(); break; case SceneManager::Scene::ENDING: runEnding(); break; case SceneManager::Scene::ENDING2: runEnding2(); break; default: break; } } return 0; }