build d'emscripten

This commit is contained in:
2026-04-17 10:00:37 +02:00
parent 6ea50cf35e
commit 5eb178b039
12 changed files with 316 additions and 44 deletions

View File

@@ -136,11 +136,27 @@ set(DEBUG_SOURCES
)
# Configuración de SDL3
if(EMSCRIPTEN)
# En Emscripten, SDL3 se compila desde source con FetchContent
include(FetchContent)
FetchContent_Declare(
SDL3
GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
GIT_TAG release-3.4.4
GIT_SHALLOW TRUE
)
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
set(SDL_STATIC ON CACHE BOOL "" FORCE)
set(SDL_TEST_LIBRARY OFF CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(SDL3)
message(STATUS "SDL3 compilado desde source para Emscripten")
else()
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
endif()
# --- SHADER COMPILATION (Linux/Windows only - macOS uses Metal) ---
if(NOT APPLE)
# --- SHADER COMPILATION (Linux/Windows only - macOS uses Metal, Emscripten no los necesita) ---
if(NOT APPLE AND NOT EMSCRIPTEN)
find_program(GLSLC_EXE NAMES glslc)
set(SHADERS_DIR "${CMAKE_SOURCE_DIR}/data/shaders")
@@ -205,10 +221,15 @@ else()
endif()
# --- 2. AÑADIR EJECUTABLE ---
if(EMSCRIPTEN)
# En Emscripten no compilamos sdl3gpu_shader (SDL3 GPU no está soportado en WebGL2)
add_executable(${PROJECT_NAME} ${APP_SOURCES})
else()
add_executable(${PROJECT_NAME} ${APP_SOURCES} ${RENDERING_SOURCES})
endif()
# Shaders deben compilarse antes que el ejecutable (Linux/Windows con glslc)
if(NOT APPLE AND GLSLC_EXE)
if(NOT APPLE AND NOT EMSCRIPTEN AND GLSLC_EXE)
add_dependencies(${PROJECT_NAME} shaders)
endif()
@@ -252,12 +273,32 @@ elseif(APPLE)
-rpath @executable_path/../Frameworks/
)
endif()
elseif(EMSCRIPTEN)
target_compile_definitions(${PROJECT_NAME} PRIVATE EMSCRIPTEN_BUILD)
# -fexceptions: habilita excepciones C++ (fkyaml, std::runtime_error...) — sin esto cualquier throw llama a abort()
target_compile_options(${PROJECT_NAME} PRIVATE -fexceptions)
target_link_options(${PROJECT_NAME} PRIVATE
"SHELL:--preload-file ${CMAKE_SOURCE_DIR}/data@/data"
"SHELL:--preload-file ${CMAKE_SOURCE_DIR}/config@/config"
"SHELL:--preload-file ${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt@/gamecontrollerdb.txt"
-fexceptions
-sALLOW_MEMORY_GROWTH=1
-sMAX_WEBGL_VERSION=2
-sINITIAL_MEMORY=67108864
-sASSERTIONS=1
# ASYNCIFY solo para permitir emscripten_sleep(0) durante la precarga de recursos
# (el bucle principal del juego ya usa SDL3 Callback API, no depende de ASYNCIFY).
-sASYNCIFY=1
)
set_target_properties(${PROJECT_NAME} PROPERTIES SUFFIX ".html")
elseif(UNIX AND NOT APPLE)
target_compile_definitions(${PROJECT_NAME} PRIVATE LINUX_BUILD)
endif()
# Especificar la ubicación del ejecutable
# Especificar la ubicación del ejecutable (en desktop; en wasm queda en build/wasm/)
if(NOT EMSCRIPTEN)
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR})
endif()
# --- 5. STATIC ANALYSIS TARGETS ---
@@ -355,6 +396,9 @@ else()
endif()
# --- 6. PACK RESOURCES TARGETS ---
# En Emscripten no generamos resources.pack: los assets se embeben vía --preload-file
# (ver rama EMSCRIPTEN más arriba). El pack_tool tampoco tiene sentido bajo emcc.
if(NOT EMSCRIPTEN)
set(PACK_TOOL_SOURCES
${CMAKE_SOURCE_DIR}/tools/pack_resources/pack_resources.cpp
${CMAKE_SOURCE_DIR}/source/core/resources/resource_pack.cpp
@@ -380,3 +424,4 @@ add_custom_command(
add_custom_target(pack DEPENDS "${CMAKE_SOURCE_DIR}/resources.pack")
add_dependencies(${PROJECT_NAME} pack)
endif()

View File

@@ -283,6 +283,48 @@ linux_release:
# Elimina la carpeta temporal
$(RMDIR) "$(RELEASE_FOLDER)"
# ==============================================================================
# COMPILACIÓN PARA WEBASSEMBLY (requiere Docker)
# ==============================================================================
wasm:
@echo "Compilando para WebAssembly - Version: $(VERSION)"
docker run --rm \
--user $(shell id -u):$(shell id -g) \
-v $(DIR_ROOT):/src \
-w /src \
emscripten/emsdk:latest \
bash -c "emcmake cmake -S . -B build/wasm -DCMAKE_BUILD_TYPE=Release && cmake --build build/wasm"
$(MKDIR) "$(DIST_DIR)/wasm"
cp build/wasm/$(TARGET_NAME).html $(DIST_DIR)/wasm/
cp build/wasm/$(TARGET_NAME).js $(DIST_DIR)/wasm/
cp build/wasm/$(TARGET_NAME).wasm $(DIST_DIR)/wasm/
cp build/wasm/$(TARGET_NAME).data $(DIST_DIR)/wasm/
@echo "Output: $(DIST_DIR)/wasm/"
scp $(DIST_DIR)/wasm/$(TARGET_NAME).js $(DIST_DIR)/wasm/$(TARGET_NAME).wasm $(DIST_DIR)/wasm/$(TARGET_NAME).data \
maverick:/home/sergio/gitea/web_jailgames/static/games/projecte-2026/wasm/
ssh maverick 'cd /home/sergio/gitea/web_jailgames && ./deploy.sh'
@echo "Deployed to maverick"
# Versión Debug del build wasm: arranca directamente a la GAME (sin logo/loading/title)
# y activa el editor y la consola. Salida a dist/wasm_debug/.
wasm_debug:
@echo "Compilando WebAssembly Debug - Version: $(VERSION)"
docker run --rm \
-v $(DIR_ROOT):/src \
-w /src \
emscripten/emsdk:latest \
bash -c "emcmake cmake -S . -B build/wasm_debug -DCMAKE_BUILD_TYPE=Debug && cmake --build build/wasm_debug"
$(MKDIR) "$(DIST_DIR)/wasm_debug"
cp build/wasm_debug/$(TARGET_NAME).html $(DIST_DIR)/wasm_debug/
cp build/wasm_debug/$(TARGET_NAME).js $(DIST_DIR)/wasm_debug/
cp build/wasm_debug/$(TARGET_NAME).wasm $(DIST_DIR)/wasm_debug/
cp build/wasm_debug/$(TARGET_NAME).data $(DIST_DIR)/wasm_debug/
@echo "Output: $(DIST_DIR)/wasm_debug/"
scp $(DIST_DIR)/wasm_debug/$(TARGET_NAME).js $(DIST_DIR)/wasm_debug/$(TARGET_NAME).wasm $(DIST_DIR)/wasm_debug/$(TARGET_NAME).data \
maverick:/home/sergio/gitea/web_jailgames/static/games/projecte-2026/wasm/
ssh maverick 'cd /home/sergio/gitea/web_jailgames && ./deploy.sh'
@echo "Deployed to maverick"
# ==============================================================================
# REGLAS ESPECIALES
# ==============================================================================
@@ -310,8 +352,12 @@ help:
@echo " make pack_tool - Compilar herramienta de empaquetado"
@echo " make resources.pack - Generar pack de recursos desde data/"
@echo ""
@echo " WebAssembly (requiere Docker):"
@echo " make wasm - Compilar a WebAssembly (Release) y desplegar a maverick"
@echo " make wasm_debug - Compilar a WebAssembly (Debug) sin desplegar"
@echo ""
@echo " Otros:"
@echo " make show_version - Mostrar version actual ($(VERSION))"
@echo " make help - Mostrar esta ayuda"
.PHONY: all debug release windows_release macos_release linux_release compile_shaders pack_tool resources.pack show_version help
.PHONY: all debug release windows_release macos_release linux_release wasm wasm_debug compile_shaders pack_tool resources.pack show_version help

View File

@@ -94,6 +94,8 @@ ui:
integer_scale_disabled: "ESCALAT SENCER DESACTIVAT"
vsync_enabled: "V-SYNC ACTIVAT"
vsync_disabled: "V-SYNC DESACTIVAT"
gamepad_connected: "CONNECTAT"
gamepad_disconnected: "DESCONNECTAT"
scoreboard:
lives: "vides "

View File

@@ -94,6 +94,8 @@ ui:
integer_scale_disabled: "INTEGER SCALE DISABLED"
vsync_enabled: "V-SYNC ENABLED"
vsync_disabled: "V-SYNC DISABLED"
gamepad_connected: "CONNECTED"
gamepad_disconnected: "DISCONNECTED"
scoreboard:
lives: "lives "

View File

@@ -45,6 +45,11 @@ namespace GlobalInputs {
return;
}
#ifdef __EMSCRIPTEN__
// En la versión web no se puede salir del juego desde fuera de la escena GAME
// (el navegador gestiona la pestaña; Escape no cierra nada).
return;
#else
// Comportamiento normal fuera del modo kiosko
const std::string CODE = "PRESS AGAIN TO EXIT";
if (stringInVector(Notifier::get()->getCodes(), CODE)) {
@@ -52,6 +57,7 @@ namespace GlobalInputs {
} else {
Notifier::get()->show({Locale::get()->get("ui.press_again_exit")}, Notifier::Style::DEFAULT, -1, true, CODE);
}
#endif // __EMSCRIPTEN__
}
void handleSkipSection() {

View File

@@ -10,6 +10,40 @@
#include "game/options.hpp" // Para Options::controls
// Emscripten-only: SDL 3.4+ no casa el GUID de los mandos de Chrome Android
// con gamecontrollerdb (el gamepad.id de Android no lleva Vendor/Product, el
// parser extrae valores basura, el GUID resultante no está en la db y el
// gamepad queda abierto con un mapping incorrecto). Como la W3C Gamepad API
// garantiza el layout estándar cuando el navegador reporta mapping=="standard",
// inyectamos un mapping SDL con ese layout para el GUID del joystick antes
// de abrirlo como gamepad. Fuera de Emscripten es un no-op.
static void installWebStandardMapping(SDL_JoystickID jid) {
#ifdef __EMSCRIPTEN__
SDL_GUID guid = SDL_GetJoystickGUIDForID(jid);
char guidStr[33];
SDL_GUIDToString(guid, guidStr, sizeof(guidStr));
const char* name = SDL_GetJoystickNameForID(jid);
if ((name == nullptr) || (*name == 0)) { name = "Standard Gamepad"; }
char mapping[512];
SDL_snprintf(mapping, sizeof(mapping),
"%s,%s,"
"a:b0,b:b1,x:b2,y:b3,"
"leftshoulder:b4,rightshoulder:b5,"
"lefttrigger:b6,righttrigger:b7,"
"back:b8,start:b9,"
"leftstick:b10,rightstick:b11,"
"dpup:b12,dpdown:b13,dpleft:b14,dpright:b15,"
"guide:b16,"
"leftx:a0,lefty:a1,rightx:a2,righty:a3,"
"platform:Emscripten",
guidStr, name);
SDL_AddGamepadMapping(mapping);
#else
(void)jid;
#endif
}
// Singleton
Input* Input::instance = nullptr;
@@ -397,6 +431,7 @@ auto Input::handleEvent(const SDL_Event& event) -> std::string {
}
auto Input::addGamepad(int device_index) -> std::string {
installWebStandardMapping(device_index);
SDL_Gamepad* pad = SDL_OpenGamepad(device_index);
if (pad == nullptr) {
std::cerr << "Error al abrir el gamepad: " << SDL_GetError() << '\n';
@@ -407,7 +442,13 @@ auto Input::addGamepad(int device_index) -> std::string {
auto name = gamepad->name;
std::cout << "Gamepad connected (" << name << ")" << '\n';
gamepads_.push_back(std::move(gamepad));
return name + " CONNECTED";
// Aplica los bindings de Options al nuevo gamepad. En hot-plug (incluido wasm,
// donde el navegador sólo expone los gamepads tras activación del usuario) el
// ctor ya llamó a applyGamepadBindingsFromOptions() pero gamepads_ estaba vacío.
applyGamepadBindingsFromOptions();
return name;
}
auto Input::removeGamepad(SDL_JoystickID id) -> std::string {
@@ -419,7 +460,7 @@ auto Input::removeGamepad(SDL_JoystickID id) -> std::string {
std::string name = (*it)->name;
std::cout << "Gamepad disconnected (" << name << ")" << '\n';
gamepads_.erase(it);
return name + " DISCONNECTED";
return name;
}
std::cerr << "No se encontró el gamepad con ID " << id << '\n';
return {};

View File

@@ -51,10 +51,20 @@ class Input {
std::string path; // Ruta del dispositivo
std::unordered_map<Action, ButtonState> bindings; // Mapa de acciones a estados de botón
// Recorta el nombre del mando hasta el primer '(' o '[' y elimina espacios finales.
// Evita nombres como "Retroid Controller (vendor: 1001) ..." en las notificaciones.
static auto trimName(const char* raw) -> std::string {
std::string s(raw != nullptr ? raw : "");
const auto pos = s.find_first_of("([");
if (pos != std::string::npos) { s.erase(pos); }
while (!s.empty() && s.back() == ' ') { s.pop_back(); }
return s;
}
explicit Gamepad(SDL_Gamepad* gamepad)
: pad(gamepad),
instance_id(SDL_GetJoystickID(SDL_GetGamepadJoystick(gamepad))),
name(std::string(SDL_GetGamepadName(gamepad))),
name(trimName(SDL_GetGamepadName(gamepad))),
path(std::string(SDL_GetGamepadPath(pad))),
bindings{
// Movimiento del jugador

View File

@@ -1,6 +1,10 @@
#include "core/rendering/screen.hpp"
#include <SDL3/SDL.h>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/html5.h>
#endif
#include <algorithm> // Para max, min, transform
#include <cctype> // Para toupper
@@ -13,7 +17,9 @@
#include "core/input/mouse.hpp" // Para updateCursorVisibility
#include "core/rendering/render_info.hpp" // Para RenderInfo
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp" // Para SDL3GPUShader
#ifndef __EMSCRIPTEN__
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp" // Para SDL3GPUShader (no soportado en WebGL2)
#endif
#include "core/rendering/surface.hpp" // Para Surface, readPalFile
#include "core/rendering/text.hpp" // Para Text
#include "core/resources/resource_cache.hpp" // Para Resource
@@ -26,6 +32,38 @@
// [SINGLETON]
Screen* Screen::screen = nullptr;
#ifdef __EMSCRIPTEN__
// ============================================================================
// Restauración del canvas en wasm/Emscripten
// ============================================================================
// SDL3 + Emscripten no notifica de manera fiable los cambios de tamaño del
// canvas HTML (fullscreen exit con Esc, orientationchange). Registramos
// callbacks nativos de Emscripten que re-sincronizan SDL con el estado real
// del navegador delegando en Screen::handleCanvasResized() → setVideoMode().
// Se difiere con emscripten_async_call(0ms) porque cuando el event llega el
// canvas aún no está estable.
// Referencias:
// - https://github.com/libsdl-org/SDL/issues/13300
// - https://github.com/libsdl-org/SDL/issues/11389
// ============================================================================
namespace {
void deferredCanvasResize(void* /*user_data*/) {
if (Screen::get() != nullptr) { Screen::get()->handleCanvasResized(); }
}
auto onEmFullscreenChange(int /*event_type*/, const EmscriptenFullscreenChangeEvent* event, void* /*user_data*/) -> EM_BOOL {
Options::video.fullscreen = (event != nullptr && event->isFullscreen != 0);
emscripten_async_call(deferredCanvasResize, nullptr, 0);
return EM_FALSE;
}
auto onEmOrientationChange(int /*event_type*/, const EmscriptenOrientationChangeEvent* /*event*/, void* /*user_data*/) -> EM_BOOL {
emscripten_async_call(deferredCanvasResize, nullptr, 0);
return EM_FALSE;
}
} // namespace
#endif
// [SINGLETON] Crearemos el objeto con esta función estática
void Screen::init() {
Screen::screen = new Screen();
@@ -164,6 +202,16 @@ void Screen::toggleVideoMode() {
setVideoMode(Options::video.fullscreen);
}
// Re-sincroniza SDL con el estado real del canvas del navegador. Lo invocan los
// callbacks nativos de Emscripten cuando detectan un fullscreenchange u
// orientationchange. En desktop SDL emite SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED
// correctamente, así que fuera de emscripten es un no-op.
void Screen::handleCanvasResized() {
#ifdef __EMSCRIPTEN__
setVideoMode(Options::video.fullscreen);
#endif
}
// Reduce el tamaño de la ventana
auto Screen::decWindowZoom() -> bool {
if (static_cast<int>(Options::video.fullscreen) == 0) {
@@ -636,6 +684,10 @@ void Screen::nextShader() {
// El device GPU se crea siempre (independientemente de postfx) para evitar
// conflictos SDL_Renderer/SDL_GPU al hacer toggle F4 en Windows/Vulkan.
void Screen::initShaders() {
#ifdef __EMSCRIPTEN__
// En WebGL2 no hay SDL3 GPU; el render va por SDL_Renderer sin shaders.
shader_backend_.reset();
#else
SDL_Texture* tex = Options::video.border.enabled ? border_texture_ : game_texture_;
if (!shader_backend_) {
@@ -664,6 +716,7 @@ void Screen::initShaders() {
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
applyCurrentCrtPiPreset();
}
#endif
}
// Obtiene información sobre la pantalla
@@ -767,10 +820,24 @@ auto Screen::initSDLVideo() -> bool {
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
SDL_SetRenderVSync(renderer_, Options::video.vertical_sync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
registerEmscriptenEventCallbacks();
std::cout << "Video system initialized successfully\n";
return true;
}
// Registra los callbacks nativos de Emscripten que restauran el canvas cuando
// SDL3 no emite los events equivalentes. Fuera de Emscripten es un no-op.
void Screen::registerEmscriptenEventCallbacks() { // NOLINT(readability-convert-member-functions-to-static)
#ifdef __EMSCRIPTEN__
// NO registramos resize callback. En móvil, el scroll hace que el navegador
// oculte/muestre la barra de URL, disparando un resize del DOM por cada scroll,
// lo que nos llevaría a llamar setVideoMode innecesariamente.
emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_TRUE, onEmFullscreenChange);
emscripten_set_orientationchange_callback(nullptr, EM_TRUE, onEmOrientationChange);
#endif
}
// Crea el objeto de texto
void Screen::createText() {
// Carga la surface de la fuente directamente del archivo

View File

@@ -39,6 +39,7 @@ class Screen {
// Video y ventana
void setVideoMode(bool mode); // Establece el modo de video
void toggleVideoMode(); // Cambia entre pantalla completa y ventana
void handleCanvasResized(); // Restaura el canvas cuando SDL3 no reporta el cambio (emscripten only: salida de fullscreen con Esc, rotación); no-op fuera de emscripten
void toggleIntegerScale(); // Alterna entre activar y desactivar el escalado entero
void toggleVSync(); // Alterna entre activar y desactivar el V-Sync
auto decWindowZoom() -> bool; // Reduce el tamaño de la ventana
@@ -145,6 +146,7 @@ class Screen {
void applyCurrentCrtPiPreset(); // Aplica los parámetros del preset CrtPi actual al backend
void getDisplayInfo(); // Obtiene información sobre la pantalla
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
void registerEmscriptenEventCallbacks(); // Registra los callbacks nativos para restaurar el canvas en wasm (no-op fuera de emscripten)
void createText(); // Crea el objeto de texto
// Constructor y destructor

View File

@@ -38,7 +38,7 @@
#include "game/editor/map_editor.hpp" // Para MapEditor
#endif
#ifndef _WIN32
#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)
#include <pwd.h>
#endif
@@ -46,12 +46,17 @@
Director::Director() {
std::cout << "Game start" << '\n';
#ifdef __EMSCRIPTEN__
// En Emscripten los assets están en el root del filesystem virtual (/data, /config)
executable_path_ = "";
#else
// Obtiene la ruta del ejecutable
std::string base = SDL_GetBasePath();
if (!base.empty() && base.back() == '/') {
base.pop_back();
}
executable_path_ = base;
#endif
// Crea la carpeta del sistema donde guardar datos
createSystemFolder("jailgames");
@@ -81,7 +86,7 @@ Director::Director() {
// Preparar ruta al pack (en macOS bundle está en Contents/Resources/)
std::string pack_path = executable_path_ + PREFIX + "/resources.pack";
#ifdef RELEASE_BUILD
#if defined(RELEASE_BUILD) && !defined(__EMSCRIPTEN__)
// ============================================================
// RELEASE BUILD: Pack-first architecture
// ============================================================
@@ -139,6 +144,18 @@ Director::Director() {
Options::setConfigFile(Resource::List::get()->get("config.yaml"));
Options::loadFromFile();
#ifdef __EMSCRIPTEN__
// A la versión web el navegador gestiona la ventana: forzamos zoom x4
// para que la textura 256x192 no se vea minúscula en el canvas HTML,
// y activamos el borde para aprovechar al máximo el espacio del canvas.
Options::video.fullscreen = false;
Options::video.integer_scale = true;
Options::window.zoom = 4;
Options::video.border.enabled = true;
Options::video.border.height = 8;
Options::video.border.width = 8;
#endif
// Configura la ruta y carga los presets de PostFX
Options::setPostFXFile(Resource::List::get()->get("postfx.yaml"));
Options::loadPostFXFromFile();
@@ -191,7 +208,7 @@ Director::Director() {
KeyConfig::init("data/input/keys.yaml");
// Special handling for gamecontrollerdb.txt - SDL needs filesystem path
#ifdef RELEASE_BUILD
#if defined(RELEASE_BUILD) && !defined(__EMSCRIPTEN__)
// In release, construct the path manually (not from Asset which has empty executable_path)
std::string gamecontroller_db = executable_path_ + PREFIX + "/gamecontrollerdb.txt";
Input::init(gamecontroller_db);
@@ -215,7 +232,7 @@ Director::Director() {
std::cout << "\n"; // Fin de inicialización de sistemas
// Inicializa el sistema de localización (antes de Cheevos que usa textos traducidos)
#ifdef RELEASE_BUILD
#if defined(RELEASE_BUILD) && !defined(__EMSCRIPTEN__)
{
// En release el locale está en el pack, no en el filesystem
std::string locale_key = Resource::List::get()->get(Options::language + ".yaml");
@@ -228,7 +245,7 @@ Director::Director() {
#endif
// Special handling for cheevos.bin - also needs filesystem path
#ifdef RELEASE_BUILD
#if defined(RELEASE_BUILD) && !defined(__EMSCRIPTEN__)
std::string cheevos_path = system_folder_ + "/cheevos.bin";
Cheevos::init(cheevos_path);
#else
@@ -271,6 +288,12 @@ Director::~Director() {
// Crea la carpeta del sistema donde guardar datos
void Director::createSystemFolder(const std::string& folder) {
#ifdef __EMSCRIPTEN__
// En Emscripten utilizamos MEMFS (no persistente entre sesiones).
// No hace falta crear directorios: MEMFS los crea automáticamente al escribir.
system_folder_ = "/config/" + folder;
return;
#else
#ifdef _WIN32
system_folder_ = std::string(getenv("APPDATA")) + "/" + folder;
#elif __APPLE__
@@ -322,6 +345,7 @@ void Director::createSystemFolder(const std::string& folder) {
}
}
}
#endif // __EMSCRIPTEN__
}
// Carga la configuración de assets desde assets.yaml

View File

@@ -1,23 +1,45 @@
#include "core/system/global_events.hpp"
#include "core/input/input.hpp" // Para Input (gamepad add/remove)
#include "core/input/mouse.hpp"
#include "core/locale/locale.hpp" // Para Locale
#include "game/options.hpp" // Para Options, options, OptionsGame, OptionsAudio
#include "game/scene_manager.hpp" // Para SceneManager
#include "game/ui/console.hpp" // Para Console
#include "game/ui/notifier.hpp" // Para Notifier
namespace GlobalEvents {
// Comprueba los eventos que se pueden producir en cualquier sección del juego
void handle(const SDL_Event& event) {
// Evento de salida de la aplicación
#ifndef __EMSCRIPTEN__
// En la versión web no tenemos evento de quit del navegador
if (event.type == SDL_EVENT_QUIT) {
SceneManager::current = SceneManager::Scene::QUIT;
return;
}
#endif
if (event.type == SDL_EVENT_RENDER_DEVICE_RESET || event.type == SDL_EVENT_RENDER_TARGETS_RESET) {
// reLoadTextures();
}
// Conexión/desconexión de gamepads: hay que enrutarlos a Input para que
// añada el dispositivo a gamepads_. Sin esto, en wasm los gamepads
// nunca se detectan (la Gamepad API del navegador sólo los expone
// tras que el usuario los active, más tarde que el discoverGamepads
// inicial). En desktop también arregla la conexión en caliente.
if (event.type == SDL_EVENT_GAMEPAD_ADDED || event.type == SDL_EVENT_GAMEPAD_REMOVED) {
if (Input::get() != nullptr) {
std::string name = Input::get()->handleEvent(event);
if (!name.empty() && Notifier::get() != nullptr && Locale::get() != nullptr) {
const std::string KEY = (event.type == SDL_EVENT_GAMEPAD_ADDED)
? "ui.gamepad_connected"
: "ui.gamepad_disconnected";
Notifier::get()->show({name + " " + Locale::get()->get(KEY)});
}
}
}
// Enrutar eventos de texto a la consola cuando está activa
if (Console::get() != nullptr && Console::get()->isActive()) {
if (event.type == SDL_EVENT_TEXT_INPUT || event.type == SDL_EVENT_KEY_DOWN) {

View File

@@ -946,11 +946,16 @@ static auto cmdKiosk(const std::vector<std::string>& args) -> std::string {
// EXIT / QUIT
static auto cmdExit(const std::vector<std::string>& args) -> std::string {
#ifdef __EMSCRIPTEN__
(void)args;
return "Not allowed in web version";
#else
if (Options::kiosk.enabled && (args.empty() || args[0] != "PLEASE")) {
return "Not allowed in kiosk mode";
}
SceneManager::current = SceneManager::Scene::QUIT;
return "Quitting...";
#endif
}
// SIZE