Files
coffee_crisis/source/core/system/director.cpp
2026-04-17 21:27:30 +02:00

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;
}