Compare commits

...

10 Commits

Author SHA1 Message Date
74da5a77ce arreglos en makefile de macos 2026-05-03 18:12:06 +02:00
e7797cee0b fix: transició entre pantalles trencat en el commit anterior 2026-04-18 12:49:47 +02:00
412df1316f optimitzacions en Surface 2026-04-18 12:40:13 +02:00
f5b6bc3ef9 afegit suppress a cppcheck 2026-04-18 12:29:37 +02:00
f9a1ae28ad normalitzat Audio 2026-04-18 11:44:14 +02:00
33cda62e44 build: unifica .clang-format/.clang-tidy i exclou external/ i spv/ amb dummies 2026-04-17 16:21:56 +02:00
21d32dab85 arreglos en make i cmake per estandaritzar amb la resta de projectes
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 13:00:02 +02:00
bfc1c6ccf5 fix: dpad controls en emscripten 2026-04-17 10:21:11 +02:00
8a44ab15e7 eliminades locales no utilitzades
modificats textos de ui a case tipo frase
notificacions llargues pasen a multilinea
2026-04-17 10:11:31 +02:00
5eb178b039 build d'emscripten 2026-04-17 10:00:37 +02:00
38 changed files with 1316 additions and 897 deletions

View File

@@ -11,9 +11,8 @@ Checks:
- -modernize-avoid-c-arrays,-warnings-as-errors - -modernize-avoid-c-arrays,-warnings-as-errors
WarningsAsErrors: '*' WarningsAsErrors: '*'
# Solo incluir archivos de tu código fuente (external tiene su propio .clang-tidy) # Solo headers del propio código fuente (external/ y spv/ tienen su propio .clang-tidy dummy)
# Excluye jail_audio.hpp del análisis HeaderFilterRegex: 'source/.*'
HeaderFilterRegex: 'source/(?!core/audio/jail_audio\.hpp|core/rendering/sdl3gpu/.*_spv\.h).*'
FormatStyle: file FormatStyle: file
CheckOptions: CheckOptions:

View File

@@ -3,6 +3,11 @@
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.10)
project(projecte_2026 VERSION 1.00) project(projecte_2026 VERSION 1.00)
# Tipus de build per defecte (Debug) si no se n'ha especificat cap
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE)
endif()
# Establecer estándar de C++ # Establecer estándar de C++
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True) set(CMAKE_CXX_STANDARD_REQUIRED True)
@@ -11,8 +16,13 @@ set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# --- GENERACIÓN DE VERSIÓN AUTOMÁTICA --- # --- GENERACIÓN DE VERSIÓN AUTOMÁTICA ---
find_package(Git QUIET) # Si GIT_HASH se ha pasado desde fuera (p.ej. desde el Makefile via -DGIT_HASH=xxx),
if(GIT_FOUND) # lo usamos tal cual. Esto evita problemas con Docker/emscripten, donde git aborta por
# "dubious ownership" en el volumen montado. En builds locales sin -DGIT_HASH, se
# resuelve aquí ejecutando git directamente.
if(NOT DEFINED GIT_HASH OR GIT_HASH STREQUAL "")
find_package(Git QUIET)
if(GIT_FOUND)
execute_process( execute_process(
COMMAND ${GIT_EXECUTABLE} rev-parse --short=7 HEAD COMMAND ${GIT_EXECUTABLE} rev-parse --short=7 HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
@@ -20,8 +30,10 @@ if(GIT_FOUND)
OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET ERROR_QUIET
) )
else() endif()
if(NOT DEFINED GIT_HASH OR GIT_HASH STREQUAL "")
set(GIT_HASH "unknown") set(GIT_HASH "unknown")
endif()
endif() endif()
# Configurar archivo de versión # Configurar archivo de versión
@@ -31,6 +43,7 @@ configure_file(${CMAKE_SOURCE_DIR}/source/version.h.in ${CMAKE_BINARY_DIR}/versi
set(APP_SOURCES set(APP_SOURCES
# Core - Audio # Core - Audio
source/core/audio/audio.cpp source/core/audio/audio.cpp
source/core/audio/audio_adapter.cpp
# Core - Input # Core - Input
source/core/input/global_inputs.cpp source/core/input/global_inputs.cpp
@@ -136,15 +149,31 @@ 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")
set(HEADERS_DIR "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu") set(HEADERS_DIR "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv")
set(SHADER_POSTFX_VERT_SRC "${SHADERS_DIR}/postfx.vert") set(SHADER_POSTFX_VERT_SRC "${SHADERS_DIR}/postfx.vert")
set(SHADER_POSTFX_FRAG_SRC "${SHADERS_DIR}/postfx.frag") set(SHADER_POSTFX_FRAG_SRC "${SHADERS_DIR}/postfx.frag")
@@ -205,10 +234,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 +286,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 ---
@@ -276,10 +330,7 @@ file(GLOB_RECURSE ALL_SOURCE_FILES
# Excluir directorio external del análisis # Excluir directorio external del análisis
list(FILTER ALL_SOURCE_FILES EXCLUDE REGEX ".*/external/.*") list(FILTER ALL_SOURCE_FILES EXCLUDE REGEX ".*/external/.*")
# Para clang-tidy, también excluir jail_audio.hpp
set(CLANG_TIDY_SOURCES ${ALL_SOURCE_FILES}) set(CLANG_TIDY_SOURCES ${ALL_SOURCE_FILES})
list(FILTER CLANG_TIDY_SOURCES EXCLUDE REGEX ".*jail_audio\\.hpp$")
list(FILTER CLANG_TIDY_SOURCES EXCLUDE REGEX ".*_spv\\.h$")
# Para cppcheck, pasar solo .cpp (los headers se procesan transitivamente). # Para cppcheck, pasar solo .cpp (los headers se procesan transitivamente).
# Si pasamos .hpp como TUs independientes, cppcheck reporta falsos positivos de # Si pasamos .hpp como TUs independientes, cppcheck reporta falsos positivos de
@@ -342,6 +393,8 @@ if(CPPCHECK_EXE)
--inline-suppr --inline-suppr
--suppress=missingIncludeSystem --suppress=missingIncludeSystem
--suppress=toomanyconfigs --suppress=toomanyconfigs
--suppress=*:*/source/external/*
--suppress=*:*/source/core/rendering/sdl3gpu/spv/*
-D_DEBUG -D_DEBUG
-DLINUX_BUILD -DLINUX_BUILD
--quiet --quiet
@@ -354,29 +407,34 @@ else()
message(STATUS "cppcheck no encontrado - target 'cppcheck' no disponible") message(STATUS "cppcheck no encontrado - target 'cppcheck' no disponible")
endif() endif()
# --- 6. PACK RESOURCES TARGETS --- # --- 6. EINA STANDALONE: pack_resources ---
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 # Executable auxiliar que empaqueta `data/` a `resources.pack`.
${CMAKE_SOURCE_DIR}/source/core/resources/resource_pack.cpp # No es compila per defecte (EXCLUDE_FROM_ALL). Build explícit:
) # cmake --build build --target pack_resources
if(NOT EMSCRIPTEN)
add_executable(pack_resources EXCLUDE_FROM_ALL
tools/pack_resources/pack_resources.cpp
source/core/resources/resource_pack.cpp
)
target_include_directories(pack_resources PRIVATE "${CMAKE_SOURCE_DIR}/source")
target_compile_options(pack_resources PRIVATE -Wall)
add_executable(pack_tool ${PACK_TOOL_SOURCES}) # Regeneració automàtica de resources.pack en cada build si canvia data/.
target_include_directories(pack_tool PRIVATE ${CMAKE_SOURCE_DIR}/source) file(GLOB_RECURSE DATA_FILES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/data/*")
set_target_properties(pack_tool PROPERTIES set(RESOURCE_PACK "${CMAKE_SOURCE_DIR}/resources.pack")
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/tools/pack_resources
)
file(GLOB_RECURSE DATA_FILES "${CMAKE_SOURCE_DIR}/data/*") add_custom_command(
OUTPUT ${RESOURCE_PACK}
add_custom_command( COMMAND $<TARGET_FILE:pack_resources>
OUTPUT "${CMAKE_SOURCE_DIR}/resources.pack"
COMMAND $<TARGET_FILE:pack_tool>
"${CMAKE_SOURCE_DIR}/data" "${CMAKE_SOURCE_DIR}/data"
"${CMAKE_SOURCE_DIR}/resources.pack" "${RESOURCE_PACK}"
DEPENDS pack_tool ${DATA_FILES} DEPENDS pack_resources ${DATA_FILES}
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Generando resources.pack desde data/..." COMMENT "Empaquetant data/ → resources.pack"
) VERBATIM
)
add_custom_target(pack DEPENDS "${CMAKE_SOURCE_DIR}/resources.pack") add_custom_target(resource_pack ALL DEPENDS ${RESOURCE_PACK})
add_dependencies(${PROJECT_NAME} pack) add_dependencies(${PROJECT_NAME} resource_pack)
endif()

231
Makefile
View File

@@ -4,7 +4,6 @@
DIR_ROOT := $(dir $(abspath $(MAKEFILE_LIST))) DIR_ROOT := $(dir $(abspath $(MAKEFILE_LIST)))
DIR_SOURCES := $(addsuffix /, $(DIR_ROOT)source) DIR_SOURCES := $(addsuffix /, $(DIR_ROOT)source)
DIR_BIN := $(addsuffix /, $(DIR_ROOT)) DIR_BIN := $(addsuffix /, $(DIR_ROOT))
DIR_TOOLS := $(addsuffix /, $(DIR_ROOT)tools)
# ============================================================================== # ==============================================================================
# TARGET NAMES # TARGET NAMES
@@ -20,8 +19,6 @@ RESOURCE_FILE := release/windows/jdd.res
# ============================================================================== # ==============================================================================
# TOOLS # TOOLS
# ============================================================================== # ==============================================================================
DIR_PACK_TOOL := $(DIR_TOOLS)pack_resources
SHADER_SCRIPT := $(DIR_ROOT)tools/shaders/compile_spirv.sh
SHADER_CMAKE := $(DIR_ROOT)tools/shaders/compile_spirv.cmake SHADER_CMAKE := $(DIR_ROOT)tools/shaders/compile_spirv.cmake
SHADERS_DIR := $(DIR_ROOT)data/shaders SHADERS_DIR := $(DIR_ROOT)data/shaders
HEADERS_DIR := $(DIR_ROOT)source/core/rendering/sdl3gpu HEADERS_DIR := $(DIR_ROOT)source/core/rendering/sdl3gpu
@@ -40,6 +37,20 @@ else
VERSION := v$(shell grep 'constexpr const char\* VERSION' source/utils/defines.hpp | sed -E 's/.*VERSION = "([^"]+)".*/\1/') VERSION := v$(shell grep 'constexpr const char\* VERSION' source/utils/defines.hpp | sed -E 's/.*VERSION = "([^"]+)".*/\1/')
endif endif
# ==============================================================================
# GIT HASH (computat al host, passat a CMake via -DGIT_HASH)
# Evita que CMake haja de cridar git des de Docker/emscripten on falla per
# "dubious ownership" del volum muntat.
# ==============================================================================
ifeq ($(OS),Windows_NT)
GIT_HASH := $(shell git rev-parse --short=7 HEAD 2>NUL)
else
GIT_HASH := $(shell git rev-parse --short=7 HEAD 2>/dev/null)
endif
ifeq ($(GIT_HASH),)
GIT_HASH := unknown
endif
# ============================================================================== # ==============================================================================
# SHELL (Windows usa cmd.exe para que las recetas con powershell funcionen igual # SHELL (Windows usa cmd.exe para que las recetas con powershell funcionen igual
# desde cualquier terminal: PowerShell, cmd o git-bash) # desde cualquier terminal: PowerShell, cmd o git-bash)
@@ -54,9 +65,13 @@ endif
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
WIN_TARGET_FILE := $(DIR_BIN)$(APP_NAME) WIN_TARGET_FILE := $(DIR_BIN)$(APP_NAME)
WIN_RELEASE_FILE := $(RELEASE_FOLDER)/$(APP_NAME) WIN_RELEASE_FILE := $(RELEASE_FOLDER)/$(APP_NAME)
# Escapa apòstrofs per a PowerShell (duplica ' → ''). Sense això, APP_NAMEs
# com "JailDoctor's Dilemma" trencarien el parsing de -Destination '...'.
WIN_RELEASE_FILE_PS := $(subst ','',$(WIN_RELEASE_FILE))
else else
WIN_TARGET_FILE := $(TARGET_FILE) WIN_TARGET_FILE := $(TARGET_FILE)
WIN_RELEASE_FILE := $(RELEASE_FILE) WIN_RELEASE_FILE := $(RELEASE_FILE)
WIN_RELEASE_FILE_PS := $(WIN_RELEASE_FILE)
endif endif
# ============================================================================== # ==============================================================================
@@ -82,15 +97,24 @@ else
UNAME_S := $(shell uname -s) UNAME_S := $(shell uname -s)
endif endif
# ==============================================================================
# CMAKE GENERATOR (Windows needs explicit MinGW Makefiles generator)
# ==============================================================================
ifeq ($(OS),Windows_NT)
CMAKE_GEN := -G "MinGW Makefiles"
else
CMAKE_GEN :=
endif
# ============================================================================== # ==============================================================================
# COMPILACIÓN CON CMAKE # COMPILACIÓN CON CMAKE
# ============================================================================== # ==============================================================================
all: all:
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release @cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
@cmake --build build @cmake --build build
debug: debug:
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug @cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Debug -DGIT_HASH=$(GIT_HASH)
@cmake --build build @cmake --build build
# ============================================================================== # ==============================================================================
@@ -98,12 +122,12 @@ debug:
# ============================================================================== # ==============================================================================
release: release:
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
@"$(MAKE)" windows_release @"$(MAKE)" _windows_release
else else
ifeq ($(UNAME_S),Darwin) ifeq ($(UNAME_S),Darwin)
@$(MAKE) macos_release @$(MAKE) _macos_release
else else
@$(MAKE) linux_release @$(MAKE) _linux_release
endif endif
endif endif
@@ -118,23 +142,22 @@ else
endif endif
# ============================================================================== # ==============================================================================
# REGLAS PARA HERRAMIENTA DE EMPAQUETADO Y RESOURCES.PACK # EMPAQUETADO DE RECURSOS (build previ de l'eina + execució)
# ============================================================================== # ==============================================================================
pack_tool: pack:
@$(MAKE) -C $(DIR_PACK_TOOL) @cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
@cmake --build build --target pack_resources
resources.pack: pack_tool @./build/pack_resources data resources.pack
@$(MAKE) -C $(DIR_PACK_TOOL) pack
# ============================================================================== # ==============================================================================
# COMPILACIÓN PARA WINDOWS (RELEASE) # COMPILACIÓN PARA WINDOWS (RELEASE)
# ============================================================================== # ==============================================================================
windows_release: _windows_release:
@echo off @echo off
@echo Creando release para Windows - Version: $(VERSION) @echo Creando release para Windows - Version: $(VERSION)
# Compila con cmake (genera shaders, resources.pack y ejecutable) # Compila con cmake (genera shaders, resources.pack y ejecutable)
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release @cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
@cmake --build build @cmake --build build
# Crea carpeta de distribución y carpeta temporal 'RELEASE_FOLDER' # Crea carpeta de distribución y carpeta temporal 'RELEASE_FOLDER'
@@ -148,7 +171,7 @@ windows_release:
@powershell -Command "Copy-Item 'README.md' -Destination '$(RELEASE_FOLDER)'" @powershell -Command "Copy-Item 'README.md' -Destination '$(RELEASE_FOLDER)'"
@powershell -Command "Copy-Item 'gamecontrollerdb.txt' -Destination '$(RELEASE_FOLDER)'" @powershell -Command "Copy-Item 'gamecontrollerdb.txt' -Destination '$(RELEASE_FOLDER)'"
@powershell -Command "Copy-Item 'release\windows\dll\*.dll' -Destination '$(RELEASE_FOLDER)'" @powershell -Command "Copy-Item 'release\windows\dll\*.dll' -Destination '$(RELEASE_FOLDER)'"
@powershell -Command "Copy-Item -Path '$(TARGET_FILE)' -Destination '\"$(WIN_RELEASE_FILE).exe\"'" @powershell -Command "Copy-Item -Path '$(TARGET_FILE).exe' -Destination '$(WIN_RELEASE_FILE_PS).exe'"
strip -s -R .comment -R .gnu.version "$(WIN_RELEASE_FILE).exe" --strip-unneeded strip -s -R .comment -R .gnu.version "$(WIN_RELEASE_FILE).exe" --strip-unneeded
# Crea el fichero .zip # Crea el fichero .zip
@@ -162,15 +185,31 @@ windows_release:
# ============================================================================== # ==============================================================================
# COMPILACIÓN PARA MACOS (RELEASE) # COMPILACIÓN PARA MACOS (RELEASE)
# ============================================================================== # ==============================================================================
macos_release: _macos_release:
@echo "Creando release para macOS - Version: $(VERSION)" @echo "Creando release para macOS - Version: $(VERSION)"
# Verificar e instalar create-dmg si es necesario # Verifica dependencias necesarias (create-dmg). Si falta, intenta instalarla
@which create-dmg > /dev/null || (echo "Instalando create-dmg..." && brew install create-dmg) # con brew; si brew tampoco está, indica el comando exacto al usuario.
@command -v create-dmg >/dev/null 2>&1 || { \
# Compila la versión para procesadores Intel con cmake (genera shaders y resources.pack) echo ""; \
@cmake -S . -B build/intel -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DMACOS_BUNDLE=ON echo "============================================"; \
@cmake --build build/intel echo " Falta la dependencia: create-dmg"; \
echo "============================================"; \
if command -v brew >/dev/null 2>&1; then \
echo " Instalando con: brew install create-dmg"; \
brew install create-dmg || { \
echo ""; \
echo " ERROR: 'brew install create-dmg' ha fallado."; \
echo " Ejecuta el comando manualmente y vuelve a probar."; \
exit 1; \
}; \
else \
echo " Homebrew no está instalado."; \
echo " Instálalo desde https://brew.sh y luego ejecuta:"; \
echo " brew install create-dmg"; \
exit 1; \
fi; \
}
# Elimina datos de compilaciones anteriores # Elimina datos de compilaciones anteriores
$(RMDIR) "$(RELEASE_FOLDER)" $(RMDIR) "$(RELEASE_FOLDER)"
@@ -184,8 +223,7 @@ macos_release:
$(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS" $(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS"
$(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources" $(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
# Copia carpetas y ficheros # Copia carpetas y ficheros del bundle (resources.pack se generará al compilar)
cp resources.pack "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
cp gamecontrollerdb.txt "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources" cp gamecontrollerdb.txt "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
cp -R release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64/SDL3.framework "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks" cp -R release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64/SDL3.framework "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks"
cp release/icons/*.icns "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources" cp release/icons/*.icns "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
@@ -199,14 +237,21 @@ macos_release:
sed -i '' '/<key>CFBundleShortVersionString<\/key>/{n;s|<string>.*</string>|<string>'"$$RAW_VERSION"'</string>|;}' "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Info.plist"; \ sed -i '' '/<key>CFBundleShortVersionString<\/key>/{n;s|<string>.*</string>|<string>'"$$RAW_VERSION"'</string>|;}' "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Info.plist"; \
sed -i '' '/<key>CFBundleVersion<\/key>/{n;s|<string>.*</string>|<string>'"$$RAW_VERSION"'</string>|;}' "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Info.plist" sed -i '' '/<key>CFBundleVersion<\/key>/{n;s|<string>.*</string>|<string>'"$$RAW_VERSION"'</string>|;}' "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Info.plist"
# Copia el ejecutable Intel al bundle # Compila y empaqueta la versión Intel (best-effort: si falla, se omite el
cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" # DMG Intel y continúa con la build de Apple Silicon).
@echo ""
# Firma la aplicación @echo "============================================"
codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app" @echo " Compilando version Intel (x86_64)"
@echo "============================================"
# Empaqueta el .dmg de la versión Intel con create-dmg @if cmake -S . -B build/intel -DCMAKE_BUILD_TYPE=Release \
@echo "Creando DMG Intel con iconos de 96x96..." -DCMAKE_OSX_ARCHITECTURES=x86_64 \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 \
-DMACOS_BUNDLE=ON -DGIT_HASH=$(GIT_HASH) \
&& cmake --build build/intel; then \
cp resources.pack "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"; \
cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)"; \
codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app"; \
echo "Creando DMG Intel con iconos de 96x96..."; \
create-dmg \ create-dmg \
--volname "$(APP_NAME)" \ --volname "$(APP_NAME)" \
--window-pos 200 120 \ --window-pos 200 120 \
@@ -219,12 +264,26 @@ macos_release:
--app-drop-link 115 102 \ --app-drop-link 115 102 \
--hide-extension "$(APP_NAME).app" \ --hide-extension "$(APP_NAME).app" \
"$(MACOS_INTEL_RELEASE)" \ "$(MACOS_INTEL_RELEASE)" \
"$(RELEASE_FOLDER)" || true "$(RELEASE_FOLDER)" || true; \
@echo "Release Intel creado: $(MACOS_INTEL_RELEASE)" echo "Release Intel creado: $(MACOS_INTEL_RELEASE)"; \
else \
echo ""; \
echo "============================================"; \
echo " WARNING: la build Intel ha fallado."; \
echo " Se omite el DMG Intel y se continúa con"; \
echo " la build de Apple Silicon."; \
echo "============================================"; \
echo ""; \
fi
# Compila la versión para procesadores Apple Silicon con cmake # Compila la versión para procesadores Apple Silicon con cmake
@cmake -S . -B build/arm -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 -DMACOS_BUNDLE=ON @echo ""
@echo "============================================"
@echo " Compilando version Apple Silicon (arm64)"
@echo "============================================"
@cmake -S . -B build/arm -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 -DMACOS_BUNDLE=ON -DGIT_HASH=$(GIT_HASH)
@cmake --build build/arm @cmake --build build/arm
cp resources.pack "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)"
# Firma la aplicación # Firma la aplicación
@@ -256,11 +315,11 @@ macos_release:
# ============================================================================== # ==============================================================================
# COMPILACIÓN PARA LINUX (RELEASE) # COMPILACIÓN PARA LINUX (RELEASE)
# ============================================================================== # ==============================================================================
linux_release: _linux_release:
@echo "Creando release para Linux - Version: $(VERSION)" @echo "Creando release para Linux - Version: $(VERSION)"
# Compila con cmake (genera shaders, resources.pack y ejecutable) # Compila con cmake (genera shaders, resources.pack y ejecutable)
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release @cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
@cmake --build build @cmake --build build
# Elimina carpeta temporal previa y la recrea (crea dist/ si no existe) # Elimina carpeta temporal previa y la recrea (crea dist/ si no existe)
@@ -283,6 +342,80 @@ 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 -DGIT_HASH=$(GIT_HASH) && 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 -DGIT_HASH=$(GIT_HASH) && 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"
# ==============================================================================
# ==============================================================================
# CODE QUALITY (delegados a cmake)
# ==============================================================================
format:
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
@cmake --build build --target format
format-check:
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
@cmake --build build --target format-check
tidy:
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
@cmake --build build --target tidy
tidy-fix:
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
@cmake --build build --target tidy-fix
cppcheck:
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
@cmake --build build --target cppcheck
# DESCARGA DE GAMECONTROLLERDB
# ==============================================================================
controllerdb:
@echo "Descargando gamecontrollerdb.txt..."
curl -fsSL https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/master/gamecontrollerdb.txt \
-o gamecontrollerdb.txt
@echo "gamecontrollerdb.txt actualizado"
# ============================================================================== # ==============================================================================
# REGLAS ESPECIALES # REGLAS ESPECIALES
# ============================================================================== # ==============================================================================
@@ -301,17 +434,25 @@ help:
@echo "" @echo ""
@echo " Release:" @echo " Release:"
@echo " make release - Crear release (detecta SO automaticamente)" @echo " make release - Crear release (detecta SO automaticamente)"
@echo " make windows_release - Crear release para Windows"
@echo " make linux_release - Crear release para Linux"
@echo " make macos_release - Crear release para macOS"
@echo "" @echo ""
@echo " Herramientas:" @echo " Herramientas:"
@echo " make compile_shaders - Compilar shaders SPIR-V" @echo " make compile_shaders - Compilar shaders SPIR-V"
@echo " make pack_tool - Compilar herramienta de empaquetado" @echo " make pack - Empaquetar recursos a resources.pack"
@echo " make resources.pack - Generar pack de recursos desde data/" @echo " make controllerdb - Descargar gamecontrollerdb.txt actualizado"
@echo ""
@echo " Calidad de codigo:"
@echo " make format - Formatear codigo con clang-format"
@echo " make format-check - Verificar formato sin modificar"
@echo " make tidy - Analisis estatico con clang-tidy"
@echo " make tidy-fix - Analisis estatico con auto-fix"
@echo " make cppcheck - Analisis estatico con cppcheck"
@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 ""
@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 controllerdb format format-check tidy tidy-fix cppcheck show_version help

View File

@@ -2,7 +2,6 @@
# lang: ca # lang: ca
title: title:
marquee: "EI JAILERS!! ESTEM EN 2022 I ENCARA HO PETEM COM EN 1998!! QUÉ, HO HEU SENTIT O NO? ELS JAILGAMES HAN TORNAT!! SÍ, COLLONS, HAN TORNAT!! MÉS DE 10 TÍTOLS QUE EL JAILDOC TÉ EN LA CUINA A FOC LENT!! MOLT LENT!! AIXÒ ÉS UNA BARBARITAT, PERÒ... QUIN EIXIRÀ PRIMER? I ATENCIÓ, QUE HI HA UN APARELLET NOU QUE VOS FARÀ VOLAR EL CAP: EL P.A.C.O.! PERÒ UN MOMENT... QUÈ ÉS AQUELLA COSETA QUE VE PER ALLÀ? OOOH, AQUELLA MINIASCII ÉS AMOR DEL BO!! LI PEGARIA UNA LLEPAETA A CADA BYTE! OSTRES! I NO VOS OBLIDEU DE PUJAR AQUELLS JAILGAMES VELLS I PANXUTS DE MS-DOS A GITHUB, QUE SI NO ES PERDRAN!! QUIN SERÀ EL PRÒXIM PROJECTE DE JAILDOC? SERÀ UN PROJECTE DE MERDA? AI MARE... NI IDEA, PERÒ ACÍ PODEU SABER-HO SI RESOLEU EL DILEMA DEL JAILDOCTOR... VOS ATREVIU O QUÈ? VAAAAA!!!"
menu: menu:
play: "1. JUGAR" play: "1. JUGAR"
keyboard: "2. REDEFINIR TECLES" keyboard: "2. REDEFINIR TECLES"
@@ -24,76 +23,42 @@ title:
defined: "BOTONS DEFINITS" defined: "BOTONS DEFINITS"
already_used: "BOTÓ JA USAT! PROVA'N UN ALTRE" already_used: "BOTÓ JA USAT! PROVA'N UN ALTRE"
game_over:
title: "G A M E O V E R"
items: "OBJECTES: "
rooms: "SALES: "
ending:
t0: "FINALMENT HO VA ACONSEGUIR"
t1: "ARRIBAR A LA JAIL"
t2: "AMB TOTS ELS SEUS PROJECTES"
t3: "A PUNT D'ALLIBERAR-LOS"
t4: "ALLÍ ESTAVEN TOTS ELS JAILERS"
t5: "ESPERANT QUE ELS JAILGAMES"
t6: "FOREN ALLIBERATS"
t7: "HI HAVIA FINS I TOT BARRULLS"
t8: "I BEGGINERS ENTRE LA GENT"
t9: "BRY ESTAVA PLORANT..."
t10: "PERÒ DE SOBTE ALGUNA COSA"
t11: "LI VA CRIDAR L'ATENCIÓ"
t12: "UN MUNT DE FERRALLA!"
t13: "PLE DE TRASTOS QUE NI ANAVEN!!"
t14: "I ALESHORES,"
t15: "QUARANTA PROJECTES NOUS"
t16: "VAN NÀIXER..."
ending2:
starring: "PROTAGONISTES"
jaildoctor: "JAILDOCTOR"
thank_you: "GRÀCIES"
for_playing: "PER JUGAR!"
credits:
instructions: "INSTRUCCIONS:"
l0: "AJUDA A JAILDOC A RECUPERAR"
l1: "ELS SEUS PROJECTES I ARRIBAR"
l2: "A LA JAIL PER ACABAR-LOS"
keys: "TECLES:"
keys_move: "CURSORS PER A MOURE I SALTAR"
f8: "F8 ACTIVAR/DESACTIVAR MÚSICA"
f11: "F11 PAUSAR EL JOC"
f1f2: "F1-F2 MIDA DE LA FINESTRA"
f3: "F3 PANTALLA COMPLETA"
f9: "F9 VORA DE LA PANTALLA"
author: "UN JOC DE JAILDESIGNER"
date: "FET A L'ESTIU/TARDOR DEL 2022"
love: "I LOVE JAILGAMES! "
achievements: achievements:
header: "ASSOLIMENT DESBLOQUEJAT!" header: "ASSOLIMENT DESBLOQUEJAT!"
ui: ui:
press_again_menu: "PREM DE NOU PER TORNAR AL MENÚ" press_again_menu: "Prem de nou per tornar al menú"
press_again_exit: "PREM DE NOU PER EIXIR" press_again_exit: "Prem de nou per eixir"
border_enabled: "VORA ACTIVADA" border_enabled: "Vora activada"
border_disabled: "VORA DESACTIVADA" border_disabled: "Vora desactivada"
fullscreen_enabled: "PANTALLA COMPLETA ACTIVADA" fullscreen_enabled: "Pantalla completa activada"
fullscreen_disabled: "PANTALLA COMPLETA DESACTIVADA" fullscreen_disabled: "Pantalla completa desactivada"
window_zoom: "ZOOM FINESTRA x" window_zoom: "Zoom finestra x"
shaders_enabled: "SHADERS ACTIVATS" shaders_enabled: "Shaders activats"
shaders_disabled: "SHADERS DESACTIVATS" shaders_disabled: "Shaders desactivats"
shader: "SHADER" shader: "Shader"
postfx: "POSTFX" postfx: "PostFX"
crtpi: "CRTPI" crtpi: "CRTPi"
supersampling_enabled: "SUPERMOSTREIG ACTIVAT" palette: "Paleta"
supersampling_disabled: "SUPERMOSTREIG DESACTIVAT" palette_sort: "Ordenació paleta"
palette: "PALETA" integer_scale_enabled: "Escalat sencer activat"
palette_sort: "ORDENACIÓ PALETA" integer_scale_disabled: "Escalat sencer desactivat"
integer_scale_enabled: "ESCALAT SENCER ACTIVAT" vsync_enabled: "V-Sync activat"
integer_scale_disabled: "ESCALAT SENCER DESACTIVAT" vsync_disabled: "V-Sync desactivat"
vsync_enabled: "V-SYNC ACTIVAT" gamepad_connected: "connectat"
vsync_disabled: "V-SYNC DESACTIVAT" gamepad_disconnected: "desconnectat"
music_enabled: "Música activada"
music_disabled: "Música desactivada"
paused: "Joc en pausa"
running: "Joc en marxa"
enabled: " activat"
disabled: " desactivat"
cheat_infinite_lives: "Vides infinites"
cheat_invincible: "Invencible"
debug_enabled: "Debug activat"
debug_disabled: "Debug desactivat"
editor_enabled: "Editor activat"
editor_disabled: "Editor desactivat"
scoreboard: scoreboard:
lives: "vides " lives: "vides "
@@ -102,18 +67,3 @@ scoreboard:
music_on: "musica" music_on: "musica"
cheat_infinite_lives: "vides infinites" cheat_infinite_lives: "vides infinites"
cheat_invincibility: "invencibilitat" cheat_invincibility: "invencibilitat"
game:
music_enabled: "MÚSICA ACTIVADA"
music_disabled: "MÚSICA DESACTIVADA"
paused: "JOC EN PAUSA"
running: "JOC EN MARXA"
enabled: " ACTIVAT"
disabled: " DESACTIVAT"
cheat_infinite_lives: "VIDES INFINITES"
cheat_invincible: "INVENCIBLE"
cheat_jail_open: "JAIL OBERTA"
debug_enabled: "DEBUG ACTIVAT"
debug_disabled: "DEBUG DESACTIVAT"
editor_enabled: "EDITOR ACTIVAT"
editor_disabled: "EDITOR DESACTIVAT"

View File

@@ -2,7 +2,6 @@
# lang: en # lang: en
title: title:
marquee: "HEY JAILERS!! IT'S 2022 AND WE'RE STILL ROCKING LIKE IT'S 1998!!! HAVE YOU HEARD IT? JAILGAMES ARE BACK!! YEEESSS BACK!! MORE THAN 10 TITLES ON JAILDOC'S KITCHEN!! THATS A LOOOOOOT OF JAILGAMES, BUT WHICH ONE WILL STRIKE FIRST? THERE IS ALSO A NEW DEVICE TO COME THAT WILL BLOW YOUR MIND WITH JAILGAMES ON THE GO: P.A.C.O. BUT WAIT! WHAT'S THAT BEAUTY I'M SEEING RIGHT OVER THERE?? OOOH THAT TINY MINIASCII IS PURE LOVE!! I WANT TO LICK EVERY BYTE OF IT!! OH SHIT! AND DON'T FORGET TO BRING BACK THOSE OLD AND FAT MS-DOS JAILGAMES TO GITHUB TO KEEP THEM ALIVE!! WHAT WILL BE THE NEXT JAILDOC RELEASE? WHAT WILL BE THE NEXT PROJECT TO COME ALIVE?? OH BABY WE DON'T KNOW BUT HERE YOU CAN FIND THE ANSWER, YOU JUST HAVE TO COMPLETE JAILDOCTOR'S DILEMMA ... COULD YOU?"
menu: menu:
play: "1. PLAY" play: "1. PLAY"
keyboard: "2. REDEFINE KEYBOARD" keyboard: "2. REDEFINE KEYBOARD"
@@ -24,76 +23,42 @@ title:
defined: "BUTTONS DEFINED" defined: "BUTTONS DEFINED"
already_used: "BUTTON ALREADY USED! TRY ANOTHER" already_used: "BUTTON ALREADY USED! TRY ANOTHER"
game_over:
title: "G A M E O V E R"
items: "ITEMS: "
rooms: "ROOMS: "
ending:
t0: "HE FINALLY MANAGED"
t1: "TO GET TO THE JAIL"
t2: "WITH ALL HIS PROJECTS"
t3: "READY TO BE FREED"
t4: "ALL THE JAILERS WERE THERE"
t5: "WAITING FOR THE JAILGAMES"
t6: "TO BE RELEASED"
t7: "THERE WERE EVEN BARRULLS AND"
t8: "BEGINNERS AMONG THE CROWD"
t9: "BRY WAS CRYING..."
t10: "BUT SUDDENLY SOMETHING"
t11: "CAUGHT HIS ATTENTION"
t12: "A PILE OF JUNK!"
t13: "FULL OF NON WORKING TRASH!!"
t14: "AND THEN,"
t15: "FOURTY NEW PROJECTS"
t16: "WERE BORN..."
ending2:
starring: "STARRING"
jaildoctor: "JAILDOCTOR"
thank_you: "THANK YOU"
for_playing: "FOR PLAYING!"
credits:
instructions: "INSTRUCTIONS:"
l0: "HELP JAILDOC TO GET BACK ALL"
l1: "HIS PROJECTS AND GO TO THE"
l2: "JAIL TO FINISH THEM"
keys: "KEYS:"
keys_move: "CURSORS TO MOVE AND JUMP"
f8: "F8 TOGGLE THE MUSIC"
f11: "F11 PAUSE THE GAME"
f1f2: "F1-F2 WINDOWS SIZE"
f3: "F3 TOGGLE FULLSCREEN"
f9: "F9 TOOGLE BORDER SCREEN"
author: "A GAME BY JAILDESIGNER"
date: "MADE ON SUMMER/FALL 2022"
love: "I LOVE JAILGAMES! "
achievements: achievements:
header: "ACHIEVEMENT UNLOCKED!" header: "ACHIEVEMENT UNLOCKED!"
ui: ui:
press_again_menu: "PRESS AGAIN TO RETURN TO MENU" press_again_menu: "Press again to return to menu"
press_again_exit: "PRESS AGAIN TO EXIT" press_again_exit: "Press again to exit"
border_enabled: "BORDER ENABLED" border_enabled: "Border enabled"
border_disabled: "BORDER DISABLED" border_disabled: "Border disabled"
fullscreen_enabled: "FULLSCREEN ENABLED" fullscreen_enabled: "Fullscreen enabled"
fullscreen_disabled: "FULLSCREEN DISABLED" fullscreen_disabled: "Fullscreen disabled"
window_zoom: "WINDOW ZOOM x" window_zoom: "Window zoom x"
shaders_enabled: "SHADERS ON" shaders_enabled: "Shaders on"
shaders_disabled: "SHADERS OFF" shaders_disabled: "Shaders off"
shader: "SHADER" shader: "Shader"
postfx: "POSTFX" postfx: "PostFX"
crtpi: "CRTPI" crtpi: "CRTPi"
supersampling_enabled: "SUPERSAMPLING ON" palette: "Palette"
supersampling_disabled: "SUPERSAMPLING OFF" palette_sort: "Palette sort"
palette: "PALETTE" integer_scale_enabled: "Integer scale enabled"
palette_sort: "PALETTE SORT" integer_scale_disabled: "Integer scale disabled"
integer_scale_enabled: "INTEGER SCALE ENABLED" vsync_enabled: "V-Sync enabled"
integer_scale_disabled: "INTEGER SCALE DISABLED" vsync_disabled: "V-Sync disabled"
vsync_enabled: "V-SYNC ENABLED" gamepad_connected: "connected"
vsync_disabled: "V-SYNC DISABLED" gamepad_disconnected: "disconnected"
music_enabled: "Music enabled"
music_disabled: "Music disabled"
paused: "Game paused"
running: "Game running"
enabled: " enabled"
disabled: " disabled"
cheat_infinite_lives: "Infinite lives"
cheat_invincible: "Invincible"
debug_enabled: "Debug enabled"
debug_disabled: "Debug disabled"
editor_enabled: "Editor enabled"
editor_disabled: "Editor disabled"
scoreboard: scoreboard:
lives: "lives " lives: "lives "
@@ -102,18 +67,3 @@ scoreboard:
music_on: "music" music_on: "music"
cheat_infinite_lives: "infinite lives" cheat_infinite_lives: "infinite lives"
cheat_invincibility: "invincibility" cheat_invincibility: "invincibility"
game:
music_enabled: "MUSIC ENABLED"
music_disabled: "MUSIC DISABLED"
paused: "GAME PAUSED"
running: "GAME RUNNING"
enabled: " ENABLED"
disabled: " DISABLED"
cheat_infinite_lives: "INFINITE LIVES"
cheat_invincible: "INVINCIBLE"
cheat_jail_open: "JAIL IS OPEN"
debug_enabled: "DEBUG ENABLED"
debug_disabled: "DEBUG DISABLED"
editor_enabled: "EDITOR ENABLED"
editor_disabled: "EDITOR DISABLED"

View File

@@ -35,24 +35,24 @@ tilemap:
- [191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191] - [191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191]
# Mapa de colisiones (0 = vacio, 1 = solido) # Mapa de colisiones (0 = vacio, 1 = solido)
collision: collision:
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,27 @@
#include "audio.hpp" #include "core/audio/audio.hpp"
#include <SDL3/SDL.h> // Para SDL_LogInfo, SDL_LogCategory, SDL_G... #include <SDL3/SDL.h> // Para SDL_GetError, SDL_Init
#include <algorithm> // Para clamp #include <algorithm> // Para clamp
#include <iostream> // Para std::cout #include <iostream> // Para std::cout
// Implementación de stb_vorbis (debe estar ANTES de incluir jail_audio.hpp) // Implementación de stb_vorbis (debe estar ANTES de incluir jail_audio.hpp).
// clang-format off // clang-format off
#undef STB_VORBIS_HEADER_ONLY #undef STB_VORBIS_HEADER_ONLY
#include "external/stb_vorbis.h" #include "external/stb_vorbis.c"
// stb_vorbis.c filtra les macros L, C i R (i PLAYBACK_*) al TU. Les netegem
// perquè xocarien amb noms de paràmetres de plantilla en altres headers.
#undef L
#undef C
#undef R
#undef PLAYBACK_MONO
#undef PLAYBACK_LEFT
#undef PLAYBACK_RIGHT
// clang-format on // clang-format on
#include "core/audio/jail_audio.hpp" // Para JA_FadeOutMusic, JA_Init, JA_PauseM... #include "core/audio/audio_adapter.hpp" // Para AudioResource::getMusic/getSound
#include "core/resources/resource_cache.hpp" // Para Resource #include "core/audio/jail_audio.hpp" // Para JA_*
#include "game/options.hpp" // Para AudioOptions, audio, MusicOptions #include "game/options.hpp" // Para Options::audio
// Singleton // Singleton
Audio* Audio::instance = nullptr; Audio* Audio::instance = nullptr;
@@ -22,7 +30,10 @@ Audio* Audio::instance = nullptr;
void Audio::init() { Audio::instance = new Audio(); } void Audio::init() { Audio::instance = new Audio(); }
// Libera la instancia // Libera la instancia
void Audio::destroy() { delete Audio::instance; } void Audio::destroy() {
delete Audio::instance;
Audio::instance = nullptr;
}
// Obtiene la instancia // Obtiene la instancia
auto Audio::get() -> Audio* { return Audio::instance; } auto Audio::get() -> Audio* { return Audio::instance; }
@@ -45,7 +56,7 @@ void Audio::update() {
} }
} }
// Reproduce la música (con crossfade opcional) // Reproduce la música por nombre (con crossfade opcional)
void Audio::playMusic(const std::string& name, const int loop, const int crossfade_ms) { void Audio::playMusic(const std::string& name, const int loop, const int crossfade_ms) {
bool new_loop = (loop != 0); bool new_loop = (loop != 0);
@@ -54,15 +65,14 @@ void Audio::playMusic(const std::string& name, const int loop, const int crossfa
return; return;
} }
// Intentar obtener recurso; si falla, no tocar estado if (!music_enabled_) return;
auto* resource = Resource::Cache::get()->getMusic(name);
auto* resource = AudioResource::getMusic(name);
if (resource == nullptr) return; if (resource == nullptr) return;
if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) { if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) {
// Crossfade: fade-out de la pista actual + fade-in de la nueva
JA_CrossfadeMusic(resource, crossfade_ms, loop); JA_CrossfadeMusic(resource, crossfade_ms, loop);
} else { } else {
// Cambio inmediato
if (music_.state == MusicState::PLAYING) { if (music_.state == MusicState::PLAYING) {
JA_StopMusic(); JA_StopMusic();
} }
@@ -74,6 +84,24 @@ void Audio::playMusic(const std::string& name, const int loop, const int crossfa
music_.state = MusicState::PLAYING; music_.state = MusicState::PLAYING;
} }
// Reproduce la música por puntero (con crossfade opcional)
void Audio::playMusic(JA_Music_t* music, const int loop, const int crossfade_ms) {
if (!music_enabled_ || music == nullptr) return;
if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) {
JA_CrossfadeMusic(music, crossfade_ms, loop);
} else {
if (music_.state == MusicState::PLAYING) {
JA_StopMusic();
}
JA_PlayMusic(music, loop);
}
music_.name.clear(); // nom desconegut quan es passa per punter
music_.loop = (loop != 0);
music_.state = MusicState::PLAYING;
}
// Pausa la música // Pausa la música
void Audio::pauseMusic() { void Audio::pauseMusic() {
if (music_enabled_ && music_.state == MusicState::PLAYING) { if (music_enabled_ && music_.state == MusicState::PLAYING) {
@@ -91,7 +119,7 @@ void Audio::resumeMusic() {
} }
// Detiene la música // Detiene la música
void Audio::stopMusic() { // NOLINT(readability-make-member-function-const) void Audio::stopMusic() {
if (music_enabled_) { if (music_enabled_) {
JA_StopMusic(); JA_StopMusic();
music_.state = MusicState::STOPPED; music_.state = MusicState::STOPPED;
@@ -101,13 +129,13 @@ void Audio::stopMusic() { // NOLINT(readability-make-member-function-const)
// Reproduce un sonido por nombre // Reproduce un sonido por nombre
void Audio::playSound(const std::string& name, Group group) const { void Audio::playSound(const std::string& name, Group group) const {
if (sound_enabled_) { if (sound_enabled_) {
JA_PlaySound(Resource::Cache::get()->getSound(name), 0, static_cast<int>(group)); JA_PlaySound(AudioResource::getSound(name), 0, static_cast<int>(group));
} }
} }
// Reproduce un sonido por puntero directo // Reproduce un sonido por puntero directo
void Audio::playSound(JA_Sound_t* sound, Group group) const { void Audio::playSound(JA_Sound_t* sound, Group group) const {
if (sound_enabled_) { if (sound_enabled_ && sound != nullptr) {
JA_PlaySound(sound, 0, static_cast<int>(group)); JA_PlaySound(sound, 0, static_cast<int>(group));
} }
} }
@@ -142,7 +170,7 @@ auto Audio::getRealMusicState() -> MusicState {
} }
} }
// Establece el volumen de los sonidos // Establece el volumen de los sonidos (float 0.0..1.0)
void Audio::setSoundVolume(float sound_volume, Group group) const { void Audio::setSoundVolume(float sound_volume, Group group) const {
if (sound_enabled_) { if (sound_enabled_) {
sound_volume = std::clamp(sound_volume, MIN_VOLUME, MAX_VOLUME); sound_volume = std::clamp(sound_volume, MIN_VOLUME, MAX_VOLUME);
@@ -151,7 +179,7 @@ void Audio::setSoundVolume(float sound_volume, Group group) const {
} }
} }
// Establece el volumen de la música // Establece el volumen de la música (float 0.0..1.0)
void Audio::setMusicVolume(float music_volume) const { void Audio::setMusicVolume(float music_volume) const {
if (music_enabled_) { if (music_enabled_) {
music_volume = std::clamp(music_volume, MIN_VOLUME, MAX_VOLUME); music_volume = std::clamp(music_volume, MIN_VOLUME, MAX_VOLUME);
@@ -176,24 +204,9 @@ void Audio::enable(bool value) {
// Inicializa SDL Audio // Inicializa SDL Audio
void Audio::initSDLAudio() { void Audio::initSDLAudio() {
if (!SDL_Init(SDL_INIT_AUDIO)) { if (!SDL_Init(SDL_INIT_AUDIO)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_AUDIO could not initialize! SDL Error: %s", SDL_GetError()); std::cout << "SDL_AUDIO could not initialize! SDL Error: " << SDL_GetError() << '\n';
} else { } else {
JA_Init(FREQUENCY, SDL_AUDIO_S16LE, 2); JA_Init(FREQUENCY, SDL_AUDIO_S16LE, 2);
enable(Options::audio.enabled); enable(Options::audio.enabled);
// Aplicar estado de música y sonido guardado en las opciones.
// enable() ya aplica los volúmenes, pero no toca music_enabled_/sound_enabled_.
// Si alguno está desactivado, hay que forzar el volumen a 0 en el backend.
if (!Options::audio.music.enabled) {
setMusicVolume(0.0F); // music_enabled_=true aún → llega a JA
enableMusic(false);
}
if (!Options::audio.sound.enabled) {
setSoundVolume(0.0F); // sound_enabled_=true aún → llega a JA
enableSound(false);
}
std::cout << "\n** AUDIO SYSTEM **\n";
std::cout << "Audio system initialized successfully\n";
} }
} }

View File

@@ -5,6 +5,10 @@
#include <utility> // Para move #include <utility> // Para move
// --- Clase Audio: gestor de audio (singleton) --- // --- Clase Audio: gestor de audio (singleton) ---
// Implementació canònica, byte-idèntica entre projectes.
// Els volums es manegen internament com a float 0.01.0; la capa de
// presentació (menús, notificacions) usa les helpers toPercent/fromPercent
// per mostrar 0100 a l'usuari.
class Audio { class Audio {
public: public:
// --- Enums --- // --- Enums ---
@@ -21,10 +25,11 @@ class Audio {
}; };
// --- Constantes --- // --- Constantes ---
static constexpr float MAX_VOLUME = 1.0F; // Volumen máximo static constexpr float MAX_VOLUME = 1.0F; // Volumen máximo (float 0..1)
static constexpr float MIN_VOLUME = 0.0F; // Volumen mínimo static constexpr float MIN_VOLUME = 0.0F; // Volumen mínimo (float 0..1)
static constexpr float VOLUME_STEP = 0.05F; // Pas estàndard per a UI (5%)
static constexpr int FREQUENCY = 48000; // Frecuencia de audio static constexpr int FREQUENCY = 48000; // Frecuencia de audio
static constexpr int DEFAULT_CROSSFADE_MS = 1500; // Duración del crossfade por defecto (ms) static constexpr int DEFAULT_CROSSFADE_MS = 1500; // Duració del crossfade per defecte (ms)
// --- Singleton --- // --- Singleton ---
static void init(); // Inicializa el objeto Audio static void init(); // Inicializa el objeto Audio
@@ -36,7 +41,8 @@ class Audio {
static void update(); // Actualización del sistema de audio static void update(); // Actualización del sistema de audio
// --- Control de música --- // --- Control de música ---
void playMusic(const std::string& name, int loop = -1, int crossfade_ms = 0); // Reproducir música (con crossfade opcional) void playMusic(const std::string& name, int loop = -1, int crossfade_ms = 0); // Reproducir música por nombre (con crossfade opcional)
void playMusic(struct JA_Music_t* music, int loop = -1, int crossfade_ms = 0); // Reproducir música por puntero (con crossfade opcional)
void pauseMusic(); // Pausar reproducción de música void pauseMusic(); // Pausar reproducción de música
void resumeMusic(); // Continua la música pausada void resumeMusic(); // Continua la música pausada
void stopMusic(); // Detener completamente la música void stopMusic(); // Detener completamente la música
@@ -47,10 +53,19 @@ class Audio {
void playSound(struct JA_Sound_t* sound, Group group = Group::GAME) const; // Reproducir sonido puntual por puntero void playSound(struct JA_Sound_t* sound, Group group = Group::GAME) const; // Reproducir sonido puntual por puntero
void stopAllSounds() const; // Detener todos los sonidos void stopAllSounds() const; // Detener todos los sonidos
// --- Control de volumen --- // --- Control de volumen (API interna: float 0.0..1.0) ---
void setSoundVolume(float volume, Group group = Group::ALL) const; // Ajustar volumen de efectos void setSoundVolume(float volume, Group group = Group::ALL) const; // Ajustar volumen de efectos
void setMusicVolume(float volume) const; // Ajustar volumen de música void setMusicVolume(float volume) const; // Ajustar volumen de música
// --- Helpers de conversió per a la capa de presentació ---
// UI (menús, notificacions) manega enters 0..100; internament viu float 0..1.
static constexpr auto toPercent(float volume) -> int {
return static_cast<int>(volume * 100.0F + 0.5F);
}
static constexpr auto fromPercent(int percent) -> float {
return static_cast<float>(percent) / 100.0F;
}
// --- Configuración general --- // --- Configuración general ---
void enable(bool value); // Establecer estado general void enable(bool value); // Establecer estado general
void toggleEnabled() { enabled_ = !enabled_; } // Alternar estado general void toggleEnabled() { enabled_ = !enabled_; } // Alternar estado general

View File

@@ -0,0 +1,13 @@
#include "core/audio/audio_adapter.hpp"
#include "core/resources/resource_cache.hpp"
namespace AudioResource {
JA_Music_t* getMusic(const std::string& name) {
return Resource::Cache::get()->getMusic(name);
}
JA_Sound_t* getSound(const std::string& name) {
return Resource::Cache::get()->getSound(name);
}
} // namespace AudioResource

View File

@@ -0,0 +1,17 @@
#pragma once
// --- Audio Resource Adapter ---
// Aquest fitxer exposa una interfície comuna a Audio per obtenir JA_Music_t* /
// JA_Sound_t* per nom. Cada projecte la implementa en audio_adapter.cpp
// delegant al seu singleton de recursos (Resource::get(), Resource::Cache::get(),
// etc.). Això permet que audio.hpp/audio.cpp siguin idèntics entre projectes.
#include <string> // Para string
struct JA_Music_t;
struct JA_Sound_t;
namespace AudioResource {
JA_Music_t* getMusic(const std::string& name);
JA_Sound_t* getSound(const std::string& name);
} // namespace AudioResource

View File

@@ -3,24 +3,41 @@
// --- Includes --- // --- Includes ---
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <stdint.h> // Para uint32_t, uint8_t #include <stdint.h> // Para uint32_t, uint8_t
#include <stdio.h> // Para NULL, fseek, printf, fclose, fopen, fread, ftell, FILE, SEEK_END, SEEK_SET #include <stdio.h> // Para NULL, fseek, fclose, fopen, fread, ftell, FILE, SEEK_END, SEEK_SET
#include <stdlib.h> // Para free, malloc #include <stdlib.h> // Para free, malloc
#include <string.h> // Para strcpy, strlen
#include <iostream> // Para std::cout
#include <memory> // Para std::unique_ptr
#include <string> // Para std::string
#include <vector> // Para std::vector
#define STB_VORBIS_HEADER_ONLY #define STB_VORBIS_HEADER_ONLY
#include "external/stb_vorbis.h" // Para stb_vorbis_open_memory i streaming #include "external/stb_vorbis.c" // Para stb_vorbis_open_memory i streaming
// Deleter stateless per a buffers reservats amb `SDL_malloc` / `SDL_LoadWAV*`.
// Compatible amb `std::unique_ptr<Uint8[], SDLFreeDeleter>` — zero size
// overhead gràcies a EBO, igual que un unique_ptr amb default_delete.
struct SDLFreeDeleter {
void operator()(Uint8* p) const noexcept {
if (p) SDL_free(p);
}
};
// --- Public Enums --- // --- Public Enums ---
enum JA_Channel_state { JA_CHANNEL_INVALID, enum JA_Channel_state {
JA_CHANNEL_INVALID,
JA_CHANNEL_FREE, JA_CHANNEL_FREE,
JA_CHANNEL_PLAYING, JA_CHANNEL_PLAYING,
JA_CHANNEL_PAUSED, JA_CHANNEL_PAUSED,
JA_SOUND_DISABLED }; JA_SOUND_DISABLED,
enum JA_Music_state { JA_MUSIC_INVALID, };
enum JA_Music_state {
JA_MUSIC_INVALID,
JA_MUSIC_PLAYING, JA_MUSIC_PLAYING,
JA_MUSIC_PAUSED, JA_MUSIC_PAUSED,
JA_MUSIC_STOPPED, JA_MUSIC_STOPPED,
JA_MUSIC_DISABLED }; JA_MUSIC_DISABLED,
};
// --- Struct Definitions --- // --- Struct Definitions ---
#define JA_MAX_SIMULTANEOUS_CHANNELS 20 #define JA_MAX_SIMULTANEOUS_CHANNELS 20
@@ -29,7 +46,9 @@ enum JA_Music_state { JA_MUSIC_INVALID,
struct JA_Sound_t { struct JA_Sound_t {
SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000}; SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
Uint32 length{0}; Uint32 length{0};
Uint8* buffer{NULL}; // Buffer descomprimit (PCM) propietat del sound. Reservat per SDL_LoadWAV
// via SDL_malloc; el deleter `SDLFreeDeleter` allibera amb SDL_free.
std::unique_ptr<Uint8[], SDLFreeDeleter> buffer;
}; };
struct JA_Channel_t { struct JA_Channel_t {
@@ -44,21 +63,22 @@ struct JA_Channel_t {
struct JA_Music_t { struct JA_Music_t {
SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000}; SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
// OGG comprimit en memòria. Propietat nostra; es copia des del fitxer una // OGG comprimit en memòria. Propietat nostra; es copia des del buffer
// sola vegada en JA_LoadMusic i es descomprimix en chunks per streaming. // d'entrada una sola vegada en JA_LoadMusic i es descomprimix en chunks
Uint8* ogg_data{nullptr}; // per streaming. Com que stb_vorbis guarda un punter persistent al
Uint32 ogg_length{0}; // `.data()` d'aquest vector, no el podem resize'jar un cop establert
stb_vorbis* vorbis{nullptr}; // Handle del decoder, viu tot el cicle del JA_Music_t // (una reallocation invalidaria el punter que el decoder conserva).
std::vector<Uint8> ogg_data;
stb_vorbis* vorbis{nullptr}; // handle del decoder, viu tot el cicle del JA_Music_t
char* filename{nullptr}; std::string filename;
int times{0}; // Loops restants (-1 = infinit, 0 = un sol play) int times{0}; // loops restants (-1 = infinit, 0 = un sol play)
SDL_AudioStream* stream{nullptr}; SDL_AudioStream* stream{nullptr};
JA_Music_state state{JA_MUSIC_INVALID}; JA_Music_state state{JA_MUSIC_INVALID};
}; };
// --- Internal Global State --- // --- Internal Global State (inline, C++17) ---
// Marcado 'inline' (C++17) para asegurar una única instancia.
inline JA_Music_t* current_music{nullptr}; inline JA_Music_t* current_music{nullptr};
inline JA_Channel_t channels[JA_MAX_SIMULTANEOUS_CHANNELS]; inline JA_Channel_t channels[JA_MAX_SIMULTANEOUS_CHANNELS];
@@ -108,15 +128,15 @@ inline int JA_FeedMusicChunk(JA_Music_t* music) {
if (!music || !music->vorbis || !music->stream) return 0; if (!music || !music->vorbis || !music->stream) return 0;
short chunk[JA_MUSIC_CHUNK_SHORTS]; short chunk[JA_MUSIC_CHUNK_SHORTS];
const int channels = music->spec.channels; const int num_channels = music->spec.channels;
const int samples_per_channel = stb_vorbis_get_samples_short_interleaved( const int samples_per_channel = stb_vorbis_get_samples_short_interleaved(
music->vorbis, music->vorbis,
channels, num_channels,
chunk, chunk,
JA_MUSIC_CHUNK_SHORTS); JA_MUSIC_CHUNK_SHORTS);
if (samples_per_channel <= 0) return 0; if (samples_per_channel <= 0) return 0;
const int bytes = samples_per_channel * channels * JA_MUSIC_BYTES_PER_SAMPLE; const int bytes = samples_per_channel * num_channels * JA_MUSIC_BYTES_PER_SAMPLE;
SDL_PutAudioStreamData(music->stream, chunk, bytes); SDL_PutAudioStreamData(music->stream, chunk, bytes);
return samples_per_channel; return samples_per_channel;
} }
@@ -205,7 +225,7 @@ inline void JA_Update() {
if (channels[i].state == JA_CHANNEL_PLAYING) { if (channels[i].state == JA_CHANNEL_PLAYING) {
if (channels[i].times != 0) { if (channels[i].times != 0) {
if ((Uint32)SDL_GetAudioStreamAvailable(channels[i].stream) < (channels[i].sound->length / 2)) { if ((Uint32)SDL_GetAudioStreamAvailable(channels[i].stream) < (channels[i].sound->length / 2)) {
SDL_PutAudioStreamData(channels[i].stream, channels[i].sound->buffer, channels[i].sound->length); SDL_PutAudioStreamData(channels[i].stream, channels[i].sound->buffer.get(), channels[i].sound->length);
if (channels[i].times > 0) channels[i].times--; if (channels[i].times > 0) channels[i].times--;
} }
} else { } else {
@@ -216,14 +236,10 @@ inline void JA_Update() {
} }
inline void JA_Init(const int freq, const SDL_AudioFormat format, const int num_channels) { inline void JA_Init(const int freq, const SDL_AudioFormat format, const int num_channels) {
#ifdef _DEBUG
SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG);
#endif
JA_audioSpec = {format, num_channels, freq}; JA_audioSpec = {format, num_channels, freq};
if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice); // Corregido: !sdlAudioDevice -> sdlAudioDevice if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice);
sdlAudioDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &JA_audioSpec); sdlAudioDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &JA_audioSpec);
if (sdlAudioDevice == 0) SDL_Log("Failed to initialize SDL audio!"); if (sdlAudioDevice == 0) std::cout << "Failed to initialize SDL audio!" << '\n';
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i) channels[i].state = JA_CHANNEL_FREE; for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i) channels[i].state = JA_CHANNEL_FREE;
for (int i = 0; i < JA_MAX_GROUPS; ++i) JA_soundVolume[i] = 0.5f; for (int i = 0; i < JA_MAX_GROUPS; ++i) JA_soundVolume[i] = 0.5f;
} }
@@ -242,26 +258,25 @@ inline void JA_Quit() {
inline JA_Music_t* JA_LoadMusic(const Uint8* buffer, Uint32 length) { inline JA_Music_t* JA_LoadMusic(const Uint8* buffer, Uint32 length) {
if (!buffer || length == 0) return nullptr; if (!buffer || length == 0) return nullptr;
// Còpia del OGG comprimit: stb_vorbis llig de forma persistent aquesta // Allocem el JA_Music_t primer per aprofitar el seu `std::vector<Uint8>`
// memòria mentre el handle estiga viu, així que hem de posseir-la nosaltres. // com a propietari del OGG comprimit. stb_vorbis guarda un punter
Uint8* ogg_copy = static_cast<Uint8*>(SDL_malloc(length)); // persistent al buffer; com que ací no el resize'jem, el .data() és
if (!ogg_copy) return nullptr; // estable durant tot el cicle de vida del music.
SDL_memcpy(ogg_copy, buffer, length); auto* music = new JA_Music_t();
music->ogg_data.assign(buffer, buffer + length);
int error = 0; int error = 0;
stb_vorbis* vorbis = stb_vorbis_open_memory(ogg_copy, static_cast<int>(length), &error, nullptr); music->vorbis = stb_vorbis_open_memory(music->ogg_data.data(),
if (!vorbis) { static_cast<int>(length),
SDL_free(ogg_copy); &error,
SDL_Log("JA_LoadMusic: stb_vorbis_open_memory failed (error %d)", error); nullptr);
if (!music->vorbis) {
std::cout << "JA_LoadMusic: stb_vorbis_open_memory failed (error " << error << ")" << '\n';
delete music;
return nullptr; return nullptr;
} }
auto* music = new JA_Music_t(); const stb_vorbis_info info = stb_vorbis_get_info(music->vorbis);
music->ogg_data = ogg_copy;
music->ogg_length = length;
music->vorbis = vorbis;
const stb_vorbis_info info = stb_vorbis_get_info(vorbis);
music->spec.channels = info.channels; music->spec.channels = info.channels;
music->spec.freq = static_cast<int>(info.sample_rate); music->spec.freq = static_cast<int>(info.sample_rate);
music->spec.format = SDL_AUDIO_S16; music->spec.format = SDL_AUDIO_S16;
@@ -270,31 +285,36 @@ inline JA_Music_t* JA_LoadMusic(const Uint8* buffer, Uint32 length) {
return music; return music;
} }
// Overload amb filename — els callers l'usen per poder comparar la música
// en curs amb JA_GetMusicFilename() i no rearrancar-la si ja és la mateixa.
inline JA_Music_t* JA_LoadMusic(Uint8* buffer, Uint32 length, const char* filename) {
JA_Music_t* music = JA_LoadMusic(static_cast<const Uint8*>(buffer), length);
if (music && filename) music->filename = filename;
return music;
}
inline JA_Music_t* JA_LoadMusic(const char* filename) { inline JA_Music_t* JA_LoadMusic(const char* filename) {
// [RZC 28/08/22] Carreguem primer el arxiu en memòria i després el descomprimim. Es algo més rapid. // Carreguem primer el arxiu en memòria i després el descomprimim.
FILE* f = fopen(filename, "rb"); FILE* f = fopen(filename, "rb");
if (!f) return NULL; // Añadida comprobación de apertura if (!f) return nullptr;
fseek(f, 0, SEEK_END); fseek(f, 0, SEEK_END);
long fsize = ftell(f); long fsize = ftell(f);
fseek(f, 0, SEEK_SET); fseek(f, 0, SEEK_SET);
auto* buffer = static_cast<Uint8*>(malloc(fsize + 1)); auto* buffer = static_cast<Uint8*>(malloc(fsize + 1));
if (!buffer) { // Añadida comprobación de malloc if (!buffer) {
fclose(f); fclose(f);
return NULL; return nullptr;
} }
if (fread(buffer, fsize, 1, f) != 1) { if (fread(buffer, fsize, 1, f) != 1) {
fclose(f); fclose(f);
free(buffer); free(buffer);
return NULL; return nullptr;
} }
fclose(f); fclose(f);
JA_Music_t* music = JA_LoadMusic(buffer, fsize); JA_Music_t* music = JA_LoadMusic(static_cast<const Uint8*>(buffer), static_cast<Uint32>(fsize));
if (music) { // Comprobar que JA_LoadMusic tuvo éxito if (music) {
music->filename = static_cast<char*>(malloc(strlen(filename) + 1)); music->filename = filename;
if (music->filename) {
strcpy(music->filename, filename);
}
} }
free(buffer); free(buffer);
@@ -317,7 +337,7 @@ inline void JA_PlayMusic(JA_Music_t* music, const int loop = -1) {
current_music->stream = SDL_CreateAudioStream(&current_music->spec, &JA_audioSpec); current_music->stream = SDL_CreateAudioStream(&current_music->spec, &JA_audioSpec);
if (!current_music->stream) { if (!current_music->stream) {
SDL_Log("Failed to create audio stream!"); std::cout << "Failed to create audio stream!" << '\n';
current_music->state = JA_MUSIC_STOPPED; current_music->state = JA_MUSIC_STOPPED;
return; return;
} }
@@ -326,18 +346,20 @@ inline void JA_PlayMusic(JA_Music_t* music, const int loop = -1) {
// Pre-cargem el buffer abans de bindejar per evitar un underrun inicial. // Pre-cargem el buffer abans de bindejar per evitar un underrun inicial.
JA_PumpMusic(current_music); JA_PumpMusic(current_music);
if (!SDL_BindAudioStream(sdlAudioDevice, current_music->stream)) printf("[ERROR] SDL_BindAudioStream failed!\n"); if (!SDL_BindAudioStream(sdlAudioDevice, current_music->stream)) {
std::cout << "[ERROR] SDL_BindAudioStream failed!" << '\n';
}
} }
inline char* JA_GetMusicFilename(const JA_Music_t* music = nullptr) { inline const char* JA_GetMusicFilename(const JA_Music_t* music = nullptr) {
if (!music) music = current_music; if (!music) music = current_music;
if (!music) return nullptr; // Añadida comprobación if (!music || music->filename.empty()) return nullptr;
return music->filename; return music->filename.c_str();
} }
inline void JA_PauseMusic() { inline void JA_PauseMusic() {
if (!JA_musicEnabled) return; if (!JA_musicEnabled) return;
if (!current_music || current_music->state != JA_MUSIC_PLAYING) return; // Comprobación mejorada if (!current_music || current_music->state != JA_MUSIC_PLAYING) return;
current_music->state = JA_MUSIC_PAUSED; current_music->state = JA_MUSIC_PAUSED;
SDL_UnbindAudioStream(current_music->stream); SDL_UnbindAudioStream(current_music->stream);
@@ -345,7 +367,7 @@ inline void JA_PauseMusic() {
inline void JA_ResumeMusic() { inline void JA_ResumeMusic() {
if (!JA_musicEnabled) return; if (!JA_musicEnabled) return;
if (!current_music || current_music->state != JA_MUSIC_PAUSED) return; // Comprobación mejorada if (!current_music || current_music->state != JA_MUSIC_PAUSED) return;
current_music->state = JA_MUSIC_PLAYING; current_music->state = JA_MUSIC_PLAYING;
SDL_BindAudioStream(sdlAudioDevice, current_music->stream); SDL_BindAudioStream(sdlAudioDevice, current_music->stream);
@@ -429,7 +451,7 @@ inline void JA_CrossfadeMusic(JA_Music_t* music, const int crossfade_ms, const i
stb_vorbis_seek_start(current_music->vorbis); stb_vorbis_seek_start(current_music->vorbis);
current_music->stream = SDL_CreateAudioStream(&current_music->spec, &JA_audioSpec); current_music->stream = SDL_CreateAudioStream(&current_music->spec, &JA_audioSpec);
if (!current_music->stream) { if (!current_music->stream) {
SDL_Log("Failed to create audio stream for crossfade!"); std::cout << "Failed to create audio stream for crossfade!" << '\n';
current_music->state = JA_MUSIC_STOPPED; current_music->state = JA_MUSIC_STOPPED;
return; return;
} }
@@ -456,8 +478,8 @@ inline void JA_DeleteMusic(JA_Music_t* music) {
} }
if (music->stream) SDL_DestroyAudioStream(music->stream); if (music->stream) SDL_DestroyAudioStream(music->stream);
if (music->vorbis) stb_vorbis_close(music->vorbis); if (music->vorbis) stb_vorbis_close(music->vorbis);
SDL_free(music->ogg_data); // ogg_data (std::vector) i filename (std::string) s'alliberen sols
free(music->filename); // filename se libera aquí // al destructor de JA_Music_t.
delete music; delete music;
} }
@@ -470,49 +492,40 @@ inline float JA_SetMusicVolume(float volume) {
} }
inline void JA_SetMusicPosition(float /*value*/) { inline void JA_SetMusicPosition(float /*value*/) {
// No implementat amb el backend de streaming. Mai va arribar a usar-se // No implementat amb el backend de streaming.
// en el codi existent, així que es manté com a stub.
} }
inline float JA_GetMusicPosition() { inline float JA_GetMusicPosition() {
// Veure nota a JA_SetMusicPosition.
return 0.0f; return 0.0f;
} }
inline void JA_EnableMusic(const bool value) { inline void JA_EnableMusic(const bool value) {
if (!value && current_music && (current_music->state == JA_MUSIC_PLAYING)) JA_StopMusic(); if (!value && current_music && (current_music->state == JA_MUSIC_PLAYING)) JA_StopMusic();
JA_musicEnabled = value; JA_musicEnabled = value;
} }
// --- Sound Functions --- // --- Sound Functions ---
inline JA_Sound_t* JA_NewSound(Uint8* buffer, Uint32 length) {
JA_Sound_t* sound = new JA_Sound_t();
sound->buffer = buffer;
sound->length = length;
// Nota: spec se queda con los valores por defecto.
return sound;
}
inline JA_Sound_t* JA_LoadSound(uint8_t* buffer, uint32_t size) { inline JA_Sound_t* JA_LoadSound(uint8_t* buffer, uint32_t size) {
JA_Sound_t* sound = new JA_Sound_t(); auto sound = std::make_unique<JA_Sound_t>();
if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), 1, &sound->spec, &sound->buffer, &sound->length)) { Uint8* raw = nullptr;
SDL_Log("Failed to load WAV from memory: %s", SDL_GetError()); if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), 1, &sound->spec, &raw, &sound->length)) {
delete sound; std::cout << "Failed to load WAV from memory: " << SDL_GetError() << '\n';
return nullptr; return nullptr;
} }
return sound; sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer
return sound.release();
} }
inline JA_Sound_t* JA_LoadSound(const char* filename) { inline JA_Sound_t* JA_LoadSound(const char* filename) {
JA_Sound_t* sound = new JA_Sound_t(); auto sound = std::make_unique<JA_Sound_t>();
if (!SDL_LoadWAV(filename, &sound->spec, &sound->buffer, &sound->length)) { Uint8* raw = nullptr;
SDL_Log("Failed to load WAV file: %s", SDL_GetError()); if (!SDL_LoadWAV(filename, &sound->spec, &raw, &sound->length)) {
delete sound; std::cout << "Failed to load WAV file: " << SDL_GetError() << '\n';
return nullptr; return nullptr;
} }
return sound; sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer
return sound.release();
} }
inline int JA_PlaySound(JA_Sound_t* sound, const int loop = 0, const int group = 0) { inline int JA_PlaySound(JA_Sound_t* sound, const int loop = 0, const int group = 0) {
@@ -532,22 +545,22 @@ inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int
if (!JA_soundEnabled || !sound) return -1; if (!JA_soundEnabled || !sound) return -1;
if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return -1; if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return -1;
JA_StopChannel(channel); // Detiene y limpia el canal si estaba en uso JA_StopChannel(channel);
channels[channel].sound = sound; channels[channel].sound = sound;
channels[channel].times = loop; channels[channel].times = loop;
channels[channel].pos = 0; channels[channel].pos = 0;
channels[channel].group = group; // Asignar grupo channels[channel].group = group;
channels[channel].state = JA_CHANNEL_PLAYING; channels[channel].state = JA_CHANNEL_PLAYING;
channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec); channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec);
if (!channels[channel].stream) { if (!channels[channel].stream) {
SDL_Log("Failed to create audio stream for sound!"); std::cout << "Failed to create audio stream for sound!" << '\n';
channels[channel].state = JA_CHANNEL_FREE; channels[channel].state = JA_CHANNEL_FREE;
return -1; return -1;
} }
SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length); SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer.get(), channels[channel].sound->length);
SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume[group]); SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume[group]);
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream); SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
@@ -559,7 +572,7 @@ inline void JA_DeleteSound(JA_Sound_t* sound) {
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) { for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
if (channels[i].sound == sound) JA_StopChannel(i); if (channels[i].sound == sound) JA_StopChannel(i);
} }
SDL_free(sound->buffer); // buffer es destrueix automàticament via RAII (SDLFreeDeleter).
delete sound; delete sound;
} }
@@ -605,7 +618,7 @@ inline void JA_StopChannel(const int channel) {
channels[i].stream = nullptr; channels[i].stream = nullptr;
channels[i].state = JA_CHANNEL_FREE; channels[i].state = JA_CHANNEL_FREE;
channels[i].pos = 0; channels[i].pos = 0;
channels[i].sound = NULL; channels[i].sound = nullptr;
} }
} }
} else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) { } else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) {
@@ -614,7 +627,7 @@ inline void JA_StopChannel(const int channel) {
channels[channel].stream = nullptr; channels[channel].stream = nullptr;
channels[channel].state = JA_CHANNEL_FREE; channels[channel].state = JA_CHANNEL_FREE;
channels[channel].pos = 0; channels[channel].pos = 0;
channels[channel].sound = NULL; channels[channel].sound = nullptr;
} }
} }
} }
@@ -626,8 +639,7 @@ inline JA_Channel_state JA_GetChannelState(const int channel) {
return channels[channel].state; return channels[channel].state;
} }
inline float JA_SetSoundVolume(float volume, const int group = -1) // -1 para todos los grupos inline float JA_SetSoundVolume(float volume, const int group = -1) {
{
const float v = SDL_clamp(volume, 0.0f, 1.0f); const float v = SDL_clamp(volume, 0.0f, 1.0f);
if (group == -1) { if (group == -1) {
@@ -637,10 +649,10 @@ inline float JA_SetSoundVolume(float volume, const int group = -1) // -1 para t
} else if (group >= 0 && group < JA_MAX_GROUPS) { } else if (group >= 0 && group < JA_MAX_GROUPS) {
JA_soundVolume[group] = v; JA_soundVolume[group] = v;
} else { } else {
return v; // Grupo inválido return v;
} }
// Aplicar volumen a canales activos // Aplicar volum als canals actius.
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) { for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
if ((channels[i].state == JA_CHANNEL_PLAYING) || (channels[i].state == JA_CHANNEL_PAUSED)) { if ((channels[i].state == JA_CHANNEL_PLAYING) || (channels[i].state == JA_CHANNEL_PAUSED)) {
if (group == -1 || channels[i].group == group) { if (group == -1 || channels[i].group == group) {
@@ -655,13 +667,13 @@ inline float JA_SetSoundVolume(float volume, const int group = -1) // -1 para t
inline void JA_EnableSound(const bool value) { inline void JA_EnableSound(const bool value) {
if (!value) { if (!value) {
JA_StopChannel(-1); // Detener todos los canales JA_StopChannel(-1);
} }
JA_soundEnabled = value; JA_soundEnabled = value;
} }
inline float JA_SetVolume(float volume) { inline float JA_SetVolume(float volume) {
float v = JA_SetMusicVolume(volume); float v = JA_SetMusicVolume(volume);
JA_SetSoundVolume(v, -1); // Aplicar a todos los grupos de sonido JA_SetSoundVolume(v, -1);
return v; return v;
} }

View File

@@ -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() {

View File

@@ -10,6 +10,48 @@
#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.
//
// OJO: el layout W3C "standard" NO se expone tal cual al joystick SDL. El
// driver de SDL3 (src/joystick/emscripten/SDL_sysjoystick.c) reempaqueta el
// gamepad W3C antes de entregarlo: los 4 botones del d-pad (W3C b12-b15) se
// convierten en un hat (hat 0), los 2 botones de triggers (W3C b6-b7) se
// convierten en ejes analógicos (a4-a5 si el mando tenía 4 ejes) y los
// botones restantes se renumeran compactándose. Por eso el mapping tiene que
// referirse a la numeración *después* del remap, no a la del W3C.
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,"
"back:b6,start:b7,"
"leftstick:b8,rightstick:b9,"
"dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,"
"leftx:a0,lefty:a1,rightx:a2,righty:a3,"
"lefttrigger:a4,righttrigger:a5,"
"platform:Emscripten",
guidStr,
name);
SDL_AddGamepadMapping(mapping);
#else
(void)jid;
#endif
}
// Singleton // Singleton
Input* Input::instance = nullptr; Input* Input::instance = nullptr;
@@ -397,6 +439,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 +450,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 +468,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 {};

View File

@@ -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

View File

@@ -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
@@ -13,7 +17,9 @@
#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

View File

@@ -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
@@ -145,6 +146,7 @@ class Screen {
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 registerEmscriptenEventCallbacks(); // Registra los callbacks nativos para restaurar el canvas en wasm (no-op fuera de emscripten)
void createText(); // Crea el objeto de texto void createText(); // Crea el objeto de texto
// Constructor y destructor // Constructor y destructor

View File

@@ -8,11 +8,11 @@
#include <iostream> // std::cout #include <iostream> // std::cout
#ifndef __APPLE__ #ifndef __APPLE__
#include "core/rendering/sdl3gpu/crtpi_frag_spv.h" #include "core/rendering/sdl3gpu/spv/crtpi_frag_spv.h"
#include "core/rendering/sdl3gpu/downscale_frag_spv.h" #include "core/rendering/sdl3gpu/spv/downscale_frag_spv.h"
#include "core/rendering/sdl3gpu/postfx_frag_spv.h" #include "core/rendering/sdl3gpu/spv/postfx_frag_spv.h"
#include "core/rendering/sdl3gpu/postfx_vert_spv.h" #include "core/rendering/sdl3gpu/spv/postfx_vert_spv.h"
#include "core/rendering/sdl3gpu/upscale_frag_spv.h" #include "core/rendering/sdl3gpu/spv/upscale_frag_spv.h"
#endif #endif
#ifdef __APPLE__ #ifdef __APPLE__

View File

@@ -0,0 +1,2 @@
DisableFormat: true
SortIncludes: Never

View File

@@ -0,0 +1,4 @@
# source/core/rendering/sdl3gpu/spv/.clang-tidy
Checks: '-*'
WarningsAsErrors: ''
HeaderFilterRegex: ''

View File

@@ -134,7 +134,7 @@ AnimatedSprite::AnimatedSprite(std::shared_ptr<Surface> surface, SDL_FRect pos)
: MovingSprite(std::move(surface), pos) { : MovingSprite(std::move(surface), pos) {
// animations_ queda buit (protegit per el guard de animate()) // animations_ queda buit (protegit per el guard de animate())
if (surface_) { if (surface_) {
clip_ = {.x = 0, .y = 0, .w = surface_->getWidth(), .h = surface_->getHeight()}; clip_ = {.x = 0, .y = 0, .w = static_cast<float>(surface_->getWidth()), .h = static_cast<float>(surface_->getHeight())};
} }
} }

View File

@@ -19,7 +19,7 @@ Sprite::Sprite() = default;
Sprite::Sprite(std::shared_ptr<Surface> surface) Sprite::Sprite(std::shared_ptr<Surface> surface)
: surface_(std::move(surface)), : surface_(std::move(surface)),
pos_{0.0F, 0.0F, surface_->getWidth(), surface_->getHeight()}, pos_{0.0F, 0.0F, static_cast<float>(surface_->getWidth()), static_cast<float>(surface_->getHeight())},
clip_(pos_) {} clip_(pos_) {}
// Muestra el sprite por pantalla // Muestra el sprite por pantalla

View File

@@ -129,7 +129,7 @@ auto Surface::loadSurface(const std::string& file_path) -> SurfaceData {
// Crear y devolver directamente el objeto SurfaceData // Crear y devolver directamente el objeto SurfaceData
printWithDots("Surface : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]"); printWithDots("Surface : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");
return {static_cast<float>(w), static_cast<float>(h), pixels}; return {static_cast<int>(w), static_cast<int>(h), pixels};
} }
// Carga una paleta desde un archivo // Carga una paleta desde un archivo
@@ -148,14 +148,14 @@ void Surface::setColor(int index, Uint32 color) {
} }
// Rellena la superficie con un color // Rellena la superficie con un color
void Surface::clear(Uint8 color) { void Surface::clear(Uint8 color) { // NOLINT(readability-convert-member-functions-to-static)
const size_t TOTAL_PIXELS = surface_data_->width * surface_data_->height; const size_t TOTAL_PIXELS = static_cast<size_t>(surface_data_->width) * static_cast<size_t>(surface_data_->height);
Uint8* data_ptr = surface_data_->data.get(); Uint8* data_ptr = surface_data_->data.get();
std::fill(data_ptr, data_ptr + TOTAL_PIXELS, color); std::fill(data_ptr, data_ptr + TOTAL_PIXELS, color);
} }
// Pone un pixel en la SurfaceData // Pone un pixel en la SurfaceData
void Surface::putPixel(int x, int y, Uint8 color) { void Surface::putPixel(int x, int y, Uint8 color) { // NOLINT(readability-convert-member-functions-to-static)
if (x < 0 || y < 0 || x >= surface_data_->width || y >= surface_data_->height) { if (x < 0 || y < 0 || x >= surface_data_->width || y >= surface_data_->height) {
return; // Coordenadas fuera de rango return; // Coordenadas fuera de rango
} }
@@ -165,39 +165,39 @@ void Surface::putPixel(int x, int y, Uint8 color) {
} }
// Obtiene el color de un pixel de la surface_data // Obtiene el color de un pixel de la surface_data
auto Surface::getPixel(int x, int y) -> Uint8 { return surface_data_->data.get()[x + (y * static_cast<int>(surface_data_->width))]; } auto Surface::getPixel(int x, int y) -> Uint8 { return surface_data_->data.get()[x + (y * surface_data_->width)]; }
// Dibuja un rectangulo relleno // Dibuja un rectangulo relleno
void Surface::fillRect(const SDL_FRect* rect, Uint8 color) { void Surface::fillRect(const SDL_FRect* rect, Uint8 color) { // NOLINT(readability-convert-member-functions-to-static)
// Limitar los valores del rectángulo al tamaño de la superficie // Limitar los valores del rectángulo al tamaño de la superficie
float x_start = std::max(0.0F, rect->x); float x_start = std::max(0.0F, rect->x);
float y_start = std::max(0.0F, rect->y); float y_start = std::max(0.0F, rect->y);
float x_end = std::min(rect->x + rect->w, surface_data_->width); float x_end = std::min(rect->x + rect->w, static_cast<float>(surface_data_->width));
float y_end = std::min(rect->y + rect->h, surface_data_->height); float y_end = std::min(rect->y + rect->h, static_cast<float>(surface_data_->height));
// Rellenar fila a fila con memset (memoria contigua por fila) // Rellenar fila a fila con memset (memoria contigua por fila)
Uint8* data_ptr = surface_data_->data.get(); Uint8* data_ptr = surface_data_->data.get();
const auto SURF_WIDTH = static_cast<size_t>(surface_data_->width); const int SURF_WIDTH = surface_data_->width;
const auto ROW_WIDTH = static_cast<size_t>(static_cast<int>(x_end) - static_cast<int>(x_start)); const int ROW_WIDTH = static_cast<int>(x_end) - static_cast<int>(x_start);
for (int y = static_cast<int>(y_start); y < static_cast<int>(y_end); ++y) { for (int y = static_cast<int>(y_start); y < static_cast<int>(y_end); ++y) {
std::memset(data_ptr + (static_cast<size_t>(y) * SURF_WIDTH) + static_cast<size_t>(x_start), color, ROW_WIDTH); std::memset(data_ptr + (y * SURF_WIDTH) + static_cast<int>(x_start), color, ROW_WIDTH);
} }
} }
// Dibuja el borde de un rectangulo // Dibuja el borde de un rectangulo
void Surface::drawRectBorder(const SDL_FRect* rect, Uint8 color) { void Surface::drawRectBorder(const SDL_FRect* rect, Uint8 color) { // NOLINT(readability-convert-member-functions-to-static)
// Limitar los valores del rectángulo al tamaño de la superficie // Limitar los valores del rectángulo al tamaño de la superficie
float x_start = std::max(0.0F, rect->x); float x_start = std::max(0.0F, rect->x);
float y_start = std::max(0.0F, rect->y); float y_start = std::max(0.0F, rect->y);
float x_end = std::min(rect->x + rect->w, surface_data_->width); float x_end = std::min(rect->x + rect->w, static_cast<float>(surface_data_->width));
float y_end = std::min(rect->y + rect->h, surface_data_->height); float y_end = std::min(rect->y + rect->h, static_cast<float>(surface_data_->height));
// Dibujar bordes horizontales con memset (líneas contiguas en memoria) // Dibujar bordes horizontales con memset (líneas contiguas en memoria)
Uint8* data_ptr = surface_data_->data.get(); Uint8* data_ptr = surface_data_->data.get();
const auto SURF_WIDTH = static_cast<size_t>(surface_data_->width); const int SURF_WIDTH = surface_data_->width;
const auto ROW_WIDTH = static_cast<size_t>(static_cast<int>(x_end) - static_cast<int>(x_start)); const int ROW_WIDTH = static_cast<int>(x_end) - static_cast<int>(x_start);
std::memset(data_ptr + (static_cast<size_t>(y_start) * SURF_WIDTH) + static_cast<size_t>(x_start), color, ROW_WIDTH); std::memset(data_ptr + (static_cast<int>(y_start) * SURF_WIDTH) + static_cast<int>(x_start), color, ROW_WIDTH);
std::memset(data_ptr + ((static_cast<size_t>(y_end) - 1) * SURF_WIDTH) + static_cast<size_t>(x_start), color, ROW_WIDTH); std::memset(data_ptr + ((static_cast<int>(y_end) - 1) * SURF_WIDTH) + static_cast<int>(x_start), color, ROW_WIDTH);
// Dibujar bordes verticales // Dibujar bordes verticales
for (int y = y_start; y < y_end; ++y) { for (int y = y_start; y < y_end; ++y) {
@@ -211,72 +211,38 @@ void Surface::drawRectBorder(const SDL_FRect* rect, Uint8 color) {
} }
} }
// Dibuja una linea // Dibuja una linea (Bresenham en enteros)
void Surface::drawLine(float x1, float y1, float x2, float y2, Uint8 color) { void Surface::drawLine(float x1, float y1, float x2, float y2, Uint8 color) { // NOLINT(readability-convert-member-functions-to-static)
// Calcula las diferencias int ix1 = static_cast<int>(std::lround(x1));
float dx = std::abs(x2 - x1); int iy1 = static_cast<int>(std::lround(y1));
float dy = std::abs(y2 - y1); const int IX2 = static_cast<int>(std::lround(x2));
const int IY2 = static_cast<int>(std::lround(y2));
// Determina la dirección del incremento const int DX = std::abs(IX2 - ix1);
float sx = (x1 < x2) ? 1 : -1; const int DY = std::abs(IY2 - iy1);
float sy = (y1 < y2) ? 1 : -1; const int SX = (ix1 < IX2) ? 1 : -1;
const int SY = (iy1 < IY2) ? 1 : -1;
float err = dx - dy; const int SURF_W = surface_data_->width;
const int SURF_H = surface_data_->height;
Uint8* data_ptr = surface_data_->data.get();
int err = DX - DY;
while (true) { while (true) {
// Asegúrate de no dibujar fuera de los límites de la superficie if (ix1 >= 0 && ix1 < SURF_W && iy1 >= 0 && iy1 < SURF_H) {
if (x1 >= 0 && x1 < surface_data_->width && y1 >= 0 && y1 < surface_data_->height) { data_ptr[ix1 + (iy1 * SURF_W)] = color;
surface_data_->data.get()[static_cast<size_t>(x1 + (y1 * surface_data_->width))] = color;
} }
if (ix1 == IX2 && iy1 == IY2) {
// Si alcanzamos el punto final, salimos
if (x1 == x2 && y1 == y2) {
break; break;
} }
int e2 = 2 * err; int e2 = 2 * err;
if (e2 > -dy) { if (e2 > -DY) {
err -= dy; err -= DY;
x1 += sx; ix1 += SX;
}
if (e2 < dx) {
err += dx;
y1 += sy;
}
}
}
void Surface::render(float dx, float dy, float sx, float sy, float w, float h) { // NOLINT(readability-make-member-function-const)
auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();
// Aplicar render offset
dx += Screen::get()->getRenderOffsetX();
dy += Screen::get()->getRenderOffsetY();
// Limitar la región para evitar accesos fuera de rango en origen
w = std::min(w, surface_data_->width - sx);
h = std::min(h, surface_data_->height - sy);
// Limitar la región para evitar accesos fuera de rango en destino
w = std::min(w, surface_data->width - dx);
h = std::min(h, surface_data->height - dy);
const Uint8* src_ptr = surface_data_->data.get();
Uint8* dst_ptr = surface_data->data.get();
for (int iy = 0; iy < h; ++iy) {
for (int ix = 0; ix < w; ++ix) {
// Verificar que las coordenadas de destino están dentro de los límites
if (int dest_x = dx + ix; dest_x >= 0 && dest_x < surface_data->width) {
if (int dest_y = dy + iy; dest_y >= 0 && dest_y < surface_data->height) {
int src_x = sx + ix;
int src_y = sy + iy;
Uint8 color = src_ptr[static_cast<size_t>(src_x + (src_y * surface_data_->width))];
if (color != static_cast<Uint8>(transparent_color_)) {
dst_ptr[static_cast<size_t>(dest_x + (dest_y * surface_data->width))] = sub_palette_[color];
}
}
} }
if (e2 < DX) {
err += DX;
iy1 += SY;
} }
} }
} }
@@ -284,44 +250,38 @@ void Surface::render(float dx, float dy, float sx, float sy, float w, float h) {
void Surface::render(int x, int y, SDL_FRect* src_rect, SDL_FlipMode flip) { // NOLINT(readability-make-member-function-const) void Surface::render(int x, int y, SDL_FRect* src_rect, SDL_FlipMode flip) { // NOLINT(readability-make-member-function-const)
auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData(); auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData();
// Aplicar render offset // Aplicar render offset (usado por transiciones entre pantallas)
x += Screen::get()->getRenderOffsetX(); x += Screen::get()->getRenderOffsetX();
y += Screen::get()->getRenderOffsetY(); y += Screen::get()->getRenderOffsetY();
// Determina la región de origen (clip) a renderizar // Determina la región de origen (clip) a renderizar
float sx = (src_rect != nullptr) ? src_rect->x : 0; float sx = (src_rect != nullptr) ? src_rect->x : 0;
float sy = (src_rect != nullptr) ? src_rect->y : 0; float sy = (src_rect != nullptr) ? src_rect->y : 0;
float w = (src_rect != nullptr) ? src_rect->w : surface_data_->width; float w = (src_rect != nullptr) ? src_rect->w : static_cast<float>(surface_data_->width);
float h = (src_rect != nullptr) ? src_rect->h : surface_data_->height; float h = (src_rect != nullptr) ? src_rect->h : static_cast<float>(surface_data_->height);
// Guardar dimensiones originales antes del clipping (necesarias para flip) // Limitar la región para evitar accesos fuera de rango (origen y destino)
float orig_w = (src_rect != nullptr) ? src_rect->w : surface_data_->width; w = std::min(w, static_cast<float>(surface_data_->width) - sx);
float orig_h = (src_rect != nullptr) ? src_rect->h : surface_data_->height; h = std::min(h, static_cast<float>(surface_data_->height) - sy);
w = std::min(w, static_cast<float>(surface_data_dest->width - x));
// Limitar la región para evitar accesos fuera de rango en origen h = std::min(h, static_cast<float>(surface_data_dest->height - y));
w = std::min(w, surface_data_->width - sx);
h = std::min(h, surface_data_->height - sy);
// Limitar la región para evitar accesos fuera de rango en destino
w = std::min(w, surface_data_dest->width - static_cast<float>(x));
h = std::min(h, surface_data_dest->height - static_cast<float>(y));
// Renderiza píxel por píxel aplicando el flip si es necesario // Renderiza píxel por píxel aplicando el flip si es necesario
const Uint8* src_ptr = surface_data_->data.get(); const Uint8* src_ptr = surface_data_->data.get();
Uint8* dst_ptr = surface_data_dest->data.get(); Uint8* dst_ptr = surface_data_dest->data.get();
for (int iy = 0; iy < h; ++iy) { for (int iy = 0; iy < h; ++iy) {
for (int ix = 0; ix < w; ++ix) { for (int ix = 0; ix < w; ++ix) {
// Coordenadas de origen (flip usa dimensiones originales, no clipped) // Coordenadas de origen
int src_x = (flip == SDL_FLIP_HORIZONTAL) ? static_cast<int>(sx + orig_w - 1 - ix) : static_cast<int>(sx + ix); int src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + w - 1 - ix) : (sx + ix);
int src_y = (flip == SDL_FLIP_VERTICAL) ? static_cast<int>(sy + orig_h - 1 - iy) : static_cast<int>(sy + iy); int src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + h - 1 - iy) : (sy + iy);
// Coordenadas de destino // Coordenadas de destino
int dest_x = x + ix; int dest_x = x + ix;
int dest_y = y + iy; int dest_y = y + iy;
// Verificar que las coordenadas están dentro de los límites // Verificar que las coordenadas de destino están dentro de los límites
if (dest_x >= 0 && dest_x < surface_data_dest->width && dest_y >= 0 && dest_y < surface_data_dest->height && if (dest_x >= 0 && dest_x < surface_data_dest->width && dest_y >= 0 && dest_y < surface_data_dest->height) {
src_x >= 0 && src_x < surface_data_->width && src_y >= 0 && src_y < surface_data_->height) { // Copia el píxel si no es transparente
Uint8 color = src_ptr[static_cast<size_t>(src_x + (src_y * surface_data_->width))]; Uint8 color = src_ptr[static_cast<size_t>(src_x + (src_y * surface_data_->width))];
if (color != static_cast<Uint8>(transparent_color_)) { if (color != static_cast<Uint8>(transparent_color_)) {
dst_ptr[static_cast<size_t>(dest_x + (dest_y * surface_data_dest->width))] = sub_palette_[color]; dst_ptr[static_cast<size_t>(dest_x + (dest_y * surface_data_dest->width))] = sub_palette_[color];
@@ -332,7 +292,7 @@ void Surface::render(int x, int y, SDL_FRect* src_rect, SDL_FlipMode flip) { //
} }
// Helper para calcular coordenadas con flip // Helper para calcular coordenadas con flip
void Surface::calculateFlippedCoords(int ix, int iy, float sx, float sy, float w, float h, SDL_FlipMode flip, int& src_x, int& src_y) { void Surface::calculateFlippedCoords(int ix, int iy, int sx, int sy, int w, int h, SDL_FlipMode flip, int& src_x, int& src_y) {
src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + w - 1 - ix) : (sx + ix); src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + w - 1 - ix) : (sx + ix);
src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + h - 1 - iy) : (sy + iy); src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + h - 1 - iy) : (sy + iy);
} }
@@ -363,9 +323,9 @@ void Surface::render(SDL_FRect* src_rect, SDL_FRect* dst_rect, SDL_FlipMode flip
float dx = (dst_rect != nullptr) ? dst_rect->x : 0; float dx = (dst_rect != nullptr) ? dst_rect->x : 0;
float dy = (dst_rect != nullptr) ? dst_rect->y : 0; float dy = (dst_rect != nullptr) ? dst_rect->y : 0;
// Aplicar render offset // Aplicar render offset (usado por transiciones entre pantallas)
dx += Screen::get()->getRenderOffsetX(); dx += static_cast<float>(Screen::get()->getRenderOffsetX());
dy += Screen::get()->getRenderOffsetY(); dy += static_cast<float>(Screen::get()->getRenderOffsetY());
float dw = (dst_rect != nullptr) ? dst_rect->w : sw; float dw = (dst_rect != nullptr) ? dst_rect->w : sw;
float dh = (dst_rect != nullptr) ? dst_rect->h : sh; float dh = (dst_rect != nullptr) ? dst_rect->h : sh;
@@ -406,7 +366,7 @@ void Surface::render(SDL_FRect* src_rect, SDL_FRect* dst_rect, SDL_FlipMode flip
void Surface::renderWithColorReplace(int x, int y, Uint8 source_color, Uint8 target_color, SDL_FRect* src_rect, SDL_FlipMode flip) const { void Surface::renderWithColorReplace(int x, int y, Uint8 source_color, Uint8 target_color, SDL_FRect* src_rect, SDL_FlipMode flip) const {
auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData(); auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();
// Aplicar render offset // Aplicar render offset (usado por transiciones entre pantallas)
x += Screen::get()->getRenderOffsetX(); x += Screen::get()->getRenderOffsetX();
y += Screen::get()->getRenderOffsetY(); y += Screen::get()->getRenderOffsetY();
@@ -436,11 +396,11 @@ void Surface::renderWithColorReplace(int x, int y, Uint8 source_color, Uint8 tar
continue; // Saltar píxeles fuera del rango del destino continue; // Saltar píxeles fuera del rango del destino
} }
// Copia el píxel si no es transparente // Copia el píxel si no es transparente; aplica sub_palette_ como el resto de render*
Uint8 color = surface_data_->data.get()[static_cast<size_t>(src_x + (src_y * surface_data_->width))]; Uint8 color = surface_data_->data.get()[static_cast<size_t>(src_x + (src_y * surface_data_->width))];
if (color != static_cast<Uint8>(transparent_color_)) { if (color != static_cast<Uint8>(transparent_color_)) {
surface_data->data[dest_x + (dest_y * surface_data->width)] = surface_data->data[dest_x + (dest_y * surface_data->width)] =
(color == source_color) ? target_color : color; (color == source_color) ? target_color : sub_palette_[color];
} }
} }
} }
@@ -467,21 +427,21 @@ static auto computeFadeDensity(int screen_y, int fade_h, int canvas_height) -> f
} }
// Render amb dissolució als cantons superior/inferior (hash 2D, sense parpelleig) // Render amb dissolució als cantons superior/inferior (hash 2D, sense parpelleig)
void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, const SDL_FRect* src_rect) const { void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, SDL_FRect* src_rect) const {
// Aplicar render offset // Aplicar render offset (usado per transicions entre pantalles)
x += Screen::get()->getRenderOffsetX(); x += Screen::get()->getRenderOffsetX();
y += Screen::get()->getRenderOffsetY(); y += Screen::get()->getRenderOffsetY();
const int SX = (src_rect != nullptr) ? static_cast<int>(src_rect->x) : 0; const int SX = (src_rect != nullptr) ? static_cast<int>(src_rect->x) : 0;
const int SY = (src_rect != nullptr) ? static_cast<int>(src_rect->y) : 0; const int SY = (src_rect != nullptr) ? static_cast<int>(src_rect->y) : 0;
const int SW = (src_rect != nullptr) ? static_cast<int>(src_rect->w) : static_cast<int>(surface_data_->width); const int SW = (src_rect != nullptr) ? static_cast<int>(src_rect->w) : surface_data_->width;
const int SH = (src_rect != nullptr) ? static_cast<int>(src_rect->h) : static_cast<int>(surface_data_->height); const int SH = (src_rect != nullptr) ? static_cast<int>(src_rect->h) : surface_data_->height;
auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData(); auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData();
for (int row = 0; row < SH; row++) { for (int row = 0; row < SH; row++) {
const int SCREEN_Y = y + row; const int SCREEN_Y = y + row;
if (SCREEN_Y < 0 || SCREEN_Y >= static_cast<int>(surface_data_dest->height)) { if (SCREEN_Y < 0 || SCREEN_Y >= surface_data_dest->height) {
continue; continue;
} }
@@ -489,11 +449,11 @@ void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height
for (int col = 0; col < SW; col++) { for (int col = 0; col < SW; col++) {
const int SCREEN_X = x + col; const int SCREEN_X = x + col;
if (SCREEN_X < 0 || SCREEN_X >= static_cast<int>(surface_data_dest->width)) { if (SCREEN_X < 0 || SCREEN_X >= surface_data_dest->width) {
continue; continue;
} }
const Uint8 COLOR = surface_data_->data[((SY + row) * static_cast<int>(surface_data_->width)) + (SX + col)]; const Uint8 COLOR = surface_data_->data[((SY + row) * surface_data_->width) + (SX + col)];
if (COLOR == static_cast<Uint8>(transparent_color_)) { if (COLOR == static_cast<Uint8>(transparent_color_)) {
continue; continue;
} }
@@ -502,27 +462,27 @@ void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height
continue; // Pixel tapat per la zona de fade continue; // Pixel tapat per la zona de fade
} }
surface_data_dest->data[SCREEN_X + (SCREEN_Y * static_cast<int>(surface_data_dest->width))] = sub_palette_[COLOR]; surface_data_dest->data[SCREEN_X + (SCREEN_Y * surface_data_dest->width)] = sub_palette_[COLOR];
} }
} }
} }
// Idem però reemplaçant un color índex // Idem però reemplaçant un color índex
void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color, const SDL_FRect* src_rect) const { void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color, SDL_FRect* src_rect) const {
// Aplicar render offset // Aplicar render offset (usado per transicions entre pantalles)
x += Screen::get()->getRenderOffsetX(); x += Screen::get()->getRenderOffsetX();
y += Screen::get()->getRenderOffsetY(); y += Screen::get()->getRenderOffsetY();
const int SX = (src_rect != nullptr) ? static_cast<int>(src_rect->x) : 0; const int SX = (src_rect != nullptr) ? static_cast<int>(src_rect->x) : 0;
const int SY = (src_rect != nullptr) ? static_cast<int>(src_rect->y) : 0; const int SY = (src_rect != nullptr) ? static_cast<int>(src_rect->y) : 0;
const int SW = (src_rect != nullptr) ? static_cast<int>(src_rect->w) : static_cast<int>(surface_data_->width); const int SW = (src_rect != nullptr) ? static_cast<int>(src_rect->w) : surface_data_->width;
const int SH = (src_rect != nullptr) ? static_cast<int>(src_rect->h) : static_cast<int>(surface_data_->height); const int SH = (src_rect != nullptr) ? static_cast<int>(src_rect->h) : surface_data_->height;
auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData(); auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData();
for (int row = 0; row < SH; row++) { for (int row = 0; row < SH; row++) {
const int SCREEN_Y = y + row; const int SCREEN_Y = y + row;
if (SCREEN_Y < 0 || SCREEN_Y >= static_cast<int>(surface_data_dest->height)) { if (SCREEN_Y < 0 || SCREEN_Y >= surface_data_dest->height) {
continue; continue;
} }
@@ -530,11 +490,11 @@ void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height
for (int col = 0; col < SW; col++) { for (int col = 0; col < SW; col++) {
const int SCREEN_X = x + col; const int SCREEN_X = x + col;
if (SCREEN_X < 0 || SCREEN_X >= static_cast<int>(surface_data_dest->width)) { if (SCREEN_X < 0 || SCREEN_X >= surface_data_dest->width) {
continue; continue;
} }
const Uint8 COLOR = surface_data_->data[((SY + row) * static_cast<int>(surface_data_->width)) + (SX + col)]; const Uint8 COLOR = surface_data_->data[((SY + row) * surface_data_->width) + (SX + col)];
if (COLOR == static_cast<Uint8>(transparent_color_)) { if (COLOR == static_cast<Uint8>(transparent_color_)) {
continue; continue;
} }
@@ -544,7 +504,7 @@ void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height
} }
const Uint8 OUT_COLOR = (COLOR == source_color) ? target_color : sub_palette_[COLOR]; const Uint8 OUT_COLOR = (COLOR == source_color) ? target_color : sub_palette_[COLOR];
surface_data_dest->data[SCREEN_X + (SCREEN_Y * static_cast<int>(surface_data_dest->width))] = OUT_COLOR; surface_data_dest->data[SCREEN_X + (SCREEN_Y * surface_data_dest->width)] = OUT_COLOR;
} }
} }
} }
@@ -553,8 +513,8 @@ void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height
void Surface::toARGBBuffer(Uint32* buffer) const { void Surface::toARGBBuffer(Uint32* buffer) const {
if (!surface_data_ || !surface_data_->data || (buffer == nullptr)) { return; } if (!surface_data_ || !surface_data_->data || (buffer == nullptr)) { return; }
const int WIDTH = static_cast<int>(surface_data_->width); const int WIDTH = surface_data_->width;
const int HEIGHT = static_cast<int>(surface_data_->height); const int HEIGHT = surface_data_->height;
const Uint8* src = surface_data_->data.get(); const Uint8* src = surface_data_->data.get();
// Obtenemos el tamaño de la paleta para evitar accesos fuera de rango // Obtenemos el tamaño de la paleta para evitar accesos fuera de rango
@@ -573,7 +533,7 @@ void Surface::toARGBBuffer(Uint32* buffer) const {
} }
// Vuelca la superficie a una textura // Vuelca la superficie a una textura
void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture) { void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture) { // NOLINT(readability-convert-member-functions-to-static)
if ((renderer == nullptr) || (texture == nullptr) || !surface_data_) { if ((renderer == nullptr) || (texture == nullptr) || !surface_data_) {
throw std::runtime_error("Renderer or texture is null."); throw std::runtime_error("Renderer or texture is null.");
} }
@@ -599,8 +559,8 @@ void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture) {
const int WIDTH = surface_data_->width; const int WIDTH = surface_data_->width;
const int HEIGHT = surface_data_->height; const int HEIGHT = surface_data_->height;
for (int y = 0; y < HEIGHT; ++y) { for (int y = 0; y < HEIGHT; ++y) {
const Uint8* src_row = src + (static_cast<size_t>(y) * static_cast<size_t>(WIDTH)); const Uint8* src_row = src + (y * WIDTH);
Uint32* dst_row = pixels + (static_cast<size_t>(y) * static_cast<size_t>(row_stride)); Uint32* dst_row = pixels + (y * row_stride);
for (int x = 0; x < WIDTH; ++x) { for (int x = 0; x < WIDTH; ++x) {
dst_row[x] = pal[src_row[x]]; dst_row[x] = pal[src_row[x]];
} }
@@ -615,7 +575,7 @@ void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture) {
} }
// Vuelca la superficie a una textura // Vuelca la superficie a una textura
void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FRect* src_rect, SDL_FRect* dest_rect) { void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FRect* src_rect, SDL_FRect* dest_rect) { // NOLINT(readability-convert-member-functions-to-static)
if ((renderer == nullptr) || (texture == nullptr) || !surface_data_) { if ((renderer == nullptr) || (texture == nullptr) || !surface_data_) {
throw std::runtime_error("Renderer or texture is null."); throw std::runtime_error("Renderer or texture is null.");
} }
@@ -648,8 +608,8 @@ void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FR
const int WIDTH = surface_data_->width; const int WIDTH = surface_data_->width;
const int HEIGHT = surface_data_->height; const int HEIGHT = surface_data_->height;
for (int y = 0; y < HEIGHT; ++y) { for (int y = 0; y < HEIGHT; ++y) {
const Uint8* src_row = src + (static_cast<size_t>(y) * static_cast<size_t>(WIDTH)); const Uint8* src_row = src + (y * WIDTH);
Uint32* dst_row = pixels + (static_cast<size_t>(y) * static_cast<size_t>(row_stride)); Uint32* dst_row = pixels + (y * row_stride);
for (int x = 0; x < WIDTH; ++x) { for (int x = 0; x < WIDTH; ++x) {
dst_row[x] = pal[src_row[x]]; dst_row[x] = pal[src_row[x]];
} }
@@ -664,12 +624,9 @@ void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FR
} }
// Realiza un efecto de fundido en la paleta principal // Realiza un efecto de fundido en la paleta principal
auto Surface::fadePalette() -> bool { auto Surface::fadePalette() -> bool { // NOLINT(readability-convert-member-functions-to-static)
// Verificar que el tamaño mínimo de palette_ sea adecuado
static constexpr int PALETTE_SIZE = 19; static constexpr int PALETTE_SIZE = 19;
if (sizeof(palette_) / sizeof(palette_[0]) < PALETTE_SIZE) { static_assert(std::tuple_size_v<Palette> >= PALETTE_SIZE, "Palette size is insufficient for fadePalette operation.");
throw std::runtime_error("Palette size is insufficient for fadePalette operation.");
}
// Desplazar colores (pares e impares) // Desplazar colores (pares e impares)
for (int i = 18; i > 1; --i) { for (int i = 18; i > 1; --i) {
@@ -684,7 +641,7 @@ auto Surface::fadePalette() -> bool {
} }
// Realiza un efecto de fundido en la paleta secundaria // Realiza un efecto de fundido en la paleta secundaria
auto Surface::fadeSubPalette(Uint32 delay) -> bool { auto Surface::fadeSubPalette(Uint32 delay) -> bool { // NOLINT(readability-convert-member-functions-to-static)
// Variable estática para almacenar el último tick // Variable estática para almacenar el último tick
static Uint32 last_tick_ = 0; static Uint32 last_tick_ = 0;
@@ -699,11 +656,8 @@ auto Surface::fadeSubPalette(Uint32 delay) -> bool {
// Actualizar el último tick // Actualizar el último tick
last_tick_ = current_tick; last_tick_ = current_tick;
// Verificar que el tamaño mínimo de sub_palette_ sea adecuado
static constexpr int SUB_PALETTE_SIZE = 19; static constexpr int SUB_PALETTE_SIZE = 19;
if (sizeof(sub_palette_) / sizeof(sub_palette_[0]) < SUB_PALETTE_SIZE) { static_assert(std::tuple_size_v<SubPalette> >= SUB_PALETTE_SIZE, "Sub-palette size is insufficient for fadeSubPalette operation.");
throw std::runtime_error("Palette size is insufficient for fadePalette operation.");
}
// Desplazar colores (pares e impares) // Desplazar colores (pares e impares)
for (int i = 18; i > 1; --i) { for (int i = 18; i > 1; --i) {
@@ -718,4 +672,4 @@ auto Surface::fadeSubPalette(Uint32 delay) -> bool {
} }
// Restaura la sub paleta a su estado original // Restaura la sub paleta a su estado original
void Surface::resetSubPalette() { initializeSubPalette(sub_palette_); } void Surface::resetSubPalette() { initializeSubPalette(sub_palette_); } // NOLINT(readability-convert-member-functions-to-static)

View File

@@ -22,8 +22,8 @@ auto readPalFile(const std::string& file_path) -> Palette;
struct SurfaceData { struct SurfaceData {
std::shared_ptr<Uint8[]> data; // Usa std::shared_ptr para gestión automática std::shared_ptr<Uint8[]> data; // Usa std::shared_ptr para gestión automática
float width; // Ancho de la imagen int width; // Ancho de la imagen
float height; // Alto de la imagen int height; // Alto de la imagen
// Constructor por defecto // Constructor por defecto
SurfaceData() SurfaceData()
@@ -32,13 +32,13 @@ struct SurfaceData {
height(0) {} height(0) {}
// Constructor que inicializa dimensiones y asigna memoria // Constructor que inicializa dimensiones y asigna memoria
SurfaceData(float w, float h) SurfaceData(int w, int h)
: data(std::shared_ptr<Uint8[]>(new Uint8[static_cast<size_t>(w * h)](), std::default_delete<Uint8[]>())), : data(std::shared_ptr<Uint8[]>(new Uint8[static_cast<size_t>(w) * static_cast<size_t>(h)](), std::default_delete<Uint8[]>())),
width(w), width(w),
height(h) {} height(h) {}
// Constructor para inicializar directamente con datos // Constructor para inicializar directamente con datos
SurfaceData(float w, float h, std::shared_ptr<Uint8[]> pixels) SurfaceData(int w, int h, std::shared_ptr<Uint8[]> pixels)
: data(std::move(pixels)), : data(std::move(pixels)),
width(w), width(w),
height(h) {} height(h) {}
@@ -56,6 +56,9 @@ struct SurfaceData {
class Surface { class Surface {
private: private:
// shared_ptr porque render() accede al SurfaceData propio y al del renderer
// surface (ver getRendererSurface()) de forma efímera; con self-blit ambos
// pueden alias y el refcount evita free accidental durante el recorrido.
std::shared_ptr<SurfaceData> surface_data_; // Datos a dibujar std::shared_ptr<SurfaceData> surface_data_; // Datos a dibujar
Palette palette_; // Paleta para volcar la SurfaceData a una Textura Palette palette_; // Paleta para volcar la SurfaceData a una Textura
SubPalette sub_palette_; // Paleta para reindexar colores SubPalette sub_palette_; // Paleta para reindexar colores
@@ -77,7 +80,6 @@ class Surface {
void loadPalette(const Palette& palette); void loadPalette(const Palette& palette);
// Copia una región de la SurfaceData de origen a la SurfaceData de destino // Copia una región de la SurfaceData de origen a la SurfaceData de destino
void render(float dx, float dy, float sx, float sy, float w, float h);
void render(int x, int y, SDL_FRect* src_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE); void render(int x, int y, SDL_FRect* src_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);
void render(SDL_FRect* src_rect = nullptr, SDL_FRect* dst_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE); void render(SDL_FRect* src_rect = nullptr, SDL_FRect* dst_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);
@@ -85,10 +87,10 @@ class Surface {
void renderWithColorReplace(int x, int y, Uint8 source_color = 0, Uint8 target_color = 0, SDL_FRect* src_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE) const; void renderWithColorReplace(int x, int y, Uint8 source_color = 0, Uint8 target_color = 0, SDL_FRect* src_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE) const;
// Render amb dissolució als cantons superior/inferior (hash 2D, sense parpelleig) // Render amb dissolució als cantons superior/inferior (hash 2D, sense parpelleig)
void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, const SDL_FRect* src_rect = nullptr) const; void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, SDL_FRect* src_rect = nullptr) const;
// Idem però reemplaçant un color índex (per a sprites sobre fons del mateix color) // Idem però reemplaçant un color índex (per a sprites sobre fons del mateix color)
void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color, const SDL_FRect* src_rect = nullptr) const; void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color, SDL_FRect* src_rect = nullptr) const;
// Establece un color en la paleta // Establece un color en la paleta
void setColor(int index, Uint32 color); void setColor(int index, Uint32 color);
@@ -127,11 +129,10 @@ class Surface {
// Metodos para gestionar surface_data_ // Metodos para gestionar surface_data_
[[nodiscard]] auto getSurfaceData() const -> std::shared_ptr<SurfaceData> { return surface_data_; } [[nodiscard]] auto getSurfaceData() const -> std::shared_ptr<SurfaceData> { return surface_data_; }
void setSurfaceData(std::shared_ptr<SurfaceData> new_data) { surface_data_ = std::move(new_data); }
// Obtien ancho y alto // Obtien ancho y alto
[[nodiscard]] auto getWidth() const -> float { return surface_data_->width; } [[nodiscard]] auto getWidth() const -> int { return surface_data_->width; }
[[nodiscard]] auto getHeight() const -> float { return surface_data_->height; } [[nodiscard]] auto getHeight() const -> int { return surface_data_->height; }
// Color transparente // Color transparente
[[nodiscard]] auto getTransparentColor() const -> Uint8 { return transparent_color_; } [[nodiscard]] auto getTransparentColor() const -> Uint8 { return transparent_color_; }
@@ -139,7 +140,6 @@ class Surface {
// Paleta // Paleta
void setPalette(const std::array<Uint32, 256>& palette) { palette_ = palette; } void setPalette(const std::array<Uint32, 256>& palette) { palette_ = palette; }
[[nodiscard]] auto getPalette() const -> const Palette& { return palette_; }
[[nodiscard]] auto getPaletteColor(Uint8 index) const -> Uint32 { return palette_[index]; } [[nodiscard]] auto getPaletteColor(Uint8 index) const -> Uint32 { return palette_[index]; }
// Inicializa la sub paleta // Inicializa la sub paleta
@@ -147,7 +147,7 @@ class Surface {
private: private:
// Helper para calcular coordenadas con flip // Helper para calcular coordenadas con flip
static void calculateFlippedCoords(int ix, int iy, float sx, float sy, float w, float h, SDL_FlipMode flip, int& src_x, int& src_y); static void calculateFlippedCoords(int ix, int iy, int sx, int sy, int w, int h, SDL_FlipMode flip, int& src_x, int& src_y);
// Helper para copiar un pixel si no es transparente // Helper para copiar un pixel si no es transparente
void copyPixelIfNotTransparent(Uint8* dest_data, int dest_x, int dest_y, int dest_width, int src_x, int src_y) const; void copyPixelIfNotTransparent(Uint8* dest_data, int dest_x, int dest_y, int dest_width, int src_x, int src_y) const;

View File

@@ -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

View File

@@ -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) {

View File

@@ -1,4 +1,4 @@
// Ogg Vorbis audio decoder - v1.20 - public domain // Ogg Vorbis audio decoder - v1.22 - public domain
// http://nothings.org/stb_vorbis/ // http://nothings.org/stb_vorbis/
// //
// Original version written by Sean Barrett in 2007. // Original version written by Sean Barrett in 2007.
@@ -29,12 +29,15 @@
// Bernhard Wodo Evan Balster github:alxprd // Bernhard Wodo Evan Balster github:alxprd
// Tom Beaumont Ingo Leitgeb Nicolas Guillemot // Tom Beaumont Ingo Leitgeb Nicolas Guillemot
// Phillip Bennefall Rohit Thiago Goulart // Phillip Bennefall Rohit Thiago Goulart
// github:manxorist saga musix github:infatum // github:manxorist Saga Musix github:infatum
// Timur Gagiev Maxwell Koo Peter Waller // Timur Gagiev Maxwell Koo Peter Waller
// github:audinowho Dougall Johnson David Reid // github:audinowho Dougall Johnson David Reid
// github:Clownacy Pedro J. Estebanez Remi Verschelde // github:Clownacy Pedro J. Estebanez Remi Verschelde
// AnthoFoxo github:morlat Gabriel Ravier
// //
// Partial history: // Partial history:
// 1.22 - 2021-07-11 - various small fixes
// 1.21 - 2021-07-02 - fix bug for files with no comments
// 1.20 - 2020-07-11 - several small fixes // 1.20 - 2020-07-11 - several small fixes
// 1.19 - 2020-02-05 - warnings // 1.19 - 2020-02-05 - warnings
// 1.18 - 2020-02-02 - fix seek bugs; parse header comments; misc warnings etc. // 1.18 - 2020-02-02 - fix seek bugs; parse header comments; misc warnings etc.
@@ -220,6 +223,12 @@ extern int stb_vorbis_decode_frame_pushdata(
// channel. In other words, (*output)[0][0] contains the first sample from // channel. In other words, (*output)[0][0] contains the first sample from
// the first channel, and (*output)[1][0] contains the first sample from // the first channel, and (*output)[1][0] contains the first sample from
// the second channel. // the second channel.
//
// *output points into stb_vorbis's internal output buffer storage; these
// buffers are owned by stb_vorbis and application code should not free
// them or modify their contents. They are transient and will be overwritten
// once you ask for more data to get decoded, so be sure to grab any data
// you need before then.
extern void stb_vorbis_flush_pushdata(stb_vorbis *f); extern void stb_vorbis_flush_pushdata(stb_vorbis *f);
// inform stb_vorbis that your next datablock will not be contiguous with // inform stb_vorbis that your next datablock will not be contiguous with
@@ -579,7 +588,7 @@ enum STBVorbisError
#if defined(_MSC_VER) || defined(__MINGW32__) #if defined(_MSC_VER) || defined(__MINGW32__)
#include <malloc.h> #include <malloc.h>
#endif #endif
#if defined(__linux__) || defined(__linux) || defined(__EMSCRIPTEN__) || defined(__NEWLIB__) #if defined(__linux__) || defined(__linux) || defined(__sun__) || defined(__EMSCRIPTEN__) || defined(__NEWLIB__)
#include <alloca.h> #include <alloca.h>
#endif #endif
#else // STB_VORBIS_NO_CRT #else // STB_VORBIS_NO_CRT
@@ -646,6 +655,12 @@ typedef signed int int32;
typedef float codetype; typedef float codetype;
#ifdef _MSC_VER
#define STBV_NOTUSED(v) (void)(v)
#else
#define STBV_NOTUSED(v) (void)sizeof(v)
#endif
// @NOTE // @NOTE
// //
// Some arrays below are tagged "//varies", which means it's actually // Some arrays below are tagged "//varies", which means it's actually
@@ -1046,7 +1061,7 @@ static float float32_unpack(uint32 x)
uint32 sign = x & 0x80000000; uint32 sign = x & 0x80000000;
uint32 exp = (x & 0x7fe00000) >> 21; uint32 exp = (x & 0x7fe00000) >> 21;
double res = sign ? -(double)mantissa : (double)mantissa; double res = sign ? -(double)mantissa : (double)mantissa;
return (float) ldexp((float)res, exp-788); return (float) ldexp((float)res, (int)exp-788);
} }
@@ -1077,6 +1092,7 @@ static int compute_codewords(Codebook *c, uint8 *len, int n, uint32 *values)
// find the first entry // find the first entry
for (k=0; k < n; ++k) if (len[k] < NO_CODE) break; for (k=0; k < n; ++k) if (len[k] < NO_CODE) break;
if (k == n) { assert(c->sorted_entries == 0); return TRUE; } if (k == n) { assert(c->sorted_entries == 0); return TRUE; }
assert(len[k] < 32); // no error return required, code reading lens checks this
// add to the list // add to the list
add_entry(c, 0, k, m++, len[k], values); add_entry(c, 0, k, m++, len[k], values);
// add all available leaves // add all available leaves
@@ -1090,6 +1106,7 @@ static int compute_codewords(Codebook *c, uint8 *len, int n, uint32 *values)
uint32 res; uint32 res;
int z = len[i], y; int z = len[i], y;
if (z == NO_CODE) continue; if (z == NO_CODE) continue;
assert(z < 32); // no error return required, code reading lens checks this
// find lowest available leaf (should always be earliest, // find lowest available leaf (should always be earliest,
// which is what the specification calls for) // which is what the specification calls for)
// note that this property, and the fact we can never have // note that this property, and the fact we can never have
@@ -1099,12 +1116,10 @@ static int compute_codewords(Codebook *c, uint8 *len, int n, uint32 *values)
while (z > 0 && !available[z]) --z; while (z > 0 && !available[z]) --z;
if (z == 0) { return FALSE; } if (z == 0) { return FALSE; }
res = available[z]; res = available[z];
assert(z >= 0 && z < 32);
available[z] = 0; available[z] = 0;
add_entry(c, bit_reverse(res), i, m++, len[i], values); add_entry(c, bit_reverse(res), i, m++, len[i], values);
// propagate availability up the tree // propagate availability up the tree
if (z != len[i]) { if (z != len[i]) {
assert(len[i] >= 0 && len[i] < 32);
for (y=len[i]; y > z; --y) { for (y=len[i]; y > z; --y) {
assert(available[y] == 0); assert(available[y] == 0);
available[y] = res + (1 << (32-y)); available[y] = res + (1 << (32-y));
@@ -2577,34 +2592,33 @@ static void imdct_step3_inner_s_loop_ld654(int n, float *e, int i_off, float *A,
while (z > base) { while (z > base) {
float k00,k11; float k00,k11;
float l00,l11;
k00 = z[-0] - z[-8]; k00 = z[-0] - z[ -8];
k11 = z[-1] - z[-9]; k11 = z[-1] - z[ -9];
z[-0] = z[-0] + z[-8]; l00 = z[-2] - z[-10];
z[-1] = z[-1] + z[-9]; l11 = z[-3] - z[-11];
z[-8] = k00; z[ -0] = z[-0] + z[ -8];
z[-9] = k11 ; z[ -1] = z[-1] + z[ -9];
z[ -2] = z[-2] + z[-10];
z[ -3] = z[-3] + z[-11];
z[ -8] = k00;
z[ -9] = k11;
z[-10] = (l00+l11) * A2;
z[-11] = (l11-l00) * A2;
k00 = z[ -2] - z[-10]; k00 = z[ -4] - z[-12];
k11 = z[ -3] - z[-11];
z[ -2] = z[ -2] + z[-10];
z[ -3] = z[ -3] + z[-11];
z[-10] = (k00+k11) * A2;
z[-11] = (k11-k00) * A2;
k00 = z[-12] - z[ -4]; // reverse to avoid a unary negation
k11 = z[ -5] - z[-13]; k11 = z[ -5] - z[-13];
l00 = z[ -6] - z[-14];
l11 = z[ -7] - z[-15];
z[ -4] = z[ -4] + z[-12]; z[ -4] = z[ -4] + z[-12];
z[ -5] = z[ -5] + z[-13]; z[ -5] = z[ -5] + z[-13];
z[-12] = k11;
z[-13] = k00;
k00 = z[-14] - z[ -6]; // reverse to avoid a unary negation
k11 = z[ -7] - z[-15];
z[ -6] = z[ -6] + z[-14]; z[ -6] = z[ -6] + z[-14];
z[ -7] = z[ -7] + z[-15]; z[ -7] = z[ -7] + z[-15];
z[-14] = (k00+k11) * A2; z[-12] = k11;
z[-15] = (k00-k11) * A2; z[-13] = -k00;
z[-14] = (l11-l00) * A2;
z[-15] = (l00+l11) * -A2;
iter_54(z); iter_54(z);
iter_54(z-8); iter_54(z-8);
@@ -3069,6 +3083,7 @@ static int do_floor(vorb *f, Mapping *map, int i, int n, float *target, YTYPE *f
for (q=1; q < g->values; ++q) { for (q=1; q < g->values; ++q) {
j = g->sorted_order[q]; j = g->sorted_order[q];
#ifndef STB_VORBIS_NO_DEFER_FLOOR #ifndef STB_VORBIS_NO_DEFER_FLOOR
STBV_NOTUSED(step2_flag);
if (finalY[j] >= 0) if (finalY[j] >= 0)
#else #else
if (step2_flag[j]) if (step2_flag[j])
@@ -3171,6 +3186,7 @@ static int vorbis_decode_packet_rest(vorb *f, int *len, Mode *m, int left_start,
// WINDOWING // WINDOWING
STBV_NOTUSED(left_end);
n = f->blocksize[m->blockflag]; n = f->blocksize[m->blockflag];
map = &f->mapping[m->mapping]; map = &f->mapping[m->mapping];
@@ -3368,7 +3384,7 @@ static int vorbis_decode_packet_rest(vorb *f, int *len, Mode *m, int left_start,
// this isn't to spec, but spec would require us to read ahead // this isn't to spec, but spec would require us to read ahead
// and decode the size of all current frames--could be done, // and decode the size of all current frames--could be done,
// but presumably it's not a commonly used feature // but presumably it's not a commonly used feature
f->current_loc = -n2; // start of first frame is positioned for discard f->current_loc = 0u - n2; // start of first frame is positioned for discard (NB this is an intentional unsigned overflow/wrap-around)
// we might have to discard samples "from" the next frame too, // we might have to discard samples "from" the next frame too,
// if we're lapping a large block then a small at the start? // if we're lapping a large block then a small at the start?
f->discard_samples_deferred = n - right_end; f->discard_samples_deferred = n - right_end;
@@ -3642,8 +3658,10 @@ static int start_decoder(vorb *f)
f->vendor[len] = (char)'\0'; f->vendor[len] = (char)'\0';
//user comments //user comments
f->comment_list_length = get32_packet(f); f->comment_list_length = get32_packet(f);
if (f->comment_list_length > 0) { f->comment_list = NULL;
f->comment_list = (char**)setup_malloc(f, sizeof(char*) * (f->comment_list_length)); if (f->comment_list_length > 0)
{
f->comment_list = (char**) setup_malloc(f, sizeof(char*) * (f->comment_list_length));
if (f->comment_list == NULL) return error(f, VORBIS_outofmem); if (f->comment_list == NULL) return error(f, VORBIS_outofmem);
} }
@@ -3867,8 +3885,7 @@ static int start_decoder(vorb *f)
unsigned int div=1; unsigned int div=1;
for (k=0; k < c->dimensions; ++k) { for (k=0; k < c->dimensions; ++k) {
int off = (z / div) % c->lookup_values; int off = (z / div) % c->lookup_values;
float val = mults[off]; float val = mults[off]*c->delta_value + c->minimum_value + last;
val = mults[off]*c->delta_value + c->minimum_value + last;
c->multiplicands[j*c->dimensions + k] = val; c->multiplicands[j*c->dimensions + k] = val;
if (c->sequence_p) if (c->sequence_p)
last = val; last = val;
@@ -3951,7 +3968,7 @@ static int start_decoder(vorb *f)
if (g->class_masterbooks[j] >= f->codebook_count) return error(f, VORBIS_invalid_setup); if (g->class_masterbooks[j] >= f->codebook_count) return error(f, VORBIS_invalid_setup);
} }
for (k=0; k < 1 << g->class_subclasses[j]; ++k) { for (k=0; k < 1 << g->class_subclasses[j]; ++k) {
g->subclass_books[j][k] = get_bits(f,8)-1; g->subclass_books[j][k] = (int16)get_bits(f,8)-1;
if (g->subclass_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup); if (g->subclass_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup);
} }
} }
@@ -4509,6 +4526,7 @@ stb_vorbis *stb_vorbis_open_pushdata(
*error = VORBIS_need_more_data; *error = VORBIS_need_more_data;
else else
*error = p.error; *error = p.error;
vorbis_deinit(&p);
return NULL; return NULL;
} }
f = vorbis_alloc(&p); f = vorbis_alloc(&p);
@@ -4566,7 +4584,7 @@ static uint32 vorbis_find_page(stb_vorbis *f, uint32 *end, uint32 *last)
header[i] = get8(f); header[i] = get8(f);
if (f->eof) return 0; if (f->eof) return 0;
if (header[4] != 0) goto invalid; if (header[4] != 0) goto invalid;
goal = header[22] + (header[23] << 8) + (header[24]<<16) + (header[25]<<24); goal = header[22] + (header[23] << 8) + (header[24]<<16) + ((uint32)header[25]<<24);
for (i=22; i < 26; ++i) for (i=22; i < 26; ++i)
header[i] = 0; header[i] = 0;
crc = 0; crc = 0;
@@ -4970,7 +4988,7 @@ unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f)
// set. whoops! // set. whoops!
break; break;
} }
previous_safe = last_page_loc+1; //previous_safe = last_page_loc+1; // NOTE: not used after this point, but note for debugging
last_page_loc = stb_vorbis_get_file_offset(f); last_page_loc = stb_vorbis_get_file_offset(f);
} }
@@ -5081,7 +5099,10 @@ stb_vorbis * stb_vorbis_open_filename(const char *filename, int *error, const st
stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, int *error, const stb_vorbis_alloc *alloc) stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, int *error, const stb_vorbis_alloc *alloc)
{ {
stb_vorbis *f, p; stb_vorbis *f, p;
if (data == NULL) return NULL; if (!data) {
if (error) *error = VORBIS_unexpected_eof;
return NULL;
}
vorbis_init(&p, alloc); vorbis_init(&p, alloc);
p.stream = (uint8 *) data; p.stream = (uint8 *) data;
p.stream_end = (uint8 *) data + len; p.stream_end = (uint8 *) data + len;
@@ -5156,11 +5177,11 @@ static void copy_samples(short *dest, float *src, int len)
static void compute_samples(int mask, short *output, int num_c, float **data, int d_offset, int len) static void compute_samples(int mask, short *output, int num_c, float **data, int d_offset, int len)
{ {
#define BUFFER_SIZE 32 #define STB_BUFFER_SIZE 32
float buffer[BUFFER_SIZE]; float buffer[STB_BUFFER_SIZE];
int i,j,o,n = BUFFER_SIZE; int i,j,o,n = STB_BUFFER_SIZE;
check_endianness(); check_endianness();
for (o = 0; o < len; o += BUFFER_SIZE) { for (o = 0; o < len; o += STB_BUFFER_SIZE) {
memset(buffer, 0, sizeof(buffer)); memset(buffer, 0, sizeof(buffer));
if (o + n > len) n = len - o; if (o + n > len) n = len - o;
for (j=0; j < num_c; ++j) { for (j=0; j < num_c; ++j) {
@@ -5177,16 +5198,17 @@ static void compute_samples(int mask, short *output, int num_c, float **data, in
output[o+i] = v; output[o+i] = v;
} }
} }
#undef STB_BUFFER_SIZE
} }
static void compute_stereo_samples(short *output, int num_c, float **data, int d_offset, int len) static void compute_stereo_samples(short *output, int num_c, float **data, int d_offset, int len)
{ {
#define BUFFER_SIZE 32 #define STB_BUFFER_SIZE 32
float buffer[BUFFER_SIZE]; float buffer[STB_BUFFER_SIZE];
int i,j,o,n = BUFFER_SIZE >> 1; int i,j,o,n = STB_BUFFER_SIZE >> 1;
// o is the offset in the source data // o is the offset in the source data
check_endianness(); check_endianness();
for (o = 0; o < len; o += BUFFER_SIZE >> 1) { for (o = 0; o < len; o += STB_BUFFER_SIZE >> 1) {
// o2 is the offset in the output data // o2 is the offset in the output data
int o2 = o << 1; int o2 = o << 1;
memset(buffer, 0, sizeof(buffer)); memset(buffer, 0, sizeof(buffer));
@@ -5216,6 +5238,7 @@ static void compute_stereo_samples(short *output, int num_c, float **data, int d
output[o2+i] = v; output[o2+i] = v;
} }
} }
#undef STB_BUFFER_SIZE
} }
static void convert_samples_short(int buf_c, short **buffer, int b_offset, int data_c, float **data, int d_offset, int samples) static void convert_samples_short(int buf_c, short **buffer, int b_offset, int data_c, float **data, int d_offset, int samples)
@@ -5288,8 +5311,6 @@ int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short
float **outputs; float **outputs;
int len = num_shorts / channels; int len = num_shorts / channels;
int n=0; int n=0;
int z = f->channels;
if (z > channels) z = channels;
while (n < len) { while (n < len) {
int k = f->channel_buffer_end - f->channel_buffer_start; int k = f->channel_buffer_end - f->channel_buffer_start;
if (n+k >= len) k = len - n; if (n+k >= len) k = len - n;
@@ -5308,8 +5329,6 @@ int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, in
{ {
float **outputs; float **outputs;
int n=0; int n=0;
int z = f->channels;
if (z > channels) z = channels;
while (n < len) { while (n < len) {
int k = f->channel_buffer_end - f->channel_buffer_start; int k = f->channel_buffer_end - f->channel_buffer_start;
if (n+k >= len) k = len - n; if (n+k >= len) k = len - n;

View File

@@ -384,7 +384,11 @@ void MiniMap::handleEvent(const SDL_Event& event, const std::string& current_roo
Screen::get()->setRendererSurface(prev); Screen::get()->setRendererSurface(prev);
} }
auto game_surface = Screen::get()->getRendererSurface(); auto game_surface = Screen::get()->getRendererSurface();
if (game_surface) { map_surface_->setPalette(game_surface->getPalette()); } if (game_surface) {
Palette src_palette{};
for (int i = 0; i < 256; ++i) { src_palette[i] = game_surface->getPaletteColor(static_cast<Uint8>(i)); }
map_surface_->setPalette(src_palette);
}
std::string file = Screenshot::save(*map_surface_); std::string file = Screenshot::save(*map_surface_);
if (!file.empty()) { std::cout << "MiniMap screenshot: " << file << "\n"; } if (!file.empty()) { std::cout << "MiniMap screenshot: " << file << "\n"; }
// Recomponer para limpiar los números de la surface // Recomponer para limpiar los números de la surface

View File

@@ -211,10 +211,10 @@ void Game::handleEvents() {
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == KeyConfig::get()->key("EDITOR", "toggle") && static_cast<int>(event.key.repeat) == 0) { if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == KeyConfig::get()->key("EDITOR", "toggle") && static_cast<int>(event.key.repeat) == 0) {
if (MapEditor::get()->isActive()) { if (MapEditor::get()->isActive()) {
GameControl::exit_editor(); GameControl::exit_editor();
Notifier::get()->show({Locale::get()->get("game.editor_disabled")}); Notifier::get()->show({Locale::get()->get("ui.editor_disabled")});
} else { } else {
GameControl::enter_editor(); GameControl::enter_editor();
Notifier::get()->show({Locale::get()->get("game.editor_enabled")}); Notifier::get()->show({Locale::get()->get("ui.editor_enabled")});
} }
} else if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == KeyConfig::get()->key("EDITOR", "grid") && static_cast<int>(event.key.repeat) == 0 && MapEditor::get()->isActive()) { } else if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == KeyConfig::get()->key("EDITOR", "grid") && static_cast<int>(event.key.repeat) == 0 && MapEditor::get()->isActive()) {
MapEditor::get()->showGrid(!MapEditor::get()->isGridEnabled()); MapEditor::get()->showGrid(!MapEditor::get()->isGridEnabled());
@@ -236,7 +236,7 @@ void Game::handleInput() {
if (Input::get()->checkAction(InputAction::TOGGLE_IN_GAME_MUSIC, Input::DO_NOT_ALLOW_REPEAT)) { if (Input::get()->checkAction(InputAction::TOGGLE_IN_GAME_MUSIC, Input::DO_NOT_ALLOW_REPEAT)) {
scoreboard_data_->music = !scoreboard_data_->music; scoreboard_data_->music = !scoreboard_data_->music;
scoreboard_data_->music ? Audio::get()->resumeMusic() : Audio::get()->pauseMusic(); scoreboard_data_->music ? Audio::get()->resumeMusic() : Audio::get()->pauseMusic();
Notifier::get()->show({scoreboard_data_->music ? Locale::get()->get("game.music_enabled") : Locale::get()->get("game.music_disabled")}); Notifier::get()->show({scoreboard_data_->music ? Locale::get()->get("ui.music_enabled") : Locale::get()->get("ui.music_disabled")});
} }
// Si la consola está activa, no procesar inputs del juego // Si la consola está activa, no procesar inputs del juego
@@ -262,7 +262,7 @@ void Game::handleInput() {
// Input de pausa solo en estado PLAYING // Input de pausa solo en estado PLAYING
if (Input::get()->checkAction(InputAction::PAUSE, Input::DO_NOT_ALLOW_REPEAT)) { if (Input::get()->checkAction(InputAction::PAUSE, Input::DO_NOT_ALLOW_REPEAT)) {
togglePause(); togglePause();
Notifier::get()->show({paused_ ? Locale::get()->get("game.paused") : Locale::get()->get("game.running")}); Notifier::get()->show({paused_ ? Locale::get()->get("ui.paused") : Locale::get()->get("ui.running")});
} }
GlobalInputs::handle(); GlobalInputs::handle();
@@ -612,7 +612,7 @@ void Game::renderPostFadeEnding() {
static void toggleCheat(Options::Cheat::State& cheat, const std::string& label) { static void toggleCheat(Options::Cheat::State& cheat, const std::string& label) {
cheat = (cheat == Options::Cheat::State::ENABLED) ? Options::Cheat::State::DISABLED : Options::Cheat::State::ENABLED; cheat = (cheat == Options::Cheat::State::ENABLED) ? Options::Cheat::State::DISABLED : Options::Cheat::State::ENABLED;
const bool ENABLED = (cheat == Options::Cheat::State::ENABLED); const bool ENABLED = (cheat == Options::Cheat::State::ENABLED);
Notifier::get()->show({label + (ENABLED ? Locale::get()->get("game.enabled") : Locale::get()->get("game.disabled"))}, Notifier::Style::DEFAULT, -1, true); Notifier::get()->show({label + (ENABLED ? Locale::get()->get("ui.enabled") : Locale::get()->get("ui.disabled"))}, Notifier::Style::DEFAULT, -1, true);
} }
// Pone la información de debug en pantalla // Pone la información de debug en pantalla
@@ -655,9 +655,9 @@ void Game::handleDebugEvents(const SDL_Event& event) {
} else if (KEY == kc->key("DEBUG", "nav_right")) { } else if (KEY == kc->key("DEBUG", "nav_right")) {
changeRoom(room_->getRoom(Room::Border::RIGHT)); changeRoom(room_->getRoom(Room::Border::RIGHT));
} else if (KEY == kc->key("DEBUG", "infinite_lives")) { } else if (KEY == kc->key("DEBUG", "infinite_lives")) {
toggleCheat(Options::cheats.infinite_lives, Locale::get()->get("game.cheat_infinite_lives")); toggleCheat(Options::cheats.infinite_lives, Locale::get()->get("ui.cheat_infinite_lives"));
} else if (KEY == kc->key("DEBUG", "invincibility")) { } else if (KEY == kc->key("DEBUG", "invincibility")) {
toggleCheat(Options::cheats.invincible, Locale::get()->get("game.cheat_invincible")); toggleCheat(Options::cheats.invincible, Locale::get()->get("ui.cheat_invincible"));
} else if (KEY == kc->key("DEBUG", "test_cheevo")) { } else if (KEY == kc->key("DEBUG", "test_cheevo")) {
Notifier::get()->show({Locale::get()->get("achievements.header"), Locale::get()->get("achievements.c11")}, Notifier::Style::CHEEVO, -1, false, "F7"); Notifier::get()->show({Locale::get()->get("achievements.header"), Locale::get()->get("achievements.c11")}, Notifier::Style::CHEEVO, -1, false, "F7");
} else if (KEY == kc->key("DEBUG", "debug_mode")) { } else if (KEY == kc->key("DEBUG", "debug_mode")) {
@@ -666,7 +666,7 @@ void Game::handleDebugEvents(const SDL_Event& event) {
invincible_before_debug_ = (Options::cheats.invincible == Options::Cheat::State::ENABLED); invincible_before_debug_ = (Options::cheats.invincible == Options::Cheat::State::ENABLED);
} }
Debug::get()->toggleEnabled(); Debug::get()->toggleEnabled();
Notifier::get()->show({Debug::get()->isEnabled() ? Locale::get()->get("game.debug_enabled") : Locale::get()->get("game.debug_disabled")}); Notifier::get()->show({Debug::get()->isEnabled() ? Locale::get()->get("ui.debug_enabled") : Locale::get()->get("ui.debug_disabled")});
room_->redrawMap(); room_->redrawMap();
if (Debug::get()->isEnabled()) { if (Debug::get()->isEnabled()) {
Options::cheats.invincible = Options::Cheat::State::ENABLED; Options::cheats.invincible = Options::Cheat::State::ENABLED;

View File

@@ -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

View File

@@ -22,6 +22,41 @@
// [SINGLETON] // [SINGLETON]
Notifier* Notifier::notifier = nullptr; Notifier* Notifier::notifier = nullptr;
// Parte un texto en varias líneas cuando no cabe en max_width píxeles.
// Divide por espacios; si una palabra sola excede el ancho, queda en su propia línea.
static auto wrapToWidth(const std::string& text, int max_width, Text* text_obj, int kerning = 1) -> std::vector<std::string> {
if (max_width <= 0 || text_obj->length(text, kerning) <= max_width) {
return {text};
}
std::vector<std::string> lines;
std::string current;
std::string word;
auto flush_word = [&]() {
if (word.empty()) { return; }
const std::string CANDIDATE = current.empty() ? word : current + " " + word;
if (text_obj->length(CANDIDATE, kerning) <= max_width) {
current = CANDIDATE;
} else {
if (!current.empty()) { lines.push_back(current); }
current = word;
}
word.clear();
};
for (const char c : text) {
if (c == ' ') {
flush_word();
} else {
word += c;
}
}
flush_word();
if (!current.empty()) { lines.push_back(current); }
return lines;
}
// Definición de estilos predefinidos // Definición de estilos predefinidos
const Notifier::Style Notifier::Style::DEFAULT = { const Notifier::Style Notifier::Style::DEFAULT = {
.bg_color = Defaults::Notification::BG_COLOR, .bg_color = Defaults::Notification::BG_COLOR,
@@ -149,14 +184,6 @@ void Notifier::show(std::vector<std::string> texts, const Style& style, int icon
auto result = std::ranges::remove_if(texts, [](const std::string& s) -> bool { return s.empty(); }); auto result = std::ranges::remove_if(texts, [](const std::string& s) -> bool { return s.empty(); });
texts.erase(result.begin(), result.end()); texts.erase(result.begin(), result.end());
// Encuentra la cadena más larga
std::string longest;
for (const auto& text : texts) {
if (text.length() > longest.length()) {
longest = text;
}
}
// Inicializa variables // Inicializa variables
constexpr float TEXT_SIZE = LINE_HEIGHT; constexpr float TEXT_SIZE = LINE_HEIGHT;
const auto PADDING_IN_H = TEXT_SIZE; const auto PADDING_IN_H = TEXT_SIZE;
@@ -164,6 +191,17 @@ void Notifier::show(std::vector<std::string> texts, const Style& style, int icon
const int ICON_SPACE = icon >= 0 ? ICON_SIZE + PADDING_IN_H : 0; const int ICON_SPACE = icon >= 0 ? ICON_SIZE + PADDING_IN_H : 0;
const TextAlign TEXT_IS = ICON_SPACE > 0 ? TextAlign::LEFT : style.text_align; const TextAlign TEXT_IS = ICON_SPACE > 0 ? TextAlign::LEFT : style.text_align;
const float WIDTH = Options::game.width - (PADDING_OUT * 2); const float WIDTH = Options::game.width - (PADDING_OUT * 2);
const int MAX_TEXT_WIDTH = static_cast<int>(WIDTH - (PADDING_IN_H * 2) - ICON_SPACE);
// Si un texto no cabe en una línea, parte por espacios en varias líneas
std::vector<std::string> wrapped;
wrapped.reserve(texts.size());
for (const auto& t : texts) {
auto lines = wrapToWidth(t, MAX_TEXT_WIDTH, text_.get());
wrapped.insert(wrapped.end(), std::make_move_iterator(lines.begin()), std::make_move_iterator(lines.end()));
}
texts = std::move(wrapped);
const float HEIGHT = (TEXT_SIZE * texts.size()) + (PADDING_IN_V * 2); const float HEIGHT = (TEXT_SIZE * texts.size()) + (PADDING_IN_V * 2);
const auto SHAPE = style.shape; const auto SHAPE = style.shape;
@@ -218,7 +256,7 @@ void Notifier::show(std::vector<std::string> texts, const Style& style, int icon
else if (SHAPE == Shape::SQUARED) { else if (SHAPE == Shape::SQUARED) {
n.surface->clear(style.bg_color); n.surface->clear(style.bg_color);
SDL_FRect squared_rect = {.x = 0, .y = 0, .w = n.surface->getWidth(), .h = n.surface->getHeight()}; SDL_FRect squared_rect = {.x = 0, .y = 0, .w = static_cast<float>(n.surface->getWidth()), .h = static_cast<float>(n.surface->getHeight())};
n.surface->drawRectBorder(&squared_rect, style.border_color); n.surface->drawRectBorder(&squared_rect, style.border_color);
} }

View File

@@ -1,53 +0,0 @@
# Makefile for pack_resources tool
# Compiler
CXX := g++
CXXFLAGS := -std=c++20 -Wall -Wextra -O2
# Directories
TOOL_DIR := .
SOURCE_DIR := ../../source/core/resources
# Source files
SOURCES := pack_resources.cpp \
$(SOURCE_DIR)/resource_pack.cpp
# Output
TARGET := pack_resources
# Platform-specific executable extension
ifeq ($(OS),Windows_NT)
TARGET := $(TARGET).exe
endif
# Default target
all: $(TARGET)
# Build the tool
$(TARGET): $(SOURCES)
@echo "Building pack_resources tool..."
$(CXX) $(CXXFLAGS) $(SOURCES) -o $(TARGET)
@echo "Build complete: $(TARGET)"
# Test: create a test pack
test: $(TARGET)
@echo "Creating test pack..."
./$(TARGET) ../../data test_resources.pack
# Create the actual resources.pack
pack: $(TARGET)
@echo "Creating resources.pack..."
./$(TARGET) ../../data ../../resources.pack
# List contents of a pack
list: $(TARGET)
@echo "Listing pack contents..."
./$(TARGET) --list ../../resources.pack
# Clean
clean:
@echo "Cleaning..."
rm -f $(TARGET) test_resources.pack
@echo "Clean complete"
.PHONY: all test pack list clean