684 lines
27 KiB
C++
684 lines
27 KiB
C++
#include "core/system/director.h"
|
|
|
|
#include <SDL3/SDL.h>
|
|
|
|
#include <cerrno> // for errno, EEXIST, EACCES, ENAMETOO...
|
|
#include <cstdio> // for printf, perror
|
|
#include <cstring> // 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 <iostream> // for cout
|
|
#include <memory>
|
|
#include <string> // for basic_string, operator+, char_t...
|
|
|
|
#include "core/audio/audio.hpp" // for Audio::init, Audio::destroy
|
|
#include "core/input/global_inputs.hpp" // for GlobalInputs::wantsQuit
|
|
#include "core/input/input.h" // for Input, InputAction
|
|
#include "core/input/mouse.hpp" // for Mouse::handleEvent, Mouse::upda...
|
|
#include "core/locale/lang.h" // for Lang, Lang::Code
|
|
#include "core/rendering/notifications.hpp" // for Notifications::show
|
|
#include "core/rendering/screen.h" // for Screen
|
|
#include "core/rendering/texture.h" // for Texture
|
|
#include "core/resources/asset.h" // for Asset, Asset::Type
|
|
#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 InputDevice, boolToString
|
|
|
|
#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)
|
|
#include <pwd.h>
|
|
#endif
|
|
|
|
// Constructor
|
|
Director::Director(int argc, const char *argv[]) {
|
|
std::cout << "Game start" << '\n';
|
|
// Inicializa variables
|
|
section_ = new Section();
|
|
section_->name = SECTION_PROG_LOGO;
|
|
|
|
// Inicializa las opciones del programa (defaults + dispositivos d'entrada)
|
|
Options::init();
|
|
|
|
// Obtén la ruta del directori on viu l'executable (acabada amb '/').
|
|
// SDL_GetBasePath és independent del CWD i evita el `argv[0]` poc fiable.
|
|
#ifdef __EMSCRIPTEN__
|
|
// En Emscripten els assets viuen a l'arrel del MEMFS — no hi ha ruta real.
|
|
executablePath = "";
|
|
#else
|
|
const char *base_path = SDL_GetBasePath();
|
|
executable_path_ = (base_path != nullptr) ? base_path : "";
|
|
#endif
|
|
|
|
// 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(system_folder_ + "/config.yaml");
|
|
Options::loadFromFile();
|
|
|
|
// Presets de shaders (creats amb defaults si no existeixen).
|
|
Options::setPostFXFile(system_folder_ + "/postfx.yaml");
|
|
Options::loadPostFXFromFile();
|
|
Options::setCrtPiFile(system_folder_ + "/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
|
|
#ifdef MACOS_BUNDLE
|
|
const std::string PACK_PATH = executablePath + "../Resources/resources.pack";
|
|
#else
|
|
const std::string PACK_PATH = executable_path_ + "resources.pack";
|
|
#endif
|
|
if (!ResourceHelper::initializeResourceSystem(PACK_PATH, ENABLE_FALLBACK)) {
|
|
std::cerr << "Fatal: resource system init failed (missing resources.pack?)" << '\n';
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
// Crea el objeto que controla los ficheros de recursos
|
|
Asset::init(executable_path_);
|
|
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 BIN_DIR = std::filesystem::path(executable_path_).parent_path().string();
|
|
#ifdef MACOS_BUNDLE
|
|
Input::init(BIN_DIR + "/../Resources/gamecontrollerdb.txt");
|
|
#else
|
|
Input::init(BIN_DIR + "/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();
|
|
|
|
active_section_ = 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_;
|
|
|
|
Audio::destroy();
|
|
|
|
SDL_DestroyRenderer(renderer_);
|
|
SDL_DestroyWindow(window_);
|
|
|
|
SDL_Quit();
|
|
|
|
ResourceHelper::shutdownResourceSystem();
|
|
|
|
std::cout << "\nBye!" << '\n';
|
|
}
|
|
|
|
// 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::Action::UP, SDL_SCANCODE_UP);
|
|
Input::get()->bindKey(Input::Action::DOWN, SDL_SCANCODE_DOWN);
|
|
Input::get()->bindKey(Input::Action::LEFT, SDL_SCANCODE_LEFT);
|
|
Input::get()->bindKey(Input::Action::RIGHT, SDL_SCANCODE_RIGHT);
|
|
Input::get()->bindKey(Input::Action::FIRE_LEFT, SDL_SCANCODE_Q);
|
|
Input::get()->bindKey(Input::Action::FIRE_CENTER, SDL_SCANCODE_W);
|
|
Input::get()->bindKey(Input::Action::FIRE_RIGHT, SDL_SCANCODE_E);
|
|
|
|
// Teclado - Otros
|
|
Input::get()->bindKey(Input::Action::ACCEPT, SDL_SCANCODE_RETURN);
|
|
// ESC només dispara EXIT (gestionat globalment per GlobalInputs com a
|
|
// confirmació de doble pulsació). PAUSE i CANCEL tenen tecles dedicades
|
|
// perquè cap escena ha de tractar ESC localment.
|
|
Input::get()->bindKey(Input::Action::EXIT, SDL_SCANCODE_ESCAPE);
|
|
Input::get()->bindKey(Input::Action::CANCEL, SDL_SCANCODE_BACKSPACE);
|
|
Input::get()->bindKey(Input::Action::PAUSE, SDL_SCANCODE_F12);
|
|
Input::get()->bindKey(Input::Action::WINDOW_DEC_ZOOM, SDL_SCANCODE_F1);
|
|
Input::get()->bindKey(Input::Action::WINDOW_INC_ZOOM, SDL_SCANCODE_F2);
|
|
Input::get()->bindKey(Input::Action::WINDOW_FULLSCREEN, SDL_SCANCODE_F3);
|
|
Input::get()->bindKey(Input::Action::TOGGLE_SHADER, SDL_SCANCODE_F4);
|
|
Input::get()->bindKey(Input::Action::TOGGLE_SHADER_TYPE, SDL_SCANCODE_F5);
|
|
Input::get()->bindKey(Input::Action::NEXT_SHADER_PRESET, SDL_SCANCODE_F6);
|
|
|
|
// Mando - Movimiento del jugador
|
|
Input::get()->bindGameControllerButton(Input::Action::UP, SDL_GAMEPAD_BUTTON_DPAD_UP);
|
|
Input::get()->bindGameControllerButton(Input::Action::DOWN, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
|
|
Input::get()->bindGameControllerButton(Input::Action::LEFT, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
|
|
Input::get()->bindGameControllerButton(Input::Action::RIGHT, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
|
|
Input::get()->bindGameControllerButton(Input::Action::FIRE_LEFT, SDL_GAMEPAD_BUTTON_WEST);
|
|
Input::get()->bindGameControllerButton(Input::Action::FIRE_CENTER, SDL_GAMEPAD_BUTTON_NORTH);
|
|
Input::get()->bindGameControllerButton(Input::Action::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::Action::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::Action::PAUSE, SDL_GAMEPAD_BUTTON_START);
|
|
Input::get()->bindGameControllerButton(Input::Action::EXIT, SDL_GAMEPAD_BUTTON_BACK);
|
|
#endif
|
|
}
|
|
|
|
// Inicializa JailAudio
|
|
void Director::initJailAudio() {
|
|
Audio::init();
|
|
}
|
|
|
|
// Arranca SDL y crea la ventana
|
|
auto Director::initSDL() -> bool {
|
|
// 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() << '\n';
|
|
}
|
|
success = false;
|
|
} else {
|
|
// Inicia el generador de numeros aleatorios
|
|
std::srand(static_cast<unsigned int>(SDL_GetTicks()));
|
|
|
|
// Calcula el zoom màxim windowed segons el display actual i clampa
|
|
// `Options::window.zoom` abans de crear la finestra.
|
|
Screen::detectMaxZoom();
|
|
|
|
// 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() << '\n';
|
|
}
|
|
success = false;
|
|
} else {
|
|
SDL_SetWindowPosition(window_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
|
|
|
// Crea un renderizador para la ventana
|
|
renderer_ = SDL_CreateRenderer(window_, nullptr);
|
|
|
|
if (renderer_ == nullptr) {
|
|
if (Options::settings.console) {
|
|
std::cout << "Renderer could not be created!\nSDL Error: " << SDL_GetError() << '\n';
|
|
}
|
|
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 << '\n';
|
|
}
|
|
return success;
|
|
}
|
|
|
|
// Crea el indice de ficheros
|
|
auto Director::setFileList() -> bool {
|
|
#ifdef MACOS_BUNDLE
|
|
const std::string PREFIX = "/../Resources";
|
|
#else
|
|
const std::string PREFIX;
|
|
#endif
|
|
|
|
// Ficheros de configuración
|
|
Asset::get()->add(system_folder_ + "/score.bin", Asset::Type::DATA, false, true);
|
|
Asset::get()->add(PREFIX + "/data/demo/demo1.bin", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/demo/demo2.bin", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/demo/demo3.bin", Asset::Type::DATA);
|
|
|
|
// Musicas
|
|
Asset::get()->add(PREFIX + "/data/music/intro.ogg", Asset::Type::MUSIC);
|
|
Asset::get()->add(PREFIX + "/data/music/playing.ogg", Asset::Type::MUSIC);
|
|
Asset::get()->add(PREFIX + "/data/music/title.ogg", Asset::Type::MUSIC);
|
|
|
|
// Sonidos
|
|
Asset::get()->add(PREFIX + "/data/sound/balloon.wav", Asset::Type::SOUND);
|
|
Asset::get()->add(PREFIX + "/data/sound/bubble1.wav", Asset::Type::SOUND);
|
|
Asset::get()->add(PREFIX + "/data/sound/bubble2.wav", Asset::Type::SOUND);
|
|
Asset::get()->add(PREFIX + "/data/sound/bubble3.wav", Asset::Type::SOUND);
|
|
Asset::get()->add(PREFIX + "/data/sound/bubble4.wav", Asset::Type::SOUND);
|
|
Asset::get()->add(PREFIX + "/data/sound/bullet.wav", Asset::Type::SOUND);
|
|
Asset::get()->add(PREFIX + "/data/sound/coffeeout.wav", Asset::Type::SOUND);
|
|
Asset::get()->add(PREFIX + "/data/sound/hiscore.wav", Asset::Type::SOUND);
|
|
Asset::get()->add(PREFIX + "/data/sound/itemdrop.wav", Asset::Type::SOUND);
|
|
Asset::get()->add(PREFIX + "/data/sound/itempickup.wav", Asset::Type::SOUND);
|
|
Asset::get()->add(PREFIX + "/data/sound/menu_move.wav", Asset::Type::SOUND);
|
|
Asset::get()->add(PREFIX + "/data/sound/menu_select.wav", Asset::Type::SOUND);
|
|
Asset::get()->add(PREFIX + "/data/sound/player_collision.wav", Asset::Type::SOUND);
|
|
Asset::get()->add(PREFIX + "/data/sound/stage_change.wav", Asset::Type::SOUND);
|
|
Asset::get()->add(PREFIX + "/data/sound/title.wav", Asset::Type::SOUND);
|
|
Asset::get()->add(PREFIX + "/data/sound/clock.wav", Asset::Type::SOUND);
|
|
Asset::get()->add(PREFIX + "/data/sound/powerball.wav", Asset::Type::SOUND);
|
|
|
|
// Texturas
|
|
Asset::get()->add(PREFIX + "/data/gfx/balloon1.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/balloon1.ani", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/gfx/balloon2.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/balloon2.ani", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/gfx/balloon3.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/balloon3.ani", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/gfx/balloon4.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/balloon4.ani", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/gfx/bullet.png", Asset::Type::BITMAP);
|
|
|
|
Asset::get()->add(PREFIX + "/data/gfx/game_buildings.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/game_clouds.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/game_grass.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/game_power_meter.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/game_sky_colors.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/game_text.png", Asset::Type::BITMAP);
|
|
|
|
Asset::get()->add(PREFIX + "/data/gfx/intro.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/logo.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/menu_game_over.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/menu_game_over_end.png", Asset::Type::BITMAP);
|
|
|
|
Asset::get()->add(PREFIX + "/data/gfx/item_points1_disk.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/item_points1_disk.ani", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/gfx/item_points2_gavina.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/item_points2_gavina.ani", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/gfx/item_points3_pacmar.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/item_points3_pacmar.ani", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/gfx/item_clock.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/item_clock.ani", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/gfx/item_coffee.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/item_coffee.ani", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/gfx/item_coffee_machine.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/item_coffee_machine.ani", Asset::Type::DATA);
|
|
|
|
Asset::get()->add(PREFIX + "/data/gfx/title_bg_tile.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/title_coffee.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/title_crisis.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/title_dust.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/title_dust.ani", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/gfx/title_gradient.png", Asset::Type::BITMAP);
|
|
|
|
Asset::get()->add(PREFIX + "/data/gfx/player_head.ani", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/gfx/player_body.ani", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/gfx/player_legs.ani", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/gfx/player_death.ani", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/gfx/player_fire.ani", Asset::Type::DATA);
|
|
|
|
Asset::get()->add(PREFIX + "/data/gfx/player_bal1_head.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/player_bal1_body.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/player_bal1_legs.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/player_bal1_death.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/player_bal1_fire.png", Asset::Type::BITMAP);
|
|
|
|
Asset::get()->add(PREFIX + "/data/gfx/player_arounder_head.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/player_arounder_body.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/player_arounder_legs.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/player_arounder_death.png", Asset::Type::BITMAP);
|
|
Asset::get()->add(PREFIX + "/data/gfx/player_arounder_fire.png", Asset::Type::BITMAP);
|
|
|
|
// Fuentes
|
|
Asset::get()->add(PREFIX + "/data/font/8bithud.png", Asset::Type::FONT);
|
|
Asset::get()->add(PREFIX + "/data/font/8bithud.txt", Asset::Type::FONT);
|
|
Asset::get()->add(PREFIX + "/data/font/nokia.png", Asset::Type::FONT);
|
|
Asset::get()->add(PREFIX + "/data/font/nokia_big2.png", Asset::Type::FONT);
|
|
Asset::get()->add(PREFIX + "/data/font/nokia.txt", Asset::Type::FONT);
|
|
Asset::get()->add(PREFIX + "/data/font/nokia2.png", Asset::Type::FONT);
|
|
Asset::get()->add(PREFIX + "/data/font/nokia2.txt", Asset::Type::FONT);
|
|
Asset::get()->add(PREFIX + "/data/font/nokia_big2.txt", Asset::Type::FONT);
|
|
Asset::get()->add(PREFIX + "/data/font/smb2_big.png", Asset::Type::FONT);
|
|
Asset::get()->add(PREFIX + "/data/font/smb2_big.txt", Asset::Type::FONT);
|
|
Asset::get()->add(PREFIX + "/data/font/smb2.png", Asset::Type::FONT);
|
|
Asset::get()->add(PREFIX + "/data/font/smb2.txt", Asset::Type::FONT);
|
|
|
|
// Textos
|
|
Asset::get()->add(PREFIX + "/data/lang/es_ES.txt", Asset::Type::LANG);
|
|
Asset::get()->add(PREFIX + "/data/lang/en_UK.txt", Asset::Type::LANG);
|
|
Asset::get()->add(PREFIX + "/data/lang/ba_BA.txt", Asset::Type::LANG);
|
|
|
|
// Menus
|
|
Asset::get()->add(PREFIX + "/data/menu/title.men", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/menu/title_gc.men", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/menu/options.men", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/menu/options_gc.men", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/menu/pause.men", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/menu/gameover.men", Asset::Type::DATA);
|
|
Asset::get()->add(PREFIX + "/data/menu/player_select.men", Asset::Type::DATA);
|
|
|
|
return Asset::get()->check();
|
|
}
|
|
|
|
// Comprueba los parametros del programa
|
|
void Director::checkProgramArguments(int argc, const char *argv[]) {
|
|
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;
|
|
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
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
// En Emscripten no necesitamos crear carpetas (MEMFS las crea automáticamente)
|
|
(void)folder;
|
|
#else
|
|
struct stat st{};
|
|
if (stat(system_folder_.c_str(), &st) == -1) {
|
|
errno = 0;
|
|
#ifdef _WIN32
|
|
int ret = mkdir(systemFolder.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);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Gestiona las transiciones entre secciones
|
|
void Director::handleSectionTransition() {
|
|
// Determina qué sección debería estar activa
|
|
ActiveSection target_section = ActiveSection::NONE;
|
|
switch (section_->name) {
|
|
case SECTION_PROG_LOGO:
|
|
target_section = ActiveSection::LOGO;
|
|
break;
|
|
case SECTION_PROG_INTRO:
|
|
target_section = ActiveSection::INTRO;
|
|
break;
|
|
case SECTION_PROG_TITLE:
|
|
target_section = ActiveSection::TITLE;
|
|
break;
|
|
case SECTION_PROG_GAME:
|
|
target_section = ActiveSection::GAME;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Si no ha cambiado, no hay nada que hacer
|
|
if (target_section == active_section_) {
|
|
return;
|
|
}
|
|
|
|
// Destruye la sección anterior
|
|
logo_.reset();
|
|
intro_.reset();
|
|
title_.reset();
|
|
game_.reset();
|
|
|
|
// Crea la nueva sección
|
|
active_section_ = target_section;
|
|
switch (active_section_) {
|
|
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 NUM_PLAYERS = section_->subsection == SUBSECTION_GAME_PLAY_1P ? 1 : 2;
|
|
game_ = std::make_unique<Game>(NUM_PLAYERS, 0, renderer_, false, section_);
|
|
break;
|
|
}
|
|
case ActiveSection::NONE:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Ejecuta un frame del juego
|
|
auto Director::iterate() -> SDL_AppResult {
|
|
#ifndef __EMSCRIPTEN__
|
|
// Doble pulsació d'ESC confirmada des de qualsevol escena.
|
|
if (GlobalInputs::wantsQuit()) {
|
|
section_->name = SECTION_PROG_QUIT;
|
|
}
|
|
#endif
|
|
|
|
#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 (active_section_) {
|
|
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
|
|
auto Director::handleEvent(SDL_Event *event) -> SDL_AppResult {
|
|
#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)) {
|
|
Notifications::show(name + " " + Lang::get()->getText(94),
|
|
Notifications::Palette::SUCCESS,
|
|
Notifications::LONG_MS);
|
|
}
|
|
} else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) {
|
|
std::string name;
|
|
if (Input::get()->handleGamepadRemoved(event->gdevice.which, name)) {
|
|
Notifications::show(name + " " + Lang::get()->getText(95),
|
|
Notifications::Palette::DANGER,
|
|
Notifications::LONG_MS);
|
|
}
|
|
}
|
|
|
|
// 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 (active_section_) {
|
|
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;
|
|
}
|