11eec8f222
El Reiniciar fins ara recarregava recursos però no rellegia el preset ni recreava la finestra (idioma/dificultat/preset es quedaven pendents fins al pròxim arrencada manual). Ara Director rep argv al constructor i, quan Section::name passa a RESET, fa execv del propi binari (_execv a Windows). El procés es reemplaça → init complet amb tots els canvis aplicats. Refactor: extret shutdownSubsystems() de close() i compartit amb relaunch(). Si execv falla els subsistemes ja s'han destruït, no podem tornar al bucle: exit amb error. A Emscripten s'amaga l'opció Reiniciar al service menu (execv no existeix; el cheat code per teclat encara cau al reset clàssic com fallback).
574 lines
23 KiB
C++
574 lines
23 KiB
C++
// IWYU pragma: no_include <bits/chrono.h>
|
|
#include "core/system/director.hpp"
|
|
|
|
#include <SDL3/SDL.h> // Para SDL_SetLogPriority, SDL_LogCategory, SDL_LogPriority, SDL_Quit
|
|
|
|
#include <cerrno> // Para errno
|
|
#include <cstdlib> // Para srand, exit, rand, EXIT_FAILURE
|
|
#include <cstring> // Para std::strerror
|
|
#include <ctime> // Para time
|
|
#include <fstream> // Para ifstream, ofstream
|
|
#include <iostream> // Para basic_ostream, operator<<, cerr
|
|
#include <memory> // Para make_unique, unique_ptr
|
|
#include <stdexcept> // Para runtime_error
|
|
#include <string> // Para allocator, basic_string, char_traits, operator+, string, operator==
|
|
|
|
#ifdef _WIN32
|
|
#include <process.h> // Per _execv
|
|
#else
|
|
#include <unistd.h> // Per execv
|
|
#endif
|
|
|
|
#include "core/audio/audio.hpp" // Para Audio
|
|
#include "core/input/input.hpp" // Para Input
|
|
#include "core/locale/lang.hpp" // Para setLanguage
|
|
#include "core/rendering/screen.hpp" // Para Screen
|
|
#include "core/resources/asset.hpp" // Para Asset
|
|
#include "core/resources/resource.hpp" // Para Resource
|
|
#include "core/resources/resource_helper.hpp" // Para initializeResourceSystem
|
|
#include "core/system/global_events.hpp" // Para GlobalEvents::handle
|
|
#include "core/system/section.hpp" // Para Name, Options, name, options, AttractMode, attract_mode
|
|
#include "core/system/shutdown.hpp" // Para resultToString, shutdownSystem, ShutdownResult
|
|
#include "core/system/system_utils.hpp" // Para createApplicationFolder, resultToString, Result
|
|
#include "external/fkyaml_node.hpp" // Para fkyaml::node
|
|
#include "game/entities/player.hpp" // Para Player
|
|
#include "game/gameplay/manage_hiscore_table.hpp" // Para ManageHiScoreTable
|
|
#include "game/options.hpp" // Para Settings, loadFromFile, saveToFile, settings, setConfigFile, setControllersFile
|
|
#include "game/scenes/credits.hpp" // Para Credits
|
|
#include "game/scenes/game.hpp" // Para Game
|
|
#include "game/scenes/hiscore_table.hpp" // Para HiScoreTable
|
|
#include "game/scenes/instructions.hpp" // Para Instructions
|
|
#include "game/scenes/intro.hpp" // Para Intro
|
|
#include "game/scenes/logo.hpp" // Para Logo
|
|
#include "game/scenes/preload.hpp" // Para Preload
|
|
#include "game/scenes/title.hpp" // Para Title
|
|
#include "game/ui/notifier.hpp" // Para Notifier
|
|
#include "game/ui/service_menu.hpp" // Para ServiceMenu
|
|
#include "utils/param.hpp" // Para loadParamsFromFile
|
|
|
|
namespace {
|
|
// Llig un camp opcional d'un YAML cap a `dest`. Si no existeix, no toca `dest`.
|
|
// Si existeix però el tipus no encaixa, deixa el valor per defecte i avisa per stderr
|
|
// (un debug.yaml mal escrit no ha de tombar l'arrencada, però l'usuari ha de saber-ho).
|
|
template <typename T>
|
|
void loadYamlField(const fkyaml::node& yaml, const std::string& key, T& dest) {
|
|
if (!yaml.contains(key)) { return; }
|
|
try {
|
|
dest = yaml[key].get_value<T>();
|
|
} catch (...) {
|
|
std::cerr << "debug.yaml: valor invàlid per a '" << key << "', es manté el valor per defecte\n";
|
|
}
|
|
}
|
|
|
|
auto parseInitialSection(const std::string& value) -> Section::Name {
|
|
if (value == "logo") { return Section::Name::LOGO; }
|
|
if (value == "intro") { return Section::Name::INTRO; }
|
|
if (value == "title") { return Section::Name::TITLE; }
|
|
if (value == "credits") { return Section::Name::CREDITS; }
|
|
if (value == "instructions") { return Section::Name::INSTRUCTIONS; }
|
|
if (value == "hiscore") { return Section::Name::HI_SCORE_TABLE; }
|
|
return Section::Name::GAME; // "game" i qualsevol valor desconegut
|
|
}
|
|
|
|
auto parseInitialOptions(const std::string& value) -> Section::Options {
|
|
if (value == "none") { return Section::Options::NONE; }
|
|
if (value == "2p") { return Section::Options::GAME_PLAY_2P; }
|
|
if (value == "both") { return Section::Options::GAME_PLAY_BOTH; }
|
|
return Section::Options::GAME_PLAY_1P; // "1p" i qualsevol valor desconegut
|
|
}
|
|
} // namespace
|
|
|
|
// Constructor
|
|
Director::Director(int /*argc*/, char** argv)
|
|
: argv_(argv) {
|
|
Section::attract_mode = Section::AttractMode::TITLE_TO_DEMO;
|
|
|
|
// Establece el nivel de prioridad de la categoría de registro
|
|
SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
|
|
SDL_SetLogPriority(SDL_LOG_CATEGORY_TEST, SDL_LOG_PRIORITY_ERROR);
|
|
|
|
// Inicia la semilla aleatoria usando el tiempo actual en segundos
|
|
std::srand(static_cast<unsigned int>(std::time(nullptr)));
|
|
|
|
std::cout << "Game start\n";
|
|
|
|
// Obtener la ruta del ejecutable desde SDL
|
|
#ifdef __EMSCRIPTEN__
|
|
// En Emscripten los assets viven en la raíz del MEMFS virtual (/data, /config),
|
|
// preloaded vía --preload-file en el linker. No hay ruta de ejecutable.
|
|
executable_path_ = "";
|
|
#else
|
|
const char* base_path = SDL_GetBasePath();
|
|
executable_path_ = (base_path != nullptr) ? base_path : "";
|
|
#endif
|
|
|
|
// Crea la carpeta del sistema donde guardar los datos persistentes
|
|
createSystemFolder("jailgames");
|
|
createSystemFolder("jailgames/coffee_crisis_arcade_edition");
|
|
|
|
// Establecer sección inicial según modo de compilación
|
|
#ifdef RECORDING
|
|
Section::name = Section::Name::GAME;
|
|
Section::options = Section::Options::GAME_PLAY_1P;
|
|
#elif _DEBUG
|
|
loadDebugConfig();
|
|
#else
|
|
Section::name = Section::Name::LOGO;
|
|
Section::options = Section::Options::NONE;
|
|
#endif
|
|
|
|
init();
|
|
}
|
|
|
|
Director::~Director() {
|
|
// Libera las secciones primero: sus destructores pueden tocar Audio/Resource/Screen,
|
|
// que close() destruye a continuación.
|
|
resetActiveSection();
|
|
close();
|
|
}
|
|
|
|
// Inicializa todo
|
|
void Director::init() {
|
|
// Configuración inicial de parametros
|
|
Asset::init(executable_path_); // Inicializa el sistema de gestión de archivos
|
|
|
|
// Determinar ruta del pack según la plataforma
|
|
#ifdef MACOS_BUNDLE
|
|
std::string pack_path = executable_path_ + "../Resources/resources.pack";
|
|
#else
|
|
std::string pack_path = executable_path_ + "resources.pack";
|
|
#endif
|
|
|
|
// Inicializar sistema de recursos con o sin fallback según el tipo de build
|
|
#if defined(RELEASE_BUILD) && !defined(__EMSCRIPTEN__)
|
|
// Release nativo: Sin fallback - Solo resources.pack (estricto)
|
|
ResourceHelper::initializeResourceSystem(pack_path, false);
|
|
#else
|
|
// Desarrollo o Emscripten: Con fallback - carga desde filesystem/MEMFS
|
|
ResourceHelper::initializeResourceSystem(pack_path, true);
|
|
#endif
|
|
|
|
loadAssets(); // Crea el índice de archivos
|
|
|
|
// gamecontrollerdb.txt no pot anar al pack (SDL_AddGamepadMappingsFromFile només llegeix del FS).
|
|
// Sempre viu al costat del binari, fora del índex d'assets.
|
|
Input::init(executable_path_ + "gamecontrollerdb.txt", Asset::get()->getPath("controllers.json")); // Carga configuración de controles
|
|
|
|
Options::setConfigFile(Asset::get()->getPath("config.yaml")); // Establece el fichero de configuración
|
|
Options::setControllersFile(Asset::get()->getPath("controllers.json")); // Establece el fichero de configuración de mandos
|
|
Options::setPostFXFile(Asset::get()->getPath("postfx.yaml")); // Establece el fichero de presets PostFX
|
|
Options::setCrtPiFile(Asset::get()->getPath("crtpi.yaml")); // Establece el fichero de presets CrtPi
|
|
Options::loadFromFile(); // Carga el archivo de configuración
|
|
Options::loadPostFXFromFile(); // Carga los presets PostFX
|
|
Options::loadCrtPiFromFile(); // Carga los presets CrtPi
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
// En la versión web el navegador gestiona la ventana: ventana (no
|
|
// fullscreen — el canvas ya marca el área), integer scale para píxeles nítidos.
|
|
Options::window.zoom = 3;
|
|
Options::video.fullscreen = false;
|
|
Options::video.integer_scale = true;
|
|
// Precarga silenciosa: pantalla negra mientras el .data termina de descargarse.
|
|
Options::loading.show = false;
|
|
Options::loading.wait_for_input = false;
|
|
#endif
|
|
loadParams(); // Carga los parámetros del programa
|
|
loadScoreFile(); // Carga el archivo de puntuaciones
|
|
|
|
// Inicialización de subsistemas principales
|
|
Lang::setLanguage(Options::settings.language); // Carga el archivo de idioma
|
|
|
|
Screen::init(); // Inicializa la pantalla y el sistema de renderizado
|
|
|
|
Audio::init(); // Activa el sistema de audio
|
|
|
|
#ifdef _DEBUG
|
|
Resource::init(debug_config.resource_loading == "lazy" ? Resource::LoadingMode::LAZY_LOAD : Resource::LoadingMode::PRELOAD);
|
|
#else
|
|
Resource::init(Resource::LoadingMode::PRELOAD);
|
|
#endif
|
|
|
|
if (Resource::get()->getLoadingMode() == Resource::LoadingMode::PRELOAD) {
|
|
// Guarda la sección destino (la que fijó loadDebugConfig o el default)
|
|
// y redirige el arranque a la escena PRELOAD hasta que loadStep termine.
|
|
Section::post_preload = Section::name;
|
|
Section::name = Section::Name::PRELOAD;
|
|
Resource::get()->beginLoad();
|
|
} else {
|
|
// LAZY_LOAD: el constructor de Resource ya cargó lo esencial síncronamente;
|
|
// no hay fase de preload, pasamos directamente a post-boot.
|
|
finishBoot();
|
|
boot_loading_ = false;
|
|
}
|
|
|
|
// ServiceMenu/Notifier/getSingletons se mueven a finishBoot() — dependen
|
|
// de Resource y se inicializan al terminar la carga incremental.
|
|
}
|
|
|
|
// Inicializaciones que dependen del Resource cargado. Se llama desde iterate()
|
|
// cuando Resource::loadStep() devuelve true, con la ventana y el bucle vivos.
|
|
void Director::finishBoot() {
|
|
ServiceMenu::init(); // Inicializa el menú de servicio
|
|
Notifier::init(std::string(), Resource::get()->getText("8bithud")); // Inicialización del sistema de notificaciones
|
|
Screen::get()->getSingletons(); // Obtiene los punteros al resto de singletones
|
|
|
|
// Restaura el vsync a la preferencia del usuario (beginLoad lo había puesto a false)
|
|
Screen::get()->setVSync(Options::video.vsync);
|
|
|
|
// Si NO estamos en modo "wait for input", transiciona ya al destino.
|
|
// Si wait_for_input está activo (y la pantalla es visible), nos quedamos
|
|
// en PRELOAD hasta que el usuario pulse tecla/botón.
|
|
if (!(Options::loading.show && Options::loading.wait_for_input)) {
|
|
Section::name = Section::post_preload;
|
|
}
|
|
}
|
|
|
|
// Allibera tots els singletons i SDL (compartit entre close() i relaunch())
|
|
void Director::shutdownSubsystems() {
|
|
// Guarda las opciones actuales en el archivo de configuración
|
|
Options::saveToFile();
|
|
|
|
// Libera los singletons y recursos en orden inverso al de inicialización
|
|
Notifier::destroy(); // Libera el sistema de notificaciones
|
|
ServiceMenu::destroy(); // Libera el sistema de menú de servicio
|
|
Input::destroy(); // Libera el sistema de entrada
|
|
Resource::destroy(); // Libera el sistema de recursos gráficos y de texto
|
|
Audio::destroy(); // Libera el sistema de audio
|
|
Screen::destroy(); // Libera el sistema de pantalla y renderizado
|
|
Asset::destroy(); // Libera el gestor de archivos
|
|
|
|
// Libera todos los recursos de SDL
|
|
SDL_Quit();
|
|
}
|
|
|
|
// Cierra todo y libera recursos del sistema y de los singletons
|
|
void Director::close() {
|
|
shutdownSubsystems();
|
|
std::cout << "\nBye!\n";
|
|
// Apaga el sistema
|
|
shutdownSystem(Section::options == Section::Options::SHUTDOWN);
|
|
}
|
|
|
|
// Reemplaça el procés actual per ell mateix (reinici en calent). En cas d'èxit no torna.
|
|
// Si no es pot reiniciar (Emscripten, argv invàlid), retorna i el caller fa el reset clàssic.
|
|
void Director::relaunch() const {
|
|
#ifdef __EMSCRIPTEN__
|
|
// Al navegador el reinici real seria location.reload(); aquí caiem al reset intern.
|
|
return;
|
|
#else
|
|
if (argv_ == nullptr || argv_[0] == nullptr) { return; }
|
|
std::cout << "Relaunching " << argv_[0] << "...\n";
|
|
shutdownSubsystems();
|
|
#ifdef _WIN32
|
|
_execv(argv_[0], argv_);
|
|
#else
|
|
execv(argv_[0], argv_);
|
|
#endif
|
|
// Si arribem aquí, execv ha fallat. Tots els subsistemes ja estan destruïts: no
|
|
// podem reprendre el bucle. Sortim amb error.
|
|
std::cerr << "Relaunch failed: " << std::strerror(errno) << "\n";
|
|
std::exit(EXIT_FAILURE);
|
|
#endif
|
|
}
|
|
|
|
// Carga los parametros
|
|
void Director::loadParams() {
|
|
// Carga los parametros para configurar el juego
|
|
#ifdef ANBERNIC
|
|
const std::string PARAM_FILE_PATH = Asset::get()->getPath("classic.txt");
|
|
#else
|
|
const std::string PARAM_FILE_PATH = Asset::get()->getPath(Options::settings.params_preset + ".txt");
|
|
#endif
|
|
loadParamsFromFile(PARAM_FILE_PATH);
|
|
}
|
|
|
|
// Carga el fichero de puntuaciones
|
|
void Director::loadScoreFile() {
|
|
auto manager = std::make_unique<ManageHiScoreTable>(Options::settings.hi_score_table);
|
|
#ifdef _DEBUG
|
|
manager->clear();
|
|
#else
|
|
manager->loadFromFile(Asset::get()->getPath("score.bin"));
|
|
#endif
|
|
}
|
|
|
|
// Carga el indice de ficheros desde el pack (o filesystem como fallback)
|
|
void Director::loadAssets() {
|
|
#ifdef MACOS_BUNDLE
|
|
const std::string PREFIX = "/../Resources";
|
|
#else
|
|
const std::string PREFIX;
|
|
#endif
|
|
|
|
// El índice ahora vive dins el pack a "config/assets.txt" (ResourceHelper li trau el "data/" prefix).
|
|
// ResourceHelper::loadFile fa fallback automàtic al filesystem si el pack no està o no conté el fitxer.
|
|
auto buffer = ResourceHelper::loadFile("/data/config/assets.txt");
|
|
if (buffer.empty()) {
|
|
throw std::runtime_error("No s'ha pogut carregar l'índex d'assets (data/config/assets.txt)");
|
|
}
|
|
Asset::get()->loadFromBuffer(buffer, PREFIX, system_folder_);
|
|
|
|
// Si falta algun fichero, sale del programa
|
|
if (!Asset::get()->check()) {
|
|
throw std::runtime_error("Falta algun fichero");
|
|
}
|
|
}
|
|
|
|
// Carga debug.yaml desde la carpeta del sistema (solo en _DEBUG)
|
|
void Director::loadDebugConfig() {
|
|
const std::string DEBUG_FILE = system_folder_ + "/debug.yaml";
|
|
|
|
std::ifstream file(DEBUG_FILE);
|
|
if (!file.good()) {
|
|
// Crear fichero por defecto
|
|
std::ofstream out(DEBUG_FILE);
|
|
if (out.is_open()) {
|
|
out << "# Coffee Crisis Arcade Edition - Debug Configuration\n";
|
|
out << "# This file is only read in DEBUG builds.\n";
|
|
out << "#\n";
|
|
out << "# initial_section: logo, intro, title, game, credits, instructions, hiscore\n";
|
|
out << "# initial_options: none, 1p, 2p, both\n";
|
|
out << "# initial_stage: 0-based stage index (only when section is game)\n";
|
|
out << "# show_render_info: show FPS/driver/preset overlay\n";
|
|
out << "# resource_loading: preload, lazy\n";
|
|
out << "\n";
|
|
out << "initial_section: game\n";
|
|
out << "initial_options: 1p\n";
|
|
out << "initial_stage: 0\n";
|
|
out << "show_render_info: true\n";
|
|
out << "resource_loading: preload\n";
|
|
out << "autoplay: false\n";
|
|
out << "invincibility: false\n";
|
|
out.close();
|
|
}
|
|
// Usar defaults de DebugConfig
|
|
} else {
|
|
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
|
file.close();
|
|
try {
|
|
auto yaml = fkyaml::node::deserialize(content);
|
|
loadYamlField(yaml, "initial_section", debug_config.initial_section);
|
|
loadYamlField(yaml, "initial_options", debug_config.initial_options);
|
|
loadYamlField(yaml, "initial_stage", debug_config.initial_stage);
|
|
loadYamlField(yaml, "show_render_info", debug_config.show_render_info);
|
|
loadYamlField(yaml, "resource_loading", debug_config.resource_loading);
|
|
loadYamlField(yaml, "autoplay", debug_config.autoplay);
|
|
loadYamlField(yaml, "invincibility", debug_config.invincibility);
|
|
} catch (...) {
|
|
std::cout << "Error parsing debug.yaml, using defaults" << '\n';
|
|
}
|
|
}
|
|
|
|
Section::name = parseInitialSection(debug_config.initial_section);
|
|
Section::options = parseInitialOptions(debug_config.initial_options);
|
|
}
|
|
|
|
// Crea la carpeta del sistema donde guardar datos
|
|
void Director::createSystemFolder(const std::string& folder) {
|
|
auto result = SystemUtils::createApplicationFolder(folder, system_folder_);
|
|
|
|
if (result != SystemUtils::Result::SUCCESS) {
|
|
std::cerr << "Error creando carpeta del sistema: "
|
|
<< SystemUtils::resultToString(result) << '\n';
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
// Libera todos los unique_ptr de sección (solo uno tiene propiedad a la vez)
|
|
void Director::resetActiveSection() {
|
|
preload_.reset();
|
|
logo_.reset();
|
|
intro_.reset();
|
|
title_.reset();
|
|
game_.reset();
|
|
instructions_.reset();
|
|
hi_score_table_.reset();
|
|
credits_.reset();
|
|
}
|
|
|
|
// Destruye la sección anterior y construye la nueva cuando Section::name cambia
|
|
void Director::handleSectionTransition() {
|
|
// RESET: intenta reinici real via execv; si no es pot (Emscripten o argv invàlid),
|
|
// cau al reset intern (recarrega recursos i torna a LOGO, sense recrear Screen/Params).
|
|
if (Section::name == Section::Name::RESET) {
|
|
relaunch(); // En èxit no torna; el binari es reemplaça
|
|
resetActiveSection(); // Fallback: libera recursos actuales antes del reload
|
|
reset();
|
|
}
|
|
|
|
if (Section::name == last_built_section_name_) {
|
|
return; // ya tenemos la sección correcta viva
|
|
}
|
|
|
|
// Destruye la sección anterior
|
|
resetActiveSection();
|
|
|
|
// Construye la nueva
|
|
switch (Section::name) {
|
|
case Section::Name::PRELOAD:
|
|
preload_ = std::make_unique<Preload>();
|
|
break;
|
|
|
|
case Section::Name::LOGO:
|
|
logo_ = std::make_unique<Logo>();
|
|
break;
|
|
|
|
case Section::Name::INTRO:
|
|
intro_ = std::make_unique<Intro>();
|
|
break;
|
|
|
|
case Section::Name::TITLE:
|
|
title_ = std::make_unique<Title>();
|
|
break;
|
|
|
|
case Section::Name::GAME: {
|
|
Player::Id player_id = Player::Id::PLAYER1;
|
|
switch (Section::options) {
|
|
case Section::Options::GAME_PLAY_1P:
|
|
player_id = Player::Id::PLAYER1;
|
|
break;
|
|
case Section::Options::GAME_PLAY_2P:
|
|
player_id = Player::Id::PLAYER2;
|
|
break;
|
|
case Section::Options::GAME_PLAY_BOTH:
|
|
player_id = Player::Id::BOTH_PLAYERS;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
#ifdef _DEBUG
|
|
const int CURRENT_STAGE = debug_config.initial_stage;
|
|
#else
|
|
constexpr int CURRENT_STAGE = 0;
|
|
#endif
|
|
game_ = std::make_unique<Game>(player_id, CURRENT_STAGE, Game::DEMO_OFF);
|
|
break;
|
|
}
|
|
|
|
case Section::Name::GAME_DEMO: {
|
|
const auto PLAYER_ID = static_cast<Player::Id>((rand() % 2) + 1);
|
|
constexpr auto CURRENT_STAGE = 0;
|
|
game_ = std::make_unique<Game>(PLAYER_ID, CURRENT_STAGE, Game::DEMO_ON);
|
|
break;
|
|
}
|
|
|
|
case Section::Name::INSTRUCTIONS:
|
|
instructions_ = std::make_unique<Instructions>();
|
|
break;
|
|
|
|
case Section::Name::CREDITS:
|
|
credits_ = std::make_unique<Credits>();
|
|
break;
|
|
|
|
case Section::Name::HI_SCORE_TABLE:
|
|
hi_score_table_ = std::make_unique<HiScoreTable>();
|
|
break;
|
|
|
|
case Section::Name::RESET:
|
|
case Section::Name::QUIT:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
last_built_section_name_ = Section::name;
|
|
}
|
|
|
|
// Reinicia objetos y vuelve a la sección inicial
|
|
void Director::reset() {
|
|
Options::saveToFile();
|
|
Options::loadFromFile();
|
|
Lang::setLanguage(Options::settings.language);
|
|
Audio::get()->stopMusic();
|
|
Audio::get()->stopAllSounds();
|
|
Resource::get()->reload();
|
|
ServiceMenu::get()->reset();
|
|
Section::name = Section::Name::LOGO;
|
|
}
|
|
|
|
// Avanza un frame de la sección activa (llamado desde SDL_AppIterate)
|
|
auto Director::iterate() -> SDL_AppResult {
|
|
if (Section::name == Section::Name::QUIT) {
|
|
return SDL_APP_SUCCESS;
|
|
}
|
|
|
|
// Fase de boot: carga incremental frame a frame con presupuesto de 50ms.
|
|
// Durante esta fase la escena activa es Preload (una barra de progreso).
|
|
if (boot_loading_) {
|
|
try {
|
|
if (Resource::get()->loadStep(50 /*ms*/)) {
|
|
finishBoot();
|
|
boot_loading_ = false;
|
|
// Los SDL_EVENT_GAMEPAD_ADDED iniciales ya los ha drenado la rama
|
|
// durante la carga: marcamos startup completo ahora para que los
|
|
// ADDED/REMOVED posteriores sí generen notificación.
|
|
GlobalEvents::markStartupComplete();
|
|
}
|
|
} catch (const std::exception& e) {
|
|
std::cerr << "Fatal error during resource load: " << e.what() << '\n';
|
|
Section::name = Section::Name::QUIT;
|
|
return SDL_APP_FAILURE;
|
|
}
|
|
}
|
|
|
|
// Gestiona las transiciones entre secciones (destruye la anterior y construye la nueva)
|
|
handleSectionTransition();
|
|
|
|
// Ejecuta un frame de la sección activa
|
|
if (preload_) {
|
|
Preload::iterate();
|
|
} else if (logo_) {
|
|
logo_->iterate();
|
|
} else if (intro_) {
|
|
intro_->iterate();
|
|
} else if (title_) {
|
|
title_->iterate();
|
|
} else if (game_) {
|
|
game_->iterate();
|
|
} else if (instructions_) {
|
|
instructions_->iterate();
|
|
} else if (hi_score_table_) {
|
|
hi_score_table_->iterate();
|
|
} else if (credits_) {
|
|
credits_->iterate();
|
|
}
|
|
|
|
return (Section::name == Section::Name::QUIT) ? SDL_APP_SUCCESS : SDL_APP_CONTINUE;
|
|
}
|
|
|
|
// Procesa un evento SDL (llamado desde SDL_AppEvent)
|
|
auto Director::handleEvent(const SDL_Event& event) -> SDL_AppResult {
|
|
// Eventos globales (SDL_EVENT_QUIT, resize, render target reset, hotplug, service menu, ratón)
|
|
GlobalEvents::handle(event);
|
|
|
|
// Reenvía a la sección activa
|
|
if (preload_) {
|
|
Preload::handleEvent(event);
|
|
} else if (logo_) {
|
|
logo_->handleEvent(event);
|
|
} else if (intro_) {
|
|
intro_->handleEvent(event);
|
|
} else if (title_) {
|
|
title_->handleEvent(event);
|
|
} else if (game_) {
|
|
game_->handleEvent(event);
|
|
} else if (instructions_) {
|
|
instructions_->handleEvent(event);
|
|
} else if (hi_score_table_) {
|
|
hi_score_table_->handleEvent(event);
|
|
} else if (credits_) {
|
|
credits_->handleEvent(event);
|
|
}
|
|
|
|
return (Section::name == Section::Name::QUIT) ? SDL_APP_SUCCESS : SDL_APP_CONTINUE;
|
|
}
|
|
|
|
// Apaga el sistema de forma segura
|
|
void Director::shutdownSystem(bool should_shutdown) {
|
|
if (should_shutdown) {
|
|
auto result = SystemShutdown::shutdownSystem(5, true); // 5 segundos, forzar apps
|
|
|
|
if (result != SystemShutdown::ShutdownResult::SUCCESS) {
|
|
std::cerr << SystemShutdown::resultToString(result) << '\n';
|
|
}
|
|
}
|
|
} |