build d'emscripten
This commit is contained in:
103
CMakeLists.txt
103
CMakeLists.txt
@@ -136,11 +136,27 @@ set(DEBUG_SOURCES
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Configuración de SDL3
|
# Configuración de SDL3
|
||||||
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
|
if(EMSCRIPTEN)
|
||||||
message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
|
# 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) ---
|
# --- SHADER COMPILATION (Linux/Windows only - macOS uses Metal, Emscripten no los necesita) ---
|
||||||
if(NOT APPLE)
|
if(NOT APPLE AND NOT EMSCRIPTEN)
|
||||||
find_program(GLSLC_EXE NAMES glslc)
|
find_program(GLSLC_EXE NAMES glslc)
|
||||||
|
|
||||||
set(SHADERS_DIR "${CMAKE_SOURCE_DIR}/data/shaders")
|
set(SHADERS_DIR "${CMAKE_SOURCE_DIR}/data/shaders")
|
||||||
@@ -205,10 +221,15 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# --- 2. AÑADIR EJECUTABLE ---
|
# --- 2. AÑADIR EJECUTABLE ---
|
||||||
add_executable(${PROJECT_NAME} ${APP_SOURCES} ${RENDERING_SOURCES})
|
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)
|
# 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)
|
add_dependencies(${PROJECT_NAME} shaders)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -252,12 +273,32 @@ elseif(APPLE)
|
|||||||
-rpath @executable_path/../Frameworks/
|
-rpath @executable_path/../Frameworks/
|
||||||
)
|
)
|
||||||
endif()
|
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)
|
elseif(UNIX AND NOT APPLE)
|
||||||
target_compile_definitions(${PROJECT_NAME} PRIVATE LINUX_BUILD)
|
target_compile_definitions(${PROJECT_NAME} PRIVATE LINUX_BUILD)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Especificar la ubicación del ejecutable
|
# Especificar la ubicación del ejecutable (en desktop; en wasm queda en build/wasm/)
|
||||||
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR})
|
if(NOT EMSCRIPTEN)
|
||||||
|
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||||
|
endif()
|
||||||
|
|
||||||
# --- 5. STATIC ANALYSIS TARGETS ---
|
# --- 5. STATIC ANALYSIS TARGETS ---
|
||||||
|
|
||||||
@@ -355,28 +396,32 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# --- 6. PACK RESOURCES TARGETS ---
|
# --- 6. PACK RESOURCES TARGETS ---
|
||||||
set(PACK_TOOL_SOURCES
|
# En Emscripten no generamos resources.pack: los assets se embeben vía --preload-file
|
||||||
${CMAKE_SOURCE_DIR}/tools/pack_resources/pack_resources.cpp
|
# (ver rama EMSCRIPTEN más arriba). El pack_tool tampoco tiene sentido bajo emcc.
|
||||||
${CMAKE_SOURCE_DIR}/source/core/resources/resource_pack.cpp
|
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
|
||||||
|
)
|
||||||
|
|
||||||
add_executable(pack_tool ${PACK_TOOL_SOURCES})
|
add_executable(pack_tool ${PACK_TOOL_SOURCES})
|
||||||
target_include_directories(pack_tool PRIVATE ${CMAKE_SOURCE_DIR}/source)
|
target_include_directories(pack_tool PRIVATE ${CMAKE_SOURCE_DIR}/source)
|
||||||
set_target_properties(pack_tool PROPERTIES
|
set_target_properties(pack_tool PROPERTIES
|
||||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/tools/pack_resources
|
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/tools/pack_resources
|
||||||
)
|
)
|
||||||
|
|
||||||
file(GLOB_RECURSE DATA_FILES "${CMAKE_SOURCE_DIR}/data/*")
|
file(GLOB_RECURSE DATA_FILES "${CMAKE_SOURCE_DIR}/data/*")
|
||||||
|
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT "${CMAKE_SOURCE_DIR}/resources.pack"
|
OUTPUT "${CMAKE_SOURCE_DIR}/resources.pack"
|
||||||
COMMAND $<TARGET_FILE:pack_tool>
|
COMMAND $<TARGET_FILE:pack_tool>
|
||||||
"${CMAKE_SOURCE_DIR}/data"
|
"${CMAKE_SOURCE_DIR}/data"
|
||||||
"${CMAKE_SOURCE_DIR}/resources.pack"
|
"${CMAKE_SOURCE_DIR}/resources.pack"
|
||||||
DEPENDS pack_tool ${DATA_FILES}
|
DEPENDS pack_tool ${DATA_FILES}
|
||||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||||
COMMENT "Generando resources.pack desde data/..."
|
COMMENT "Generando resources.pack desde data/..."
|
||||||
)
|
)
|
||||||
|
|
||||||
add_custom_target(pack DEPENDS "${CMAKE_SOURCE_DIR}/resources.pack")
|
add_custom_target(pack DEPENDS "${CMAKE_SOURCE_DIR}/resources.pack")
|
||||||
add_dependencies(${PROJECT_NAME} pack)
|
add_dependencies(${PROJECT_NAME} pack)
|
||||||
|
endif()
|
||||||
|
|||||||
48
Makefile
48
Makefile
@@ -283,6 +283,48 @@ linux_release:
|
|||||||
# Elimina la carpeta temporal
|
# Elimina la carpeta temporal
|
||||||
$(RMDIR) "$(RELEASE_FOLDER)"
|
$(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
|
# REGLAS ESPECIALES
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
@@ -310,8 +352,12 @@ help:
|
|||||||
@echo " make pack_tool - Compilar herramienta de empaquetado"
|
@echo " make pack_tool - Compilar herramienta de empaquetado"
|
||||||
@echo " make resources.pack - Generar pack de recursos desde data/"
|
@echo " make resources.pack - Generar pack de recursos desde data/"
|
||||||
@echo ""
|
@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 " Otros:"
|
||||||
@echo " make show_version - Mostrar version actual ($(VERSION))"
|
@echo " make show_version - Mostrar version actual ($(VERSION))"
|
||||||
@echo " make help - Mostrar esta ayuda"
|
@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
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ ui:
|
|||||||
integer_scale_disabled: "ESCALAT SENCER DESACTIVAT"
|
integer_scale_disabled: "ESCALAT SENCER DESACTIVAT"
|
||||||
vsync_enabled: "V-SYNC ACTIVAT"
|
vsync_enabled: "V-SYNC ACTIVAT"
|
||||||
vsync_disabled: "V-SYNC DESACTIVAT"
|
vsync_disabled: "V-SYNC DESACTIVAT"
|
||||||
|
gamepad_connected: "CONNECTAT"
|
||||||
|
gamepad_disconnected: "DESCONNECTAT"
|
||||||
|
|
||||||
scoreboard:
|
scoreboard:
|
||||||
lives: "vides "
|
lives: "vides "
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ ui:
|
|||||||
integer_scale_disabled: "INTEGER SCALE DISABLED"
|
integer_scale_disabled: "INTEGER SCALE DISABLED"
|
||||||
vsync_enabled: "V-SYNC ENABLED"
|
vsync_enabled: "V-SYNC ENABLED"
|
||||||
vsync_disabled: "V-SYNC DISABLED"
|
vsync_disabled: "V-SYNC DISABLED"
|
||||||
|
gamepad_connected: "CONNECTED"
|
||||||
|
gamepad_disconnected: "DISCONNECTED"
|
||||||
|
|
||||||
scoreboard:
|
scoreboard:
|
||||||
lives: "lives "
|
lives: "lives "
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ namespace GlobalInputs {
|
|||||||
return;
|
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
|
// Comportamiento normal fuera del modo kiosko
|
||||||
const std::string CODE = "PRESS AGAIN TO EXIT";
|
const std::string CODE = "PRESS AGAIN TO EXIT";
|
||||||
if (stringInVector(Notifier::get()->getCodes(), CODE)) {
|
if (stringInVector(Notifier::get()->getCodes(), CODE)) {
|
||||||
@@ -52,6 +57,7 @@ namespace GlobalInputs {
|
|||||||
} else {
|
} else {
|
||||||
Notifier::get()->show({Locale::get()->get("ui.press_again_exit")}, Notifier::Style::DEFAULT, -1, true, CODE);
|
Notifier::get()->show({Locale::get()->get("ui.press_again_exit")}, Notifier::Style::DEFAULT, -1, true, CODE);
|
||||||
}
|
}
|
||||||
|
#endif // __EMSCRIPTEN__
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleSkipSection() {
|
void handleSkipSection() {
|
||||||
|
|||||||
@@ -10,6 +10,40 @@
|
|||||||
|
|
||||||
#include "game/options.hpp" // Para Options::controls
|
#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
|
// Singleton
|
||||||
Input* Input::instance = nullptr;
|
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 {
|
auto Input::addGamepad(int device_index) -> std::string {
|
||||||
|
installWebStandardMapping(device_index);
|
||||||
SDL_Gamepad* pad = SDL_OpenGamepad(device_index);
|
SDL_Gamepad* pad = SDL_OpenGamepad(device_index);
|
||||||
if (pad == nullptr) {
|
if (pad == nullptr) {
|
||||||
std::cerr << "Error al abrir el gamepad: " << SDL_GetError() << '\n';
|
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;
|
auto name = gamepad->name;
|
||||||
std::cout << "Gamepad connected (" << name << ")" << '\n';
|
std::cout << "Gamepad connected (" << name << ")" << '\n';
|
||||||
gamepads_.push_back(std::move(gamepad));
|
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 {
|
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::string name = (*it)->name;
|
||||||
std::cout << "Gamepad disconnected (" << name << ")" << '\n';
|
std::cout << "Gamepad disconnected (" << name << ")" << '\n';
|
||||||
gamepads_.erase(it);
|
gamepads_.erase(it);
|
||||||
return name + " DISCONNECTED";
|
return name;
|
||||||
}
|
}
|
||||||
std::cerr << "No se encontró el gamepad con ID " << id << '\n';
|
std::cerr << "No se encontró el gamepad con ID " << id << '\n';
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@@ -51,10 +51,20 @@ class Input {
|
|||||||
std::string path; // Ruta del dispositivo
|
std::string path; // Ruta del dispositivo
|
||||||
std::unordered_map<Action, ButtonState> bindings; // Mapa de acciones a estados de botón
|
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)
|
explicit Gamepad(SDL_Gamepad* gamepad)
|
||||||
: pad(gamepad),
|
: pad(gamepad),
|
||||||
instance_id(SDL_GetJoystickID(SDL_GetGamepadJoystick(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))),
|
path(std::string(SDL_GetGamepadPath(pad))),
|
||||||
bindings{
|
bindings{
|
||||||
// Movimiento del jugador
|
// Movimiento del jugador
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
#include "core/rendering/screen.hpp"
|
#include "core/rendering/screen.hpp"
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
#include <emscripten.h>
|
||||||
|
#include <emscripten/html5.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <algorithm> // Para max, min, transform
|
#include <algorithm> // Para max, min, transform
|
||||||
#include <cctype> // Para toupper
|
#include <cctype> // Para toupper
|
||||||
@@ -11,9 +15,11 @@
|
|||||||
#include <iterator> // Para istreambuf_iterator, operator==
|
#include <iterator> // Para istreambuf_iterator, operator==
|
||||||
#include <string> // Para char_traits, string, operator+, operator==
|
#include <string> // Para char_traits, string, operator+, operator==
|
||||||
|
|
||||||
#include "core/input/mouse.hpp" // Para updateCursorVisibility
|
#include "core/input/mouse.hpp" // Para updateCursorVisibility
|
||||||
#include "core/rendering/render_info.hpp" // Para RenderInfo
|
#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/surface.hpp" // Para Surface, readPalFile
|
||||||
#include "core/rendering/text.hpp" // Para Text
|
#include "core/rendering/text.hpp" // Para Text
|
||||||
#include "core/resources/resource_cache.hpp" // Para Resource
|
#include "core/resources/resource_cache.hpp" // Para Resource
|
||||||
@@ -26,6 +32,38 @@
|
|||||||
// [SINGLETON]
|
// [SINGLETON]
|
||||||
Screen* Screen::screen = nullptr;
|
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
|
// [SINGLETON] Crearemos el objeto con esta función estática
|
||||||
void Screen::init() {
|
void Screen::init() {
|
||||||
Screen::screen = new Screen();
|
Screen::screen = new Screen();
|
||||||
@@ -164,6 +202,16 @@ void Screen::toggleVideoMode() {
|
|||||||
setVideoMode(Options::video.fullscreen);
|
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
|
// Reduce el tamaño de la ventana
|
||||||
auto Screen::decWindowZoom() -> bool {
|
auto Screen::decWindowZoom() -> bool {
|
||||||
if (static_cast<int>(Options::video.fullscreen) == 0) {
|
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
|
// El device GPU se crea siempre (independientemente de postfx) para evitar
|
||||||
// conflictos SDL_Renderer/SDL_GPU al hacer toggle F4 en Windows/Vulkan.
|
// conflictos SDL_Renderer/SDL_GPU al hacer toggle F4 en Windows/Vulkan.
|
||||||
void Screen::initShaders() {
|
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_;
|
SDL_Texture* tex = Options::video.border.enabled ? border_texture_ : game_texture_;
|
||||||
|
|
||||||
if (!shader_backend_) {
|
if (!shader_backend_) {
|
||||||
@@ -664,6 +716,7 @@ void Screen::initShaders() {
|
|||||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||||
applyCurrentCrtPiPreset();
|
applyCurrentCrtPiPreset();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtiene información sobre la pantalla
|
// Obtiene información sobre la pantalla
|
||||||
@@ -767,10 +820,24 @@ auto Screen::initSDLVideo() -> bool {
|
|||||||
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
|
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
|
||||||
SDL_SetRenderVSync(renderer_, Options::video.vertical_sync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
SDL_SetRenderVSync(renderer_, Options::video.vertical_sync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
||||||
|
|
||||||
|
registerEmscriptenEventCallbacks();
|
||||||
|
|
||||||
std::cout << "Video system initialized successfully\n";
|
std::cout << "Video system initialized successfully\n";
|
||||||
return true;
|
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
|
// Crea el objeto de texto
|
||||||
void Screen::createText() {
|
void Screen::createText() {
|
||||||
// Carga la surface de la fuente directamente del archivo
|
// Carga la surface de la fuente directamente del archivo
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ class Screen {
|
|||||||
// Video y ventana
|
// Video y ventana
|
||||||
void setVideoMode(bool mode); // Establece el modo de video
|
void setVideoMode(bool mode); // Establece el modo de video
|
||||||
void toggleVideoMode(); // Cambia entre pantalla completa y ventana
|
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 toggleIntegerScale(); // Alterna entre activar y desactivar el escalado entero
|
||||||
void toggleVSync(); // Alterna entre activar y desactivar el V-Sync
|
void toggleVSync(); // Alterna entre activar y desactivar el V-Sync
|
||||||
auto decWindowZoom() -> bool; // Reduce el tamaño de la ventana
|
auto decWindowZoom() -> bool; // Reduce el tamaño de la ventana
|
||||||
@@ -144,8 +145,9 @@ class Screen {
|
|||||||
void applyCurrentPostFXPreset(); // Aplica los parámetros del preset PostFX actual al backend
|
void applyCurrentPostFXPreset(); // Aplica los parámetros del preset PostFX actual al backend
|
||||||
void applyCurrentCrtPiPreset(); // Aplica los parámetros del preset CrtPi actual al backend
|
void applyCurrentCrtPiPreset(); // Aplica los parámetros del preset CrtPi actual al backend
|
||||||
void getDisplayInfo(); // Obtiene información sobre la pantalla
|
void getDisplayInfo(); // Obtiene información sobre la pantalla
|
||||||
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
|
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
|
||||||
void createText(); // Crea el objeto de texto
|
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
|
// Constructor y destructor
|
||||||
Screen();
|
Screen();
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
#include "game/editor/map_editor.hpp" // Para MapEditor
|
#include "game/editor/map_editor.hpp" // Para MapEditor
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef _WIN32
|
#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -46,12 +46,17 @@
|
|||||||
Director::Director() {
|
Director::Director() {
|
||||||
std::cout << "Game start" << '\n';
|
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
|
// Obtiene la ruta del ejecutable
|
||||||
std::string base = SDL_GetBasePath();
|
std::string base = SDL_GetBasePath();
|
||||||
if (!base.empty() && base.back() == '/') {
|
if (!base.empty() && base.back() == '/') {
|
||||||
base.pop_back();
|
base.pop_back();
|
||||||
}
|
}
|
||||||
executable_path_ = base;
|
executable_path_ = base;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Crea la carpeta del sistema donde guardar datos
|
// Crea la carpeta del sistema donde guardar datos
|
||||||
createSystemFolder("jailgames");
|
createSystemFolder("jailgames");
|
||||||
@@ -81,7 +86,7 @@ Director::Director() {
|
|||||||
// Preparar ruta al pack (en macOS bundle está en Contents/Resources/)
|
// Preparar ruta al pack (en macOS bundle está en Contents/Resources/)
|
||||||
std::string pack_path = executable_path_ + PREFIX + "/resources.pack";
|
std::string pack_path = executable_path_ + PREFIX + "/resources.pack";
|
||||||
|
|
||||||
#ifdef RELEASE_BUILD
|
#if defined(RELEASE_BUILD) && !defined(__EMSCRIPTEN__)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// RELEASE BUILD: Pack-first architecture
|
// RELEASE BUILD: Pack-first architecture
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -139,6 +144,18 @@ Director::Director() {
|
|||||||
Options::setConfigFile(Resource::List::get()->get("config.yaml"));
|
Options::setConfigFile(Resource::List::get()->get("config.yaml"));
|
||||||
Options::loadFromFile();
|
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
|
// Configura la ruta y carga los presets de PostFX
|
||||||
Options::setPostFXFile(Resource::List::get()->get("postfx.yaml"));
|
Options::setPostFXFile(Resource::List::get()->get("postfx.yaml"));
|
||||||
Options::loadPostFXFromFile();
|
Options::loadPostFXFromFile();
|
||||||
@@ -191,7 +208,7 @@ Director::Director() {
|
|||||||
KeyConfig::init("data/input/keys.yaml");
|
KeyConfig::init("data/input/keys.yaml");
|
||||||
|
|
||||||
// Special handling for gamecontrollerdb.txt - SDL needs filesystem path
|
// 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)
|
// In release, construct the path manually (not from Asset which has empty executable_path)
|
||||||
std::string gamecontroller_db = executable_path_ + PREFIX + "/gamecontrollerdb.txt";
|
std::string gamecontroller_db = executable_path_ + PREFIX + "/gamecontrollerdb.txt";
|
||||||
Input::init(gamecontroller_db);
|
Input::init(gamecontroller_db);
|
||||||
@@ -215,7 +232,7 @@ Director::Director() {
|
|||||||
std::cout << "\n"; // Fin de inicialización de sistemas
|
std::cout << "\n"; // Fin de inicialización de sistemas
|
||||||
|
|
||||||
// Inicializa el sistema de localización (antes de Cheevos que usa textos traducidos)
|
// 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
|
// En release el locale está en el pack, no en el filesystem
|
||||||
std::string locale_key = Resource::List::get()->get(Options::language + ".yaml");
|
std::string locale_key = Resource::List::get()->get(Options::language + ".yaml");
|
||||||
@@ -228,7 +245,7 @@ Director::Director() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Special handling for cheevos.bin - also needs filesystem path
|
// 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";
|
std::string cheevos_path = system_folder_ + "/cheevos.bin";
|
||||||
Cheevos::init(cheevos_path);
|
Cheevos::init(cheevos_path);
|
||||||
#else
|
#else
|
||||||
@@ -271,6 +288,12 @@ Director::~Director() {
|
|||||||
|
|
||||||
// Crea la carpeta del sistema donde guardar datos
|
// Crea la carpeta del sistema donde guardar datos
|
||||||
void Director::createSystemFolder(const std::string& folder) {
|
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
|
#ifdef _WIN32
|
||||||
system_folder_ = std::string(getenv("APPDATA")) + "/" + folder;
|
system_folder_ = std::string(getenv("APPDATA")) + "/" + folder;
|
||||||
#elif __APPLE__
|
#elif __APPLE__
|
||||||
@@ -322,6 +345,7 @@ void Director::createSystemFolder(const std::string& folder) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif // __EMSCRIPTEN__
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carga la configuración de assets desde assets.yaml
|
// Carga la configuración de assets desde assets.yaml
|
||||||
|
|||||||
@@ -1,23 +1,45 @@
|
|||||||
#include "core/system/global_events.hpp"
|
#include "core/system/global_events.hpp"
|
||||||
|
|
||||||
|
#include "core/input/input.hpp" // Para Input (gamepad add/remove)
|
||||||
#include "core/input/mouse.hpp"
|
#include "core/input/mouse.hpp"
|
||||||
|
#include "core/locale/locale.hpp" // Para Locale
|
||||||
#include "game/options.hpp" // Para Options, options, OptionsGame, OptionsAudio
|
#include "game/options.hpp" // Para Options, options, OptionsGame, OptionsAudio
|
||||||
#include "game/scene_manager.hpp" // Para SceneManager
|
#include "game/scene_manager.hpp" // Para SceneManager
|
||||||
#include "game/ui/console.hpp" // Para Console
|
#include "game/ui/console.hpp" // Para Console
|
||||||
|
#include "game/ui/notifier.hpp" // Para Notifier
|
||||||
|
|
||||||
namespace GlobalEvents {
|
namespace GlobalEvents {
|
||||||
// Comprueba los eventos que se pueden producir en cualquier sección del juego
|
// Comprueba los eventos que se pueden producir en cualquier sección del juego
|
||||||
void handle(const SDL_Event& event) {
|
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) {
|
if (event.type == SDL_EVENT_QUIT) {
|
||||||
SceneManager::current = SceneManager::Scene::QUIT;
|
SceneManager::current = SceneManager::Scene::QUIT;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (event.type == SDL_EVENT_RENDER_DEVICE_RESET || event.type == SDL_EVENT_RENDER_TARGETS_RESET) {
|
if (event.type == SDL_EVENT_RENDER_DEVICE_RESET || event.type == SDL_EVENT_RENDER_TARGETS_RESET) {
|
||||||
// reLoadTextures();
|
// 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
|
// Enrutar eventos de texto a la consola cuando está activa
|
||||||
if (Console::get() != nullptr && Console::get()->isActive()) {
|
if (Console::get() != nullptr && Console::get()->isActive()) {
|
||||||
if (event.type == SDL_EVENT_TEXT_INPUT || event.type == SDL_EVENT_KEY_DOWN) {
|
if (event.type == SDL_EVENT_TEXT_INPUT || event.type == SDL_EVENT_KEY_DOWN) {
|
||||||
|
|||||||
@@ -946,11 +946,16 @@ static auto cmdKiosk(const std::vector<std::string>& args) -> std::string {
|
|||||||
|
|
||||||
// EXIT / QUIT
|
// EXIT / QUIT
|
||||||
static auto cmdExit(const std::vector<std::string>& args) -> std::string {
|
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")) {
|
if (Options::kiosk.enabled && (args.empty() || args[0] != "PLEASE")) {
|
||||||
return "Not allowed in kiosk mode";
|
return "Not allowed in kiosk mode";
|
||||||
}
|
}
|
||||||
SceneManager::current = SceneManager::Scene::QUIT;
|
SceneManager::current = SceneManager::Scene::QUIT;
|
||||||
return "Quitting...";
|
return "Quitting...";
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// SIZE
|
// SIZE
|
||||||
|
|||||||
Reference in New Issue
Block a user