653 lines
24 KiB
C++
653 lines
24 KiB
C++
#include "core/system/director.h"
|
|
|
|
#include <SDL3/SDL.h>
|
|
#include <errno.h> // for errno, EEXIST, EACCES, ENAMETOO...
|
|
#include <stdio.h> // for printf, perror
|
|
#include <string.h> // for strcmp
|
|
#ifndef __EMSCRIPTEN__
|
|
#include <sys/stat.h> // for mkdir, stat, S_IRWXU
|
|
#include <unistd.h> // for getuid
|
|
#endif
|
|
|
|
#include <cstdlib> // for exit, EXIT_FAILURE, srand
|
|
#include <filesystem>
|
|
#include <fstream> // for basic_ostream, operator<<, basi...
|
|
#include <iostream> // for cout
|
|
#include <memory>
|
|
#include <string> // for basic_string, operator+, char_t...
|
|
#include <vector> // for vector
|
|
|
|
#include "core/audio/jail_audio.hpp" // for JA_Init
|
|
#include "core/input/input.h" // for Input, inputs_e, INPUT_USE_GAME...
|
|
#include "core/input/mouse.hpp" // for Mouse::handleEvent, Mouse::upda...
|
|
#include "core/locale/lang.h" // for Lang, MAX_LANGUAGES, ba_BA, en_UK
|
|
#include "core/rendering/screen.h" // for Screen
|
|
#include "core/rendering/texture.h" // for Texture
|
|
#include "core/resources/asset.h" // for Asset, assetType
|
|
#include "core/resources/resource.h"
|
|
#include "core/resources/resource_helper.h"
|
|
#include "game/defaults.hpp" // for SECTION_PROG_LOGO, GAMECANVAS_H...
|
|
#include "game/game.h" // for Game
|
|
#include "game/options.hpp" // for Options::init, loadFromFile...
|
|
#include "game/scenes/intro.h" // for Intro
|
|
#include "game/scenes/logo.h" // for Logo
|
|
#include "game/scenes/title.h" // for Title
|
|
#include "utils/utils.h" // for input_t, boolToString
|
|
|
|
#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)
|
|
#include <pwd.h>
|
|
#endif
|
|
|
|
// Constructor
|
|
Director::Director(int argc, const char *argv[]) {
|
|
std::cout << "Game start" << std::endl;
|
|
// Inicializa variables
|
|
section = new section_t();
|
|
section->name = SECTION_PROG_LOGO;
|
|
|
|
// Inicializa las opciones del programa (defaults + dispositivos d'entrada)
|
|
Options::init();
|
|
|
|
// Comprueba los parametros del programa (pot activar console)
|
|
checkProgramArguments(argc, argv);
|
|
|
|
// Crea la carpeta del sistema donde guardar datos
|
|
createSystemFolder("jailgames");
|
|
#ifndef DEBUG
|
|
createSystemFolder("jailgames/coffee_crisis");
|
|
#else
|
|
createSystemFolder("jailgames/coffee_crisis_debug");
|
|
#endif
|
|
|
|
// Estableix el fitxer de configuració i carrega les opcions (o crea el
|
|
// YAML amb defaults si no existeix).
|
|
Options::setConfigFile(systemFolder + "/config.yaml");
|
|
Options::loadFromFile();
|
|
|
|
// Presets de shaders (creats amb defaults si no existeixen).
|
|
Options::setPostFXFile(systemFolder + "/postfx.yaml");
|
|
Options::loadPostFXFromFile();
|
|
Options::setCrtPiFile(systemFolder + "/crtpi.yaml");
|
|
Options::loadCrtPiFromFile();
|
|
|
|
// Inicializa el sistema de recursos (pack + fallback).
|
|
// En wasm siempre se usa filesystem (MEMFS) porque el propio --preload-file
|
|
// de emscripten ya empaqueta data/ — no hay resources.pack.
|
|
{
|
|
#if defined(__EMSCRIPTEN__)
|
|
const bool enable_fallback = true;
|
|
#elif defined(RELEASE_BUILD)
|
|
const bool enable_fallback = false;
|
|
#else
|
|
const bool enable_fallback = true;
|
|
#endif
|
|
if (!ResourceHelper::initializeResourceSystem("resources.pack", enable_fallback)) {
|
|
std::cerr << "Fatal: resource system init failed (missing resources.pack?)" << std::endl;
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
// Crea el objeto que controla los ficheros de recursos
|
|
Asset::init(executablePath);
|
|
Asset::get()->setVerbose(Options::settings.console);
|
|
|
|
// Si falta algún fichero no inicia el programa
|
|
if (!setFileList()) {
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// Inicializa SDL
|
|
initSDL();
|
|
|
|
// Inicializa JailAudio
|
|
initJailAudio();
|
|
|
|
// Establece el modo de escalado de texturas
|
|
Texture::setGlobalScaleMode(Options::video.scale_mode);
|
|
|
|
// Crea los objetos
|
|
Lang::init();
|
|
Lang::get()->setLang(Options::settings.language);
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
Input::init("/gamecontrollerdb.txt");
|
|
#else
|
|
{
|
|
const std::string binDir = std::filesystem::path(executablePath).parent_path().string();
|
|
#ifdef MACOS_BUNDLE
|
|
Input::init(binDir + "/../Resources/gamecontrollerdb.txt");
|
|
#else
|
|
Input::init(binDir + "/gamecontrollerdb.txt");
|
|
#endif
|
|
}
|
|
#endif
|
|
initInput();
|
|
|
|
// Orden importante: Screen + initShaders ANTES de Resource::init.
|
|
// Si `Resource::init` se ejecuta primero, carga ~100 texturas vía
|
|
// `SDL_CreateTexture` que dejan el SDL_Renderer con el swapchain en un
|
|
// estado que hace crashear al driver Vulkan cuando después `initShaders`
|
|
// intenta reclamar la ventana para el dispositivo SDL3 GPU.
|
|
//
|
|
// Por eso el constructor de Screen NO carga notificationText desde
|
|
// Resource; se enlaza después vía `Screen::get()->initNotifications()`.
|
|
Screen::init(window, renderer);
|
|
|
|
#ifndef NO_SHADERS
|
|
if (Options::video.gpu.acceleration) {
|
|
Screen::get()->initShaders();
|
|
}
|
|
#endif
|
|
|
|
// Ahora sí, precarga todos los recursos en memoria (texturas, sonidos,
|
|
// música, ...). Vivirán durante toda la vida de la app.
|
|
Resource::init(renderer);
|
|
|
|
// Completa el enlazado de Screen con recursos que necesitan Resource
|
|
// inicializado (actualmente sólo el Text de las notificaciones).
|
|
Screen::get()->initNotifications();
|
|
|
|
activeSection = ActiveSection::None;
|
|
}
|
|
|
|
Director::~Director() {
|
|
Options::saveToFile();
|
|
|
|
// Libera las secciones primero: sus destructores tocan audio/render SDL
|
|
// (p.ej. Intro::~Intro llama a JA_DeleteMusic) y deben ejecutarse antes
|
|
// de SDL_Quit().
|
|
logo.reset();
|
|
intro.reset();
|
|
title.reset();
|
|
game.reset();
|
|
|
|
// Screen puede tener referencias a Text propiedad de Resource: destruir
|
|
// Screen antes que Resource.
|
|
Screen::destroy();
|
|
|
|
// Libera todos los recursos precargados antes de cerrar SDL.
|
|
Resource::destroy();
|
|
|
|
Asset::destroy();
|
|
Input::destroy();
|
|
Lang::destroy();
|
|
delete section;
|
|
|
|
SDL_DestroyRenderer(renderer);
|
|
SDL_DestroyWindow(window);
|
|
|
|
SDL_Quit();
|
|
|
|
ResourceHelper::shutdownResourceSystem();
|
|
|
|
std::cout << "\nBye!" << std::endl;
|
|
}
|
|
|
|
// Inicializa el objeto input
|
|
void Director::initInput() {
|
|
// Establece si ha de mostrar mensajes
|
|
Input::get()->setVerbose(Options::settings.console);
|
|
|
|
// Busca si hay un mando conectado
|
|
Input::get()->discoverGameController();
|
|
|
|
// Teclado - Movimiento del jugador
|
|
Input::get()->bindKey(input_up, SDL_SCANCODE_UP);
|
|
Input::get()->bindKey(input_down, SDL_SCANCODE_DOWN);
|
|
Input::get()->bindKey(input_left, SDL_SCANCODE_LEFT);
|
|
Input::get()->bindKey(input_right, SDL_SCANCODE_RIGHT);
|
|
Input::get()->bindKey(input_fire_left, SDL_SCANCODE_Q);
|
|
Input::get()->bindKey(input_fire_center, SDL_SCANCODE_W);
|
|
Input::get()->bindKey(input_fire_right, SDL_SCANCODE_E);
|
|
|
|
// Teclado - Otros
|
|
Input::get()->bindKey(input_accept, SDL_SCANCODE_RETURN);
|
|
Input::get()->bindKey(input_cancel, SDL_SCANCODE_ESCAPE);
|
|
Input::get()->bindKey(input_pause, SDL_SCANCODE_ESCAPE);
|
|
Input::get()->bindKey(input_exit, SDL_SCANCODE_ESCAPE);
|
|
Input::get()->bindKey(input_window_dec_size, SDL_SCANCODE_F1);
|
|
Input::get()->bindKey(input_window_inc_size, SDL_SCANCODE_F2);
|
|
Input::get()->bindKey(input_window_fullscreen, SDL_SCANCODE_F3);
|
|
Input::get()->bindKey(input_prev_preset, SDL_SCANCODE_F8);
|
|
Input::get()->bindKey(input_next_preset, SDL_SCANCODE_F9);
|
|
Input::get()->bindKey(input_toggle_shader, SDL_SCANCODE_F10);
|
|
Input::get()->bindKey(input_toggle_shader_type, SDL_SCANCODE_F11);
|
|
|
|
// Mando - Movimiento del jugador
|
|
Input::get()->bindGameControllerButton(input_up, SDL_GAMEPAD_BUTTON_DPAD_UP);
|
|
Input::get()->bindGameControllerButton(input_down, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
|
|
Input::get()->bindGameControllerButton(input_left, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
|
|
Input::get()->bindGameControllerButton(input_right, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
|
|
Input::get()->bindGameControllerButton(input_fire_left, SDL_GAMEPAD_BUTTON_WEST);
|
|
Input::get()->bindGameControllerButton(input_fire_center, SDL_GAMEPAD_BUTTON_NORTH);
|
|
Input::get()->bindGameControllerButton(input_fire_right, SDL_GAMEPAD_BUTTON_EAST);
|
|
|
|
// Mando - Otros
|
|
// SOUTH queda sin asignar para evitar salidas accidentales: pausa/cancel se hace con START/BACK.
|
|
Input::get()->bindGameControllerButton(input_accept, SDL_GAMEPAD_BUTTON_EAST);
|
|
#ifdef GAME_CONSOLE
|
|
Input::get()->bindGameControllerButton(input_pause, SDL_GAMEPAD_BUTTON_BACK);
|
|
Input::get()->bindGameControllerButton(input_exit, SDL_GAMEPAD_BUTTON_START);
|
|
#else
|
|
Input::get()->bindGameControllerButton(input_pause, SDL_GAMEPAD_BUTTON_START);
|
|
Input::get()->bindGameControllerButton(input_exit, SDL_GAMEPAD_BUTTON_BACK);
|
|
#endif
|
|
}
|
|
|
|
// Inicializa JailAudio
|
|
void Director::initJailAudio() {
|
|
JA_Init(48000, SDL_AUDIO_S16, 2);
|
|
}
|
|
|
|
// Arranca SDL y crea la ventana
|
|
bool Director::initSDL() {
|
|
// Indicador de éxito
|
|
bool success = true;
|
|
|
|
// Inicializa SDL
|
|
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD)) {
|
|
if (Options::settings.console) {
|
|
std::cout << "SDL could not initialize!\nSDL Error: " << SDL_GetError() << std::endl;
|
|
}
|
|
success = false;
|
|
} else {
|
|
// Inicia el generador de numeros aleatorios
|
|
std::srand(static_cast<unsigned int>(SDL_GetTicks()));
|
|
|
|
// Crea la ventana
|
|
window = SDL_CreateWindow(
|
|
Options::window.caption.c_str(),
|
|
GAMECANVAS_WIDTH * Options::window.zoom,
|
|
GAMECANVAS_HEIGHT * Options::window.zoom,
|
|
0);
|
|
if (window == nullptr) {
|
|
if (Options::settings.console) {
|
|
std::cout << "Window could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
|
|
}
|
|
success = false;
|
|
} else {
|
|
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
|
|
|
// Crea un renderizador para la ventana
|
|
renderer = SDL_CreateRenderer(window, NULL);
|
|
|
|
if (renderer == nullptr) {
|
|
if (Options::settings.console) {
|
|
std::cout << "Renderer could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
|
|
}
|
|
success = false;
|
|
} else {
|
|
// Modo de blending por defecto (consistente con CCAE):
|
|
// permite alpha blending para fades y notificaciones.
|
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
|
|
|
// Activa vsync si es necesario
|
|
if (Options::video.vsync) {
|
|
SDL_SetRenderVSync(renderer, 1);
|
|
}
|
|
|
|
// Inicializa el color de renderizado
|
|
SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);
|
|
|
|
// Establece el tamaño del buffer de renderizado
|
|
SDL_SetRenderLogicalPresentation(renderer, GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
|
|
|
// Establece el modo de mezcla
|
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Options::settings.console) {
|
|
std::cout << std::endl;
|
|
}
|
|
return success;
|
|
}
|
|
|
|
// Crea el indice de ficheros
|
|
bool Director::setFileList() {
|
|
#ifdef MACOS_BUNDLE
|
|
const std::string prefix = "/../Resources";
|
|
#else
|
|
const std::string prefix = "";
|
|
#endif
|
|
|
|
// Ficheros de configuración
|
|
Asset::get()->add(systemFolder + "/score.bin", t_data, false, true);
|
|
Asset::get()->add(prefix + "/data/demo/demo.bin", t_data);
|
|
|
|
// Musicas
|
|
Asset::get()->add(prefix + "/data/music/intro.ogg", t_music);
|
|
Asset::get()->add(prefix + "/data/music/playing.ogg", t_music);
|
|
Asset::get()->add(prefix + "/data/music/title.ogg", t_music);
|
|
|
|
// Sonidos
|
|
Asset::get()->add(prefix + "/data/sound/balloon.wav", t_sound);
|
|
Asset::get()->add(prefix + "/data/sound/bubble1.wav", t_sound);
|
|
Asset::get()->add(prefix + "/data/sound/bubble2.wav", t_sound);
|
|
Asset::get()->add(prefix + "/data/sound/bubble3.wav", t_sound);
|
|
Asset::get()->add(prefix + "/data/sound/bubble4.wav", t_sound);
|
|
Asset::get()->add(prefix + "/data/sound/bullet.wav", t_sound);
|
|
Asset::get()->add(prefix + "/data/sound/coffeeout.wav", t_sound);
|
|
Asset::get()->add(prefix + "/data/sound/hiscore.wav", t_sound);
|
|
Asset::get()->add(prefix + "/data/sound/itemdrop.wav", t_sound);
|
|
Asset::get()->add(prefix + "/data/sound/itempickup.wav", t_sound);
|
|
Asset::get()->add(prefix + "/data/sound/menu_move.wav", t_sound);
|
|
Asset::get()->add(prefix + "/data/sound/menu_select.wav", t_sound);
|
|
Asset::get()->add(prefix + "/data/sound/player_collision.wav", t_sound);
|
|
Asset::get()->add(prefix + "/data/sound/stage_change.wav", t_sound);
|
|
Asset::get()->add(prefix + "/data/sound/title.wav", t_sound);
|
|
Asset::get()->add(prefix + "/data/sound/clock.wav", t_sound);
|
|
Asset::get()->add(prefix + "/data/sound/powerball.wav", t_sound);
|
|
|
|
// Texturas
|
|
Asset::get()->add(prefix + "/data/gfx/balloon1.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/balloon1.ani", t_data);
|
|
Asset::get()->add(prefix + "/data/gfx/balloon2.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/balloon2.ani", t_data);
|
|
Asset::get()->add(prefix + "/data/gfx/balloon3.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/balloon3.ani", t_data);
|
|
Asset::get()->add(prefix + "/data/gfx/balloon4.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/balloon4.ani", t_data);
|
|
Asset::get()->add(prefix + "/data/gfx/bullet.png", t_bitmap);
|
|
|
|
Asset::get()->add(prefix + "/data/gfx/game_buildings.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/game_clouds.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/game_grass.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/game_power_meter.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/game_sky_colors.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/game_text.png", t_bitmap);
|
|
|
|
Asset::get()->add(prefix + "/data/gfx/intro.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/logo.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/menu_game_over.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/menu_game_over_end.png", t_bitmap);
|
|
|
|
Asset::get()->add(prefix + "/data/gfx/item_points1_disk.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/item_points1_disk.ani", t_data);
|
|
Asset::get()->add(prefix + "/data/gfx/item_points2_gavina.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/item_points2_gavina.ani", t_data);
|
|
Asset::get()->add(prefix + "/data/gfx/item_points3_pacmar.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/item_points3_pacmar.ani", t_data);
|
|
Asset::get()->add(prefix + "/data/gfx/item_clock.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/item_clock.ani", t_data);
|
|
Asset::get()->add(prefix + "/data/gfx/item_coffee.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/item_coffee.ani", t_data);
|
|
Asset::get()->add(prefix + "/data/gfx/item_coffee_machine.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/item_coffee_machine.ani", t_data);
|
|
|
|
Asset::get()->add(prefix + "/data/gfx/title_bg_tile.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/title_coffee.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/title_crisis.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/title_dust.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/title_dust.ani", t_data);
|
|
Asset::get()->add(prefix + "/data/gfx/title_gradient.png", t_bitmap);
|
|
|
|
Asset::get()->add(prefix + "/data/gfx/player_head.ani", t_data);
|
|
Asset::get()->add(prefix + "/data/gfx/player_body.ani", t_data);
|
|
Asset::get()->add(prefix + "/data/gfx/player_legs.ani", t_data);
|
|
Asset::get()->add(prefix + "/data/gfx/player_death.ani", t_data);
|
|
Asset::get()->add(prefix + "/data/gfx/player_fire.ani", t_data);
|
|
|
|
Asset::get()->add(prefix + "/data/gfx/player_bal1_head.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/player_bal1_body.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/player_bal1_legs.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/player_bal1_death.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/player_bal1_fire.png", t_bitmap);
|
|
|
|
Asset::get()->add(prefix + "/data/gfx/player_arounder_head.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/player_arounder_body.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/player_arounder_legs.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/player_arounder_death.png", t_bitmap);
|
|
Asset::get()->add(prefix + "/data/gfx/player_arounder_fire.png", t_bitmap);
|
|
|
|
// Fuentes
|
|
Asset::get()->add(prefix + "/data/font/8bithud.png", t_font);
|
|
Asset::get()->add(prefix + "/data/font/8bithud.txt", t_font);
|
|
Asset::get()->add(prefix + "/data/font/nokia.png", t_font);
|
|
Asset::get()->add(prefix + "/data/font/nokia_big2.png", t_font);
|
|
Asset::get()->add(prefix + "/data/font/nokia.txt", t_font);
|
|
Asset::get()->add(prefix + "/data/font/nokia2.png", t_font);
|
|
Asset::get()->add(prefix + "/data/font/nokia2.txt", t_font);
|
|
Asset::get()->add(prefix + "/data/font/nokia_big2.txt", t_font);
|
|
Asset::get()->add(prefix + "/data/font/smb2_big.png", t_font);
|
|
Asset::get()->add(prefix + "/data/font/smb2_big.txt", t_font);
|
|
Asset::get()->add(prefix + "/data/font/smb2.png", t_font);
|
|
Asset::get()->add(prefix + "/data/font/smb2.txt", t_font);
|
|
|
|
// Textos
|
|
Asset::get()->add(prefix + "/data/lang/es_ES.txt", t_lang);
|
|
Asset::get()->add(prefix + "/data/lang/en_UK.txt", t_lang);
|
|
Asset::get()->add(prefix + "/data/lang/ba_BA.txt", t_lang);
|
|
|
|
// Menus
|
|
Asset::get()->add(prefix + "/data/menu/title.men", t_data);
|
|
Asset::get()->add(prefix + "/data/menu/title_gc.men", t_data);
|
|
Asset::get()->add(prefix + "/data/menu/options.men", t_data);
|
|
Asset::get()->add(prefix + "/data/menu/options_gc.men", t_data);
|
|
Asset::get()->add(prefix + "/data/menu/pause.men", t_data);
|
|
Asset::get()->add(prefix + "/data/menu/gameover.men", t_data);
|
|
Asset::get()->add(prefix + "/data/menu/player_select.men", t_data);
|
|
|
|
return Asset::get()->check();
|
|
}
|
|
|
|
// Comprueba los parametros del programa
|
|
void Director::checkProgramArguments(int argc, const char *argv[]) {
|
|
// Establece la ruta del programa
|
|
executablePath = argv[0];
|
|
|
|
// Comprueba el resto de parametros
|
|
for (int i = 1; i < argc; ++i) {
|
|
if (strcmp(argv[i], "--console") == 0) {
|
|
Options::settings.console = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Crea la carpeta del sistema donde guardar datos
|
|
void Director::createSystemFolder(const std::string &folder) {
|
|
#ifdef __EMSCRIPTEN__
|
|
// En Emscripten usamos una carpeta en MEMFS (no persistente)
|
|
systemFolder = "/config/" + folder;
|
|
#elif _WIN32
|
|
systemFolder = std::string(getenv("APPDATA")) + "/" + folder;
|
|
#elif __APPLE__
|
|
struct passwd *pw = getpwuid(getuid());
|
|
const char *homedir = pw->pw_dir;
|
|
systemFolder = std::string(homedir) + "/Library/Application Support" + "/" + folder;
|
|
#elif __linux__
|
|
struct passwd *pw = getpwuid(getuid());
|
|
const char *homedir = pw->pw_dir;
|
|
systemFolder = 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
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
// En Emscripten no necesitamos crear carpetas (MEMFS las crea automáticamente)
|
|
(void)folder;
|
|
#else
|
|
struct stat st = {0};
|
|
if (stat(systemFolder.c_str(), &st) == -1) {
|
|
errno = 0;
|
|
#ifdef _WIN32
|
|
int ret = mkdir(systemFolder.c_str());
|
|
#else
|
|
int ret = mkdir(systemFolder.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);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Gestiona las transiciones entre secciones
|
|
void Director::handleSectionTransition() {
|
|
// Determina qué sección debería estar activa
|
|
ActiveSection targetSection = ActiveSection::None;
|
|
switch (section->name) {
|
|
case SECTION_PROG_LOGO:
|
|
targetSection = ActiveSection::Logo;
|
|
break;
|
|
case SECTION_PROG_INTRO:
|
|
targetSection = ActiveSection::Intro;
|
|
break;
|
|
case SECTION_PROG_TITLE:
|
|
targetSection = ActiveSection::Title;
|
|
break;
|
|
case SECTION_PROG_GAME:
|
|
targetSection = ActiveSection::Game;
|
|
break;
|
|
}
|
|
|
|
// Si no ha cambiado, no hay nada que hacer
|
|
if (targetSection == activeSection) return;
|
|
|
|
// Destruye la sección anterior
|
|
logo.reset();
|
|
intro.reset();
|
|
title.reset();
|
|
game.reset();
|
|
|
|
// Crea la nueva sección
|
|
activeSection = targetSection;
|
|
switch (activeSection) {
|
|
case ActiveSection::Logo:
|
|
logo = std::make_unique<Logo>(renderer, section);
|
|
break;
|
|
case ActiveSection::Intro:
|
|
intro = std::make_unique<Intro>(renderer, section);
|
|
break;
|
|
case ActiveSection::Title:
|
|
title = std::make_unique<Title>(renderer, section);
|
|
break;
|
|
case ActiveSection::Game: {
|
|
const int numPlayers = section->subsection == SUBSECTION_GAME_PLAY_1P ? 1 : 2;
|
|
game = std::make_unique<Game>(numPlayers, 0, renderer, false, section);
|
|
break;
|
|
}
|
|
case ActiveSection::None:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Ejecuta un frame del juego
|
|
SDL_AppResult Director::iterate() {
|
|
#ifdef __EMSCRIPTEN__
|
|
// En WASM no se puede salir: reinicia al logo
|
|
if (section->name == SECTION_PROG_QUIT) {
|
|
section->name = SECTION_PROG_LOGO;
|
|
}
|
|
#else
|
|
if (section->name == SECTION_PROG_QUIT) {
|
|
return SDL_APP_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
// Actualiza la visibilidad del cursor del ratón
|
|
Mouse::updateCursorVisibility(Options::video.fullscreen);
|
|
|
|
// Gestiona las transiciones entre secciones
|
|
handleSectionTransition();
|
|
|
|
// Ejecuta un frame de la sección activa
|
|
switch (activeSection) {
|
|
case ActiveSection::Logo:
|
|
logo->iterate();
|
|
break;
|
|
case ActiveSection::Intro:
|
|
intro->iterate();
|
|
break;
|
|
case ActiveSection::Title:
|
|
title->iterate();
|
|
break;
|
|
case ActiveSection::Game:
|
|
game->iterate();
|
|
break;
|
|
case ActiveSection::None:
|
|
break;
|
|
}
|
|
|
|
return SDL_APP_CONTINUE;
|
|
}
|
|
|
|
// Procesa un evento
|
|
SDL_AppResult Director::handleEvent(SDL_Event *event) {
|
|
#ifndef __EMSCRIPTEN__
|
|
// Evento de salida de la aplicación
|
|
if (event->type == SDL_EVENT_QUIT) {
|
|
section->name = SECTION_PROG_QUIT;
|
|
return SDL_APP_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
// Hot-plug de mandos
|
|
if (event->type == SDL_EVENT_GAMEPAD_ADDED) {
|
|
std::string name;
|
|
if (Input::get()->handleGamepadAdded(event->gdevice.which, name)) {
|
|
Screen::get()->notify(name + " " + Lang::get()->getText(94),
|
|
color_t{0x40, 0xFF, 0x40},
|
|
color_t{0, 0, 0},
|
|
2500);
|
|
}
|
|
} else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) {
|
|
std::string name;
|
|
if (Input::get()->handleGamepadRemoved(event->gdevice.which, name)) {
|
|
Screen::get()->notify(name + " " + Lang::get()->getText(95),
|
|
color_t{0xFF, 0x50, 0x50},
|
|
color_t{0, 0, 0},
|
|
2500);
|
|
}
|
|
}
|
|
|
|
// Gestiona la visibilidad del cursor según el movimiento del ratón
|
|
Mouse::handleEvent(*event, Options::video.fullscreen);
|
|
|
|
// Reenvía el evento a la sección activa
|
|
switch (activeSection) {
|
|
case ActiveSection::Logo:
|
|
logo->handleEvent(event);
|
|
break;
|
|
case ActiveSection::Intro:
|
|
intro->handleEvent(event);
|
|
break;
|
|
case ActiveSection::Title:
|
|
title->handleEvent(event);
|
|
break;
|
|
case ActiveSection::Game:
|
|
game->handleEvent(event);
|
|
break;
|
|
case ActiveSection::None:
|
|
break;
|
|
}
|
|
|
|
return SDL_APP_CONTINUE;
|
|
}
|