469 lines
18 KiB
C++
469 lines
18 KiB
C++
// IWYU pragma: no_include <bits/chrono.h>
|
|
#include "director.hpp"
|
|
|
|
#include <SDL3/SDL.h> // Para SDL_SetLogPriority, SDL_LogCategory, SDL_LogPriority, SDL_Quit
|
|
|
|
#include <cstdlib> // Para srand, exit, rand, EXIT_FAILURE
|
|
#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==
|
|
|
|
#include "asset.hpp" // Para Asset
|
|
#include "audio.hpp" // Para Audio
|
|
#include "external/fkyaml_node.hpp" // Para fkyaml::node
|
|
#include "global_events.hpp" // Para GlobalEvents::handle
|
|
#include "input.hpp" // Para Input
|
|
#include "lang.hpp" // Para setLanguage
|
|
#include "manage_hiscore_table.hpp" // Para ManageHiScoreTable
|
|
#include "options.hpp" // Para Settings, loadFromFile, saveToFile, settings, setConfigFile, setControllersFile
|
|
#include "param.hpp" // Para loadParamsFromFile
|
|
#include "player.hpp" // Para Player
|
|
#include "resource.hpp" // Para Resource
|
|
#include "resource_helper.hpp" // Para initializeResourceSystem
|
|
#include "screen.hpp" // Para Screen
|
|
#include "section.hpp" // Para Name, Options, name, options, AttractMode, attract_mode
|
|
#include "sections/credits.hpp" // Para Credits
|
|
#include "sections/game.hpp" // Para Game
|
|
#include "sections/hiscore_table.hpp" // Para HiScoreTable
|
|
#include "sections/instructions.hpp" // Para Instructions
|
|
#include "sections/intro.hpp" // Para Intro
|
|
#include "sections/logo.hpp" // Para Logo
|
|
#include "sections/title.hpp" // Para Title
|
|
#include "shutdown.hpp" // Para resultToString, shutdownSystem, ShutdownResult
|
|
#include "system_utils.hpp" // Para createApplicationFolder, resultToString, Result
|
|
#include "ui/notifier.hpp" // Para Notifier
|
|
#include "ui/service_menu.hpp" // Para ServiceMenu
|
|
|
|
// Constructor
|
|
Director::Director() {
|
|
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
|
|
const char* base_path = SDL_GetBasePath();
|
|
executable_path_ = (base_path != nullptr) ? base_path : "";
|
|
|
|
// 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
|
|
#ifdef RELEASE_BUILD
|
|
// Release: Sin fallback - Solo resources.pack (estricto)
|
|
ResourceHelper::initializeResourceSystem(pack_path, false);
|
|
#else
|
|
// Desarrollo: Con fallback - Puede usar data/ si falta el pack (flexible)
|
|
ResourceHelper::initializeResourceSystem(pack_path, true);
|
|
#endif
|
|
|
|
loadAssets(); // Crea el índice de archivos
|
|
|
|
Input::init(Asset::get()->getPath("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
|
|
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
|
|
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
|
|
}
|
|
|
|
// Cierra todo y libera recursos del sistema y de los singletons
|
|
void Director::close() {
|
|
// 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
|
|
|
|
std::cout << "\nBye!\n";
|
|
|
|
// Libera todos los recursos de SDL
|
|
SDL_Quit();
|
|
|
|
// Apaga el sistema
|
|
shutdownSystem(Section::options == Section::Options::SHUTDOWN);
|
|
}
|
|
|
|
// Carga los parametros
|
|
void Director::loadParams() {
|
|
// Carga los parametros para configurar el juego
|
|
#ifdef ANBERNIC
|
|
const std::string PARAM_FILE_PATH = Asset::get()->getPath("param_320x240.txt");
|
|
#else
|
|
const std::string PARAM_FILE_PATH = Asset::get()->getPath(Options::settings.params_file);
|
|
#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 un fichero
|
|
void Director::loadAssets() {
|
|
#ifdef MACOS_BUNDLE
|
|
const std::string PREFIX = "/../Resources";
|
|
#else
|
|
const std::string PREFIX;
|
|
#endif
|
|
|
|
// Cargar la configuración de assets (también aplicar el prefijo al archivo de configuración)
|
|
std::string config_path = executable_path_ + PREFIX + "/config/assets.txt";
|
|
Asset::get()->loadFromFile(config_path, 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.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);
|
|
if (yaml.contains("initial_section")) {
|
|
try {
|
|
debug_config.initial_section = yaml["initial_section"].get_value<std::string>();
|
|
} catch (...) {}
|
|
}
|
|
if (yaml.contains("initial_options")) {
|
|
try {
|
|
debug_config.initial_options = yaml["initial_options"].get_value<std::string>();
|
|
} catch (...) {}
|
|
}
|
|
if (yaml.contains("initial_stage")) {
|
|
try {
|
|
debug_config.initial_stage = yaml["initial_stage"].get_value<int>();
|
|
} catch (...) {}
|
|
}
|
|
if (yaml.contains("show_render_info")) {
|
|
try {
|
|
debug_config.show_render_info = yaml["show_render_info"].get_value<bool>();
|
|
} catch (...) {}
|
|
}
|
|
if (yaml.contains("resource_loading")) {
|
|
try {
|
|
debug_config.resource_loading = yaml["resource_loading"].get_value<std::string>();
|
|
} catch (...) {}
|
|
}
|
|
} catch (...) {
|
|
std::cout << "Error parsing debug.yaml, using defaults" << '\n';
|
|
}
|
|
}
|
|
|
|
// Mapear strings a enums
|
|
const auto& sec = debug_config.initial_section;
|
|
if (sec == "logo") {
|
|
Section::name = Section::Name::LOGO;
|
|
} else if (sec == "intro") {
|
|
Section::name = Section::Name::INTRO;
|
|
} else if (sec == "title") {
|
|
Section::name = Section::Name::TITLE;
|
|
} else if (sec == "game") {
|
|
Section::name = Section::Name::GAME;
|
|
} else if (sec == "credits") {
|
|
Section::name = Section::Name::CREDITS;
|
|
} else if (sec == "instructions") {
|
|
Section::name = Section::Name::INSTRUCTIONS;
|
|
} else if (sec == "hiscore") {
|
|
Section::name = Section::Name::HI_SCORE_TABLE;
|
|
} else {
|
|
Section::name = Section::Name::GAME;
|
|
}
|
|
|
|
const auto& opt = debug_config.initial_options;
|
|
if (opt == "none") {
|
|
Section::options = Section::Options::NONE;
|
|
} else if (opt == "1p") {
|
|
Section::options = Section::Options::GAME_PLAY_1P;
|
|
} else if (opt == "2p") {
|
|
Section::options = Section::Options::GAME_PLAY_2P;
|
|
} else if (opt == "both") {
|
|
Section::options = Section::Options::GAME_PLAY_BOTH;
|
|
} else {
|
|
Section::options = Section::Options::GAME_PLAY_1P;
|
|
}
|
|
}
|
|
|
|
// 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() {
|
|
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: recarga recursos y vuelve a LOGO (el propio reset() cambia Section::name)
|
|
if (Section::name == Section::Name::RESET) {
|
|
resetActiveSection(); // 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::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;
|
|
}
|
|
|
|
// Gestiona las transiciones entre secciones (destruye la anterior y construye la nueva)
|
|
handleSectionTransition();
|
|
|
|
// Ejecuta un frame de la sección activa
|
|
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(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 (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';
|
|
}
|
|
}
|
|
} |