Compare commits
9 Commits
10a3e2fedd
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e49f35d3b | |||
| b9cee1bc70 | |||
| 3390d01ef6 | |||
| 561028ff04 | |||
| 671583ebbe | |||
| e941502615 | |||
| 8bab2da2ed | |||
| 5ef278ce70 | |||
| 14103175a9 |
@@ -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 los headers SPIR-V generados en rendering/sdl3gpu/
|
HeaderFilterRegex: 'source/.*'
|
||||||
HeaderFilterRegex: 'source/(?!external/|rendering/sdl3gpu/.*_spv\.h).*'
|
|
||||||
FormatStyle: file
|
FormatStyle: file
|
||||||
|
|
||||||
CheckOptions:
|
CheckOptions:
|
||||||
|
|||||||
51
.vscode/c_cpp_properties.json
vendored
Normal file
51
.vscode/c_cpp_properties.json
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Mac",
|
||||||
|
"includePath": [
|
||||||
|
"${workspaceFolder}/source/**",
|
||||||
|
"${workspaceFolder}/build"
|
||||||
|
],
|
||||||
|
"defines": [
|
||||||
|
"_DEBUG",
|
||||||
|
"_NO_AUDIO",
|
||||||
|
"MACOS_BUILD"
|
||||||
|
],
|
||||||
|
"cStandard": "c17",
|
||||||
|
"cppStandard": "c++20",
|
||||||
|
"intelliSenseMode": "macos-clang-arm64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Linux",
|
||||||
|
"includePath": [
|
||||||
|
"${workspaceFolder}/source/**",
|
||||||
|
"${workspaceFolder}/build"
|
||||||
|
],
|
||||||
|
"defines": [
|
||||||
|
"_DEBUG",
|
||||||
|
"_NO_AUDIO",
|
||||||
|
"LINUX_BUILD"
|
||||||
|
],
|
||||||
|
"cStandard": "c17",
|
||||||
|
"cppStandard": "c++20",
|
||||||
|
"intelliSenseMode": "linux-gcc-x64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Win32",
|
||||||
|
"includePath": [
|
||||||
|
"${workspaceFolder}/source/**",
|
||||||
|
"${workspaceFolder}/build"
|
||||||
|
],
|
||||||
|
"defines": [
|
||||||
|
"_DEBUG",
|
||||||
|
"_NO_AUDIO",
|
||||||
|
"WINDOWS_BUILD",
|
||||||
|
"_WIN32"
|
||||||
|
],
|
||||||
|
"cStandard": "c17",
|
||||||
|
"cppStandard": "c++20",
|
||||||
|
"intelliSenseMode": "windows-msvc-x64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 4
|
||||||
|
}
|
||||||
@@ -3,6 +3,11 @@
|
|||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(coffee_crisis_arcade_edition VERSION 2.00)
|
project(coffee_crisis_arcade_edition VERSION 2.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)
|
||||||
@@ -39,6 +44,7 @@ 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/define_buttons.cpp
|
source/core/input/define_buttons.cpp
|
||||||
@@ -135,7 +141,7 @@ if(EMSCRIPTEN)
|
|||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
SDL3
|
SDL3
|
||||||
GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
|
GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
|
||||||
GIT_TAG release-3.2.12
|
GIT_TAG release-3.4.4
|
||||||
GIT_SHALLOW TRUE
|
GIT_SHALLOW TRUE
|
||||||
)
|
)
|
||||||
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
||||||
@@ -158,11 +164,11 @@ if(NOT APPLE AND NOT EMSCRIPTEN)
|
|||||||
set(SHADER_UPSCALE_SRC "${CMAKE_SOURCE_DIR}/data/shaders/upscale.frag")
|
set(SHADER_UPSCALE_SRC "${CMAKE_SOURCE_DIR}/data/shaders/upscale.frag")
|
||||||
set(SHADER_DOWNSCALE_SRC "${CMAKE_SOURCE_DIR}/data/shaders/downscale.frag")
|
set(SHADER_DOWNSCALE_SRC "${CMAKE_SOURCE_DIR}/data/shaders/downscale.frag")
|
||||||
|
|
||||||
set(SHADER_VERT_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/postfx_vert_spv.h")
|
set(SHADER_VERT_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv/postfx_vert_spv.h")
|
||||||
set(SHADER_FRAG_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/postfx_frag_spv.h")
|
set(SHADER_FRAG_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv/postfx_frag_spv.h")
|
||||||
set(SHADER_CRTPI_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/crtpi_frag_spv.h")
|
set(SHADER_CRTPI_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv/crtpi_frag_spv.h")
|
||||||
set(SHADER_UPSCALE_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/upscale_frag_spv.h")
|
set(SHADER_UPSCALE_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv/upscale_frag_spv.h")
|
||||||
set(SHADER_DOWNSCALE_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/downscale_frag_spv.h")
|
set(SHADER_DOWNSCALE_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv/downscale_frag_spv.h")
|
||||||
|
|
||||||
set(ALL_SHADER_SOURCES "${SHADER_VERT_SRC}" "${SHADER_FRAG_SRC}" "${SHADER_CRTPI_SRC}" "${SHADER_UPSCALE_SRC}" "${SHADER_DOWNSCALE_SRC}")
|
set(ALL_SHADER_SOURCES "${SHADER_VERT_SRC}" "${SHADER_FRAG_SRC}" "${SHADER_CRTPI_SRC}" "${SHADER_UPSCALE_SRC}" "${SHADER_DOWNSCALE_SRC}")
|
||||||
set(ALL_SHADER_HEADERS "${SHADER_VERT_H}" "${SHADER_FRAG_H}" "${SHADER_CRTPI_H}" "${SHADER_UPSCALE_H}" "${SHADER_DOWNSCALE_H}")
|
set(ALL_SHADER_HEADERS "${SHADER_VERT_H}" "${SHADER_FRAG_H}" "${SHADER_CRTPI_H}" "${SHADER_UPSCALE_H}" "${SHADER_DOWNSCALE_H}")
|
||||||
@@ -316,7 +322,6 @@ list(FILTER ALL_SOURCE_FILES EXCLUDE REGEX ".*/external/.*")
|
|||||||
# cuyo uso vive en un .cpp distinto.
|
# cuyo uso vive en un .cpp distinto.
|
||||||
set(CPPCHECK_SOURCES ${ALL_SOURCE_FILES})
|
set(CPPCHECK_SOURCES ${ALL_SOURCE_FILES})
|
||||||
list(FILTER CPPCHECK_SOURCES INCLUDE REGEX ".*\\.cpp$")
|
list(FILTER CPPCHECK_SOURCES INCLUDE REGEX ".*\\.cpp$")
|
||||||
list(FILTER CPPCHECK_SOURCES EXCLUDE REGEX ".*_spv\\.h$")
|
|
||||||
|
|
||||||
# Targets de clang-tidy
|
# Targets de clang-tidy
|
||||||
if(CLANG_TIDY_EXE)
|
if(CLANG_TIDY_EXE)
|
||||||
@@ -396,3 +401,35 @@ if(CPPCHECK_EXE)
|
|||||||
else()
|
else()
|
||||||
message(STATUS "cppcheck no encontrado - target 'cppcheck' no disponible")
|
message(STATUS "cppcheck no encontrado - target 'cppcheck' no disponible")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# --- EINA STANDALONE: pack_resources ---
|
||||||
|
# Executable auxiliar que empaqueta `data/` a `resources.pack`.
|
||||||
|
# No es compila per defecte (EXCLUDE_FROM_ALL). Build explícit:
|
||||||
|
# cmake --build build --target pack_resources
|
||||||
|
# Després executar: ./build/pack_resources data resources.pack
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Regeneració automàtica de resources.pack en cada build si canvia data/.
|
||||||
|
file(GLOB_RECURSE DATA_FILES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/data/*")
|
||||||
|
set(RESOURCE_PACK "${CMAKE_SOURCE_DIR}/resources.pack")
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${RESOURCE_PACK}
|
||||||
|
COMMAND $<TARGET_FILE:pack_resources>
|
||||||
|
"${CMAKE_SOURCE_DIR}/data"
|
||||||
|
"${RESOURCE_PACK}"
|
||||||
|
DEPENDS pack_resources ${DATA_FILES}
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
|
COMMENT "Empaquetant data/ → resources.pack"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_target(resource_pack ALL DEPENDS ${RESOURCE_PACK})
|
||||||
|
add_dependencies(${PROJECT_NAME} resource_pack)
|
||||||
|
endif()
|
||||||
|
|||||||
359
Makefile
359
Makefile
@@ -2,7 +2,6 @@
|
|||||||
# DIRECTORIES
|
# DIRECTORIES
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
DIR_ROOT := $(dir $(abspath $(MAKEFILE_LIST)))
|
DIR_ROOT := $(dir $(abspath $(MAKEFILE_LIST)))
|
||||||
DIR_TOOLS := $(addsuffix /, $(DIR_ROOT)tools)
|
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# TARGET NAMES
|
# TARGET NAMES
|
||||||
@@ -22,8 +21,14 @@ RESOURCE_FILE := release/windows/coffee.res
|
|||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# TOOLS
|
# TOOLS
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
DIR_PACK_TOOL := $(DIR_TOOLS)pack_resources
|
SHADER_CMAKE := $(DIR_ROOT)tools/shaders/compile_spirv.cmake
|
||||||
SHADER_SCRIPT := $(DIR_ROOT)tools/shaders/compile_spirv.sh
|
SHADERS_DIR := $(DIR_ROOT)data/shaders
|
||||||
|
HEADERS_DIR := $(DIR_ROOT)source/core/rendering/sdl3gpu
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
GLSLC := $(shell where glslc 2>NUL)
|
||||||
|
else
|
||||||
|
GLSLC := $(shell command -v glslc 2>/dev/null)
|
||||||
|
endif
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# VERSION (extraída de defines.hpp)
|
# VERSION (extraída de defines.hpp)
|
||||||
@@ -58,9 +63,13 @@ endif
|
|||||||
ifeq ($(OS),Windows_NT)
|
ifeq ($(OS),Windows_NT)
|
||||||
WIN_TARGET_FILE := $(DIR_ROOT)$(APP_NAME)
|
WIN_TARGET_FILE := $(DIR_ROOT)$(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
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
@@ -70,7 +79,6 @@ WINDOWS_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-win32-x64.z
|
|||||||
MACOS_INTEL_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-macos-intel.dmg
|
MACOS_INTEL_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-macos-intel.dmg
|
||||||
MACOS_APPLE_SILICON_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-macos-apple-silicon.dmg
|
MACOS_APPLE_SILICON_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-macos-apple-silicon.dmg
|
||||||
LINUX_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-linux.tar.gz
|
LINUX_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-linux.tar.gz
|
||||||
RASPI_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-raspberry.tar.gz
|
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# PLATAFORMA
|
# PLATAFORMA
|
||||||
@@ -87,15 +95,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
|
||||||
@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
|
||||||
@cmake --build build
|
@cmake --build build
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
@@ -103,41 +120,43 @@ 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
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# 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:
|
@./build/pack_resources data resources.pack
|
||||||
@$(MAKE) -C $(DIR_PACK_TOOL) pack
|
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# COMPILACIÓN DE SHADERS
|
# REGLAS PARA COMPILACIÓN DE SHADERS (multiplataforma via cmake)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
spirv:
|
compile_shaders:
|
||||||
@echo "Compilando shaders SPIR-V..."
|
ifdef GLSLC
|
||||||
$(SHADER_SCRIPT)
|
@cmake -D GLSLC=$(GLSLC) -D SHADERS_DIR=$(SHADERS_DIR) -D HEADERS_DIR=$(HEADERS_DIR) -P $(SHADER_CMAKE)
|
||||||
|
else
|
||||||
|
@echo "glslc no encontrado - asegurate de que los headers SPIR-V precompilados existen"
|
||||||
|
endif
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# COMPILACIÓN PARA WINDOWS (RELEASE)
|
# COMPILACIÓN PARA WINDOWS (RELEASE)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
windows_release:
|
_windows_release:
|
||||||
@$(MAKE) resources.pack
|
@$(MAKE) pack
|
||||||
@echo off
|
@echo off
|
||||||
@echo Creando release para Windows - Version: $(VERSION)
|
@echo Creando release para Windows - Version: $(VERSION)
|
||||||
|
|
||||||
# Compila con cmake
|
# Compila con cmake
|
||||||
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||||
@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'
|
||||||
@@ -153,7 +172,7 @@ windows_release:
|
|||||||
@powershell -Command "Copy-Item 'LICENSE' -Destination '$(RELEASE_FOLDER)'"
|
@powershell -Command "Copy-Item 'LICENSE' -Destination '$(RELEASE_FOLDER)'"
|
||||||
@powershell -Command "Copy-Item 'README.md' -Destination '$(RELEASE_FOLDER)'"
|
@powershell -Command "Copy-Item 'README.md' -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)' -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
|
||||||
@@ -167,16 +186,32 @@ windows_release:
|
|||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# COMPILACIÓN PARA MACOS (RELEASE)
|
# COMPILACIÓN PARA MACOS (RELEASE)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
macos_release:
|
_macos_release:
|
||||||
@$(MAKE) resources.pack
|
@$(MAKE) pack
|
||||||
@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
|
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)"
|
||||||
@@ -200,30 +235,55 @@ macos_release:
|
|||||||
cp LICENSE "$(RELEASE_FOLDER)"
|
cp LICENSE "$(RELEASE_FOLDER)"
|
||||||
cp README.md "$(RELEASE_FOLDER)"
|
cp README.md "$(RELEASE_FOLDER)"
|
||||||
|
|
||||||
# Copia el ejecutable Intel al bundle
|
# Actualiza versión en Info.plist
|
||||||
cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)"
|
@echo "Actualizando Info.plist con versión $(VERSION)..."
|
||||||
|
@RAW_VERSION=$$(echo "$(VERSION)" | sed 's/^v//'); \
|
||||||
|
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"
|
||||||
|
|
||||||
# Firma la aplicación
|
# Compila y empaqueta la versión Intel (best-effort: si falla, se omite el
|
||||||
codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app"
|
# DMG Intel y continúa con la build de Apple Silicon).
|
||||||
|
@echo ""
|
||||||
# Empaqueta el .dmg de la versión Intel con create-dmg
|
@echo "============================================"
|
||||||
@echo "Creando DMG Intel con iconos de 96x96..."
|
@echo " Compilando version Intel (x86_64)"
|
||||||
create-dmg \
|
@echo "============================================"
|
||||||
--volname "$(APP_NAME)" \
|
@if cmake -S . -B build/intel -DCMAKE_BUILD_TYPE=Release \
|
||||||
--window-pos 200 120 \
|
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
|
||||||
--window-size 720 300 \
|
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 \
|
||||||
--icon-size 96 \
|
-DMACOS_BUNDLE=ON \
|
||||||
--text-size 12 \
|
&& cmake --build build/intel; then \
|
||||||
--icon "$(APP_NAME).app" 278 102 \
|
cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)"; \
|
||||||
--icon "LICENSE" 441 102 \
|
codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app"; \
|
||||||
--icon "README.md" 604 102 \
|
echo "Creando DMG Intel con iconos de 96x96..."; \
|
||||||
--app-drop-link 115 102 \
|
create-dmg \
|
||||||
--hide-extension "$(APP_NAME).app" \
|
--volname "$(APP_NAME)" \
|
||||||
"$(MACOS_INTEL_RELEASE)" \
|
--window-pos 200 120 \
|
||||||
"$(RELEASE_FOLDER)" || true
|
--window-size 720 300 \
|
||||||
@echo "Release Intel creado: $(MACOS_INTEL_RELEASE)"
|
--icon-size 96 \
|
||||||
|
--text-size 12 \
|
||||||
|
--icon "$(APP_NAME).app" 278 102 \
|
||||||
|
--icon "LICENSE" 441 102 \
|
||||||
|
--icon "README.md" 604 102 \
|
||||||
|
--app-drop-link 115 102 \
|
||||||
|
--hide-extension "$(APP_NAME).app" \
|
||||||
|
"$(MACOS_INTEL_RELEASE)" \
|
||||||
|
"$(RELEASE_FOLDER)" || true; \
|
||||||
|
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
|
||||||
|
@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
|
@cmake -S . -B build/arm -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 -DMACOS_BUNDLE=ON
|
||||||
@cmake --build build/arm
|
@cmake --build build/arm
|
||||||
cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)"
|
cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)"
|
||||||
@@ -257,12 +317,12 @@ macos_release:
|
|||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# COMPILACIÓN PARA LINUX (RELEASE)
|
# COMPILACIÓN PARA LINUX (RELEASE)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
linux_release:
|
_linux_release:
|
||||||
@$(MAKE) resources.pack
|
@$(MAKE) pack
|
||||||
@echo "Creando release para Linux - Version: $(VERSION)"
|
@echo "Creando release para Linux - Version: $(VERSION)"
|
||||||
|
|
||||||
# Compila con cmake
|
# Compila con cmake
|
||||||
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||||
@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)
|
||||||
@@ -285,145 +345,13 @@ linux_release:
|
|||||||
# Elimina la carpeta temporal
|
# Elimina la carpeta temporal
|
||||||
$(RMDIR) "$(RELEASE_FOLDER)"
|
$(RMDIR) "$(RELEASE_FOLDER)"
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
# COMPILACIÓN PARA LINUX (RELEASE CON INTEGRACIÓN DESKTOP)
|
|
||||||
# ==============================================================================
|
|
||||||
linux_release_desktop:
|
|
||||||
@$(MAKE) resources.pack
|
|
||||||
@echo "Creando release con integracion desktop para Linux - Version: $(VERSION)"
|
|
||||||
|
|
||||||
# Compila con cmake
|
|
||||||
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
|
||||||
@cmake --build build
|
|
||||||
|
|
||||||
# Elimina carpetas previas y recrea (crea dist/ si no existe)
|
|
||||||
$(RMDIR) "$(RELEASE_FOLDER)"
|
|
||||||
|
|
||||||
# Crea la estructura de directorios estándar para Linux
|
|
||||||
$(MKDIR) "$(RELEASE_FOLDER)/$(TARGET_NAME)"
|
|
||||||
$(MKDIR) "$(RELEASE_FOLDER)/$(TARGET_NAME)/bin"
|
|
||||||
$(MKDIR) "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications"
|
|
||||||
$(MKDIR) "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/icons/hicolor/256x256/apps"
|
|
||||||
$(MKDIR) "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/$(TARGET_NAME)"
|
|
||||||
|
|
||||||
# Copia ficheros del juego
|
|
||||||
cp -R config "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/$(TARGET_NAME)/"
|
|
||||||
cp resources.pack "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/$(TARGET_NAME)/"
|
|
||||||
cp LICENSE "$(RELEASE_FOLDER)/$(TARGET_NAME)/"
|
|
||||||
cp README.md "$(RELEASE_FOLDER)/$(TARGET_NAME)/"
|
|
||||||
|
|
||||||
# Copia el ejecutable
|
|
||||||
cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(TARGET_NAME)/bin/$(TARGET_NAME)"
|
|
||||||
strip -s -R .comment -R .gnu.version "$(RELEASE_FOLDER)/$(TARGET_NAME)/bin/$(TARGET_NAME)" --strip-unneeded
|
|
||||||
|
|
||||||
# Crea el archivo .desktop
|
|
||||||
@echo '[Desktop Entry]' > "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
|
|
||||||
@echo 'Version=1.0' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
|
|
||||||
@echo 'Type=Application' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
|
|
||||||
@echo 'Name=$(APP_NAME)' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
|
|
||||||
@echo 'Comment=Arcade action game - defend Earth from alien invasion!' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
|
|
||||||
@echo 'Exec=/opt/$(TARGET_NAME)/bin/$(TARGET_NAME)' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
|
|
||||||
@echo 'Icon=$(TARGET_NAME)' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
|
|
||||||
@echo 'Path=/opt/$(TARGET_NAME)/share/$(TARGET_NAME)' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
|
|
||||||
@echo 'Terminal=false' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
|
|
||||||
@echo 'StartupNotify=true' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
|
|
||||||
@echo 'Categories=Game;ArcadeGame;' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
|
|
||||||
@echo 'Keywords=arcade;action;shooter;retro;' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
|
|
||||||
|
|
||||||
# Copia el icono (si existe) y lo redimensiona si es necesario
|
|
||||||
@if [ -f "release/icons/icon.png" ]; then \
|
|
||||||
if command -v magick >/dev/null 2>&1; then \
|
|
||||||
magick "release/icons/icon.png" -resize 256x256 "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/icons/hicolor/256x256/apps/$(TARGET_NAME).png"; \
|
|
||||||
echo "Icono redimensionado de release/icons/icon.png (usando ImageMagick)"; \
|
|
||||||
elif command -v convert >/dev/null 2>&1; then \
|
|
||||||
convert "release/icons/icon.png" -resize 256x256 "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/icons/hicolor/256x256/apps/$(TARGET_NAME).png"; \
|
|
||||||
echo "Icono redimensionado de release/icons/icon.png (usando ImageMagick legacy)"; \
|
|
||||||
elif command -v ffmpeg >/dev/null 2>&1; then \
|
|
||||||
ffmpeg -i "release/icons/icon.png" -vf scale=256:256 "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/icons/hicolor/256x256/apps/$(TARGET_NAME).png" -y -loglevel quiet; \
|
|
||||||
echo "Icono redimensionado de release/icons/icon.png (usando ffmpeg)"; \
|
|
||||||
else \
|
|
||||||
cp "release/icons/icon.png" "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/icons/hicolor/256x256/apps/$(TARGET_NAME).png"; \
|
|
||||||
echo "Icono copiado sin redimensionar (instalar ImageMagick o ffmpeg para redimensionado automatico)"; \
|
|
||||||
fi; \
|
|
||||||
else \
|
|
||||||
echo "Advertencia: No se encontró release/icons/icon.png - crear icono manualmente"; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Crea script de instalación
|
|
||||||
@echo '#!/bin/bash' > "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
|
|
||||||
@echo 'echo "Instalando $(APP_NAME)..."' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
|
|
||||||
@echo 'sudo mkdir -p /opt/$(TARGET_NAME)' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
|
|
||||||
@echo 'sudo cp -R bin /opt/$(TARGET_NAME)/' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
|
|
||||||
@echo 'sudo cp -R share /opt/$(TARGET_NAME)/' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
|
|
||||||
@echo 'sudo cp LICENSE /opt/$(TARGET_NAME)/' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
|
|
||||||
@echo 'sudo cp README.md /opt/$(TARGET_NAME)/' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
|
|
||||||
@echo 'sudo mkdir -p /usr/share/applications' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
|
|
||||||
@echo 'sudo mkdir -p /usr/share/icons/hicolor/256x256/apps' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
|
|
||||||
@echo 'sudo cp /opt/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop /usr/share/applications/' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
|
|
||||||
@echo 'sudo cp /opt/$(TARGET_NAME)/share/icons/hicolor/256x256/apps/$(TARGET_NAME).png /usr/share/icons/hicolor/256x256/apps/' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
|
|
||||||
@echo 'sudo update-desktop-database /usr/share/applications 2>/dev/null || true' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
|
|
||||||
@echo 'sudo gtk-update-icon-cache /usr/share/icons/hicolor 2>/dev/null || true' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
|
|
||||||
@echo 'echo "$(APP_NAME) instalado correctamente!"' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
|
|
||||||
@echo 'echo "Ya puedes encontrarlo en el menu de aplicaciones en la categoria Juegos."' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
|
|
||||||
chmod +x "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
|
|
||||||
|
|
||||||
# Crea script de desinstalación
|
|
||||||
@echo '#!/bin/bash' > "$(RELEASE_FOLDER)/$(TARGET_NAME)/uninstall.sh"
|
|
||||||
@echo 'echo "Desinstalando $(APP_NAME)..."' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/uninstall.sh"
|
|
||||||
@echo 'sudo rm -rf /opt/$(TARGET_NAME)' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/uninstall.sh"
|
|
||||||
@echo 'sudo rm -f /usr/share/applications/$(TARGET_NAME).desktop' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/uninstall.sh"
|
|
||||||
@echo 'sudo rm -f /usr/share/icons/hicolor/256x256/apps/$(TARGET_NAME).png' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/uninstall.sh"
|
|
||||||
@echo 'sudo update-desktop-database /usr/share/applications 2>/dev/null || true' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/uninstall.sh"
|
|
||||||
@echo 'sudo gtk-update-icon-cache /usr/share/icons/hicolor 2>/dev/null || true' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/uninstall.sh"
|
|
||||||
@echo 'echo "$(APP_NAME) desinstalado correctamente."' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/uninstall.sh"
|
|
||||||
chmod +x "$(RELEASE_FOLDER)/$(TARGET_NAME)/uninstall.sh"
|
|
||||||
|
|
||||||
# Empaqueta ficheros
|
|
||||||
$(RMFILE) "$(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-linux-desktop.tar.gz"
|
|
||||||
tar -czvf "$(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-linux-desktop.tar.gz" -C "$(RELEASE_FOLDER)" .
|
|
||||||
@echo "Release con integracion desktop creado: $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-linux-desktop.tar.gz"
|
|
||||||
@echo "Para instalar: extraer y ejecutar ./$(TARGET_NAME)/install.sh"
|
|
||||||
|
|
||||||
# Elimina la carpeta temporal
|
|
||||||
$(RMDIR) "$(RELEASE_FOLDER)"
|
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
# COMPILACIÓN PARA RASPBERRY PI (RELEASE)
|
|
||||||
# ==============================================================================
|
|
||||||
raspi_release:
|
|
||||||
@$(MAKE) resources.pack
|
|
||||||
@echo "Creando release para Raspberry Pi - Version: $(VERSION)"
|
|
||||||
|
|
||||||
# Compila con cmake
|
|
||||||
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
|
||||||
@cmake --build build
|
|
||||||
|
|
||||||
# Elimina carpetas previas y recrea (crea dist/ si no existe)
|
|
||||||
$(RMDIR) "$(RELEASE_FOLDER)"
|
|
||||||
$(MKDIR) "$(RELEASE_FOLDER)"
|
|
||||||
|
|
||||||
# Copia ficheros
|
|
||||||
cp -R config "$(RELEASE_FOLDER)"
|
|
||||||
cp resources.pack "$(RELEASE_FOLDER)"
|
|
||||||
cp LICENSE "$(RELEASE_FOLDER)"
|
|
||||||
cp README.md "$(RELEASE_FOLDER)"
|
|
||||||
cp "$(TARGET_FILE)" "$(RELEASE_FILE)"
|
|
||||||
strip -s -R .comment -R .gnu.version "$(RELEASE_FILE)" --strip-unneeded
|
|
||||||
|
|
||||||
# Empaqueta ficheros
|
|
||||||
$(RMFILE) "$(RASPI_RELEASE)"
|
|
||||||
tar -czvf "$(RASPI_RELEASE)" -C "$(RELEASE_FOLDER)" .
|
|
||||||
@echo "Release creado: $(RASPI_RELEASE)"
|
|
||||||
|
|
||||||
# Elimina la carpeta temporal
|
|
||||||
$(RMDIR) "$(RELEASE_FOLDER)"
|
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# COMPILACIÓN PARA WEBASSEMBLY (requiere Docker)
|
# COMPILACIÓN PARA WEBASSEMBLY (requiere Docker)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
wasm:
|
wasm:
|
||||||
@echo "Compilando para WebAssembly - Version: $(VERSION) ($(GIT_HASH))"
|
@echo "Compilando para WebAssembly - Version: $(VERSION) ($(GIT_HASH))"
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
|
--user $(shell id -u):$(shell id -g) \
|
||||||
-v $(DIR_ROOT):/src \
|
-v $(DIR_ROOT):/src \
|
||||||
-w /src \
|
-w /src \
|
||||||
emscripten/emsdk:latest \
|
emscripten/emsdk:latest \
|
||||||
@@ -434,26 +362,59 @@ wasm:
|
|||||||
cp build/wasm/$(TARGET_NAME).wasm $(DIST_DIR)/wasm/
|
cp build/wasm/$(TARGET_NAME).wasm $(DIST_DIR)/wasm/
|
||||||
cp build/wasm/$(TARGET_NAME).data $(DIST_DIR)/wasm/
|
cp build/wasm/$(TARGET_NAME).data $(DIST_DIR)/wasm/
|
||||||
@echo "Output: $(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/coffee-crisis-arcade-edition/wasm/
|
||||||
|
ssh maverick 'cd /home/sergio/gitea/web_jailgames && ./deploy.sh'
|
||||||
|
@echo "Deployed to maverick"
|
||||||
|
|
||||||
|
# Versió Debug del build wasm: build local sense deploy. Sortida a dist/wasm_debug/.
|
||||||
|
wasm_debug:
|
||||||
|
@echo "Compilando WebAssembly Debug - Version: $(VERSION) ($(GIT_HASH))"
|
||||||
|
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_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/"
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# CODE QUALITY (delegados a cmake)
|
# CODE QUALITY (delegados a cmake)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
format:
|
format:
|
||||||
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||||
@cmake --build build --target format
|
@cmake --build build --target format
|
||||||
|
|
||||||
format-check:
|
format-check:
|
||||||
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||||
@cmake --build build --target format-check
|
@cmake --build build --target format-check
|
||||||
|
|
||||||
tidy:
|
tidy:
|
||||||
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||||
@cmake --build build --target tidy
|
@cmake --build build --target tidy
|
||||||
|
|
||||||
tidy-fix:
|
tidy-fix:
|
||||||
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||||
@cmake --build build --target tidy-fix
|
@cmake --build build --target tidy-fix
|
||||||
|
|
||||||
|
cppcheck:
|
||||||
|
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||||
|
@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
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
@@ -470,26 +431,22 @@ 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 basico para Linux"
|
|
||||||
@echo " make linux_release_desktop - Crear release con integracion desktop para Linux"
|
|
||||||
@echo " make macos_release - Crear release para macOS"
|
|
||||||
@echo " make raspi_release - Crear release para Raspberry Pi"
|
|
||||||
@echo " make wasm - Crear build WebAssembly (requiere Docker) en dist/wasm"
|
@echo " make wasm - Crear build WebAssembly (requiere Docker) en dist/wasm"
|
||||||
|
@echo " make wasm_debug - Build WebAssembly Debug local (sin deploy)"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo " Herramientas:"
|
@echo " Herramientas:"
|
||||||
@echo " make spirv - 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 ""
|
@echo ""
|
||||||
@echo " Calidad de codigo:"
|
@echo " Calidad de codigo:"
|
||||||
@echo " make format - Formatear codigo con clang-format"
|
@echo " make format - Formatear codigo con clang-format"
|
||||||
@echo " make format-check - Verificar formato sin modificar"
|
@echo " make format-check - Verificar formato sin modificar"
|
||||||
@echo " make tidy - Analisis estatico con clang-tidy"
|
@echo " make tidy - Analisis estatico con clang-tidy"
|
||||||
@echo " make tidy-fix - Analisis estatico con auto-fix"
|
@echo " make tidy-fix - Analisis estatico con auto-fix"
|
||||||
|
@echo " make cppcheck - Analisis estatico con cppcheck"
|
||||||
@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 linux_release_desktop raspi_release wasm pack_tool resources.pack spirv format format-check tidy tidy-fix show_version help
|
.PHONY: all debug release _windows_release _macos_release _linux_release wasm wasm_debug pack compile_shaders format format-check tidy tidy-fix cppcheck controllerdb show_version help
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"[SERVICE_MENU] SUPERSAMPLING": "Supermostreig",
|
"[SERVICE_MENU] SUPERSAMPLING": "Supermostreig",
|
||||||
"[SERVICE_MENU] VSYNC": "Sincronisme vertical",
|
"[SERVICE_MENU] VSYNC": "Sincronisme vertical",
|
||||||
"[SERVICE_MENU] INTEGER_SCALE": "Escalat sencer",
|
"[SERVICE_MENU] INTEGER_SCALE": "Escalat sencer",
|
||||||
|
"[SERVICE_MENU] FILTER": "Filtre",
|
||||||
"[SERVICE_MENU] MAIN_VOLUME": "Volumen general",
|
"[SERVICE_MENU] MAIN_VOLUME": "Volumen general",
|
||||||
"[SERVICE_MENU] MUSIC_VOLUME": "Volumen de la musica",
|
"[SERVICE_MENU] MUSIC_VOLUME": "Volumen de la musica",
|
||||||
"[SERVICE_MENU] SFX_VOLUME": "Volumen dels sons",
|
"[SERVICE_MENU] SFX_VOLUME": "Volumen dels sons",
|
||||||
|
|||||||
@@ -85,6 +85,7 @@
|
|||||||
"[SERVICE_MENU] SUPERSAMPLING": "Supersampling",
|
"[SERVICE_MENU] SUPERSAMPLING": "Supersampling",
|
||||||
"[SERVICE_MENU] VSYNC": "V-Sync",
|
"[SERVICE_MENU] VSYNC": "V-Sync",
|
||||||
"[SERVICE_MENU] INTEGER_SCALE": "Integer Scale",
|
"[SERVICE_MENU] INTEGER_SCALE": "Integer Scale",
|
||||||
|
"[SERVICE_MENU] FILTER": "Filter",
|
||||||
"[SERVICE_MENU] MAIN_VOLUME": "Main Volume",
|
"[SERVICE_MENU] MAIN_VOLUME": "Main Volume",
|
||||||
"[SERVICE_MENU] MUSIC_VOLUME": "Music Volume",
|
"[SERVICE_MENU] MUSIC_VOLUME": "Music Volume",
|
||||||
"[SERVICE_MENU] SFX_VOLUME": "Sound Volume",
|
"[SERVICE_MENU] SFX_VOLUME": "Sound Volume",
|
||||||
|
|||||||
@@ -85,6 +85,7 @@
|
|||||||
"[SERVICE_MENU] SUPERSAMPLING": "Supersampling",
|
"[SERVICE_MENU] SUPERSAMPLING": "Supersampling",
|
||||||
"[SERVICE_MENU] VSYNC": "Sincronismo vertical",
|
"[SERVICE_MENU] VSYNC": "Sincronismo vertical",
|
||||||
"[SERVICE_MENU] INTEGER_SCALE": "Escalado proporcional",
|
"[SERVICE_MENU] INTEGER_SCALE": "Escalado proporcional",
|
||||||
|
"[SERVICE_MENU] FILTER": "Filtro",
|
||||||
"[SERVICE_MENU] MAIN_VOLUME": "Volumen general",
|
"[SERVICE_MENU] MAIN_VOLUME": "Volumen general",
|
||||||
"[SERVICE_MENU] MUSIC_VOLUME": "Volumen de la musica",
|
"[SERVICE_MENU] MUSIC_VOLUME": "Volumen de la musica",
|
||||||
"[SERVICE_MENU] SFX_VOLUME": "Volumen de los efectos",
|
"[SERVICE_MENU] SFX_VOLUME": "Volumen de los efectos",
|
||||||
|
|||||||
2232
gamecontrollerdb.txt
Normal file
2232
gamecontrollerdb.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
#include "core/audio/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
|
||||||
@@ -8,9 +8,9 @@
|
|||||||
// 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.h filtra les macros L, C i R (i PLAYBACK_*) al TU. Les netegem
|
// 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 json.hpp i altres.
|
// perquè xocarien amb noms de paràmetres de plantilla en altres headers.
|
||||||
#undef L
|
#undef L
|
||||||
#undef C
|
#undef C
|
||||||
#undef R
|
#undef R
|
||||||
@@ -19,9 +19,9 @@
|
|||||||
#undef PLAYBACK_RIGHT
|
#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.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;
|
||||||
@@ -30,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; }
|
||||||
@@ -46,17 +49,57 @@ Audio::~Audio() {
|
|||||||
// Método principal
|
// Método principal
|
||||||
void Audio::update() {
|
void Audio::update() {
|
||||||
JA_Update();
|
JA_Update();
|
||||||
|
|
||||||
|
// Sincronizar estado: detectar cuando la música se para (ej. fade-out completado)
|
||||||
|
if (instance && instance->music_.state == MusicState::PLAYING && JA_GetMusicState() != JA_MUSIC_PLAYING) {
|
||||||
|
instance->music_.state = MusicState::STOPPED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reproduce la música
|
// Reproduce la música por nombre (con crossfade opcional)
|
||||||
void Audio::playMusic(const std::string& name, const int loop) {
|
void Audio::playMusic(const std::string& name, const int loop, const int crossfade_ms) {
|
||||||
music_.name = name;
|
bool new_loop = (loop != 0);
|
||||||
music_.loop = (loop != 0);
|
|
||||||
|
|
||||||
if (music_enabled_ && music_.state != MusicState::PLAYING) {
|
// Si ya está sonando exactamente la misma pista y mismo modo loop, no hacemos nada
|
||||||
JA_PlayMusic(Resource::get()->getMusic(name), loop);
|
if (music_.state == MusicState::PLAYING && music_.name == name && music_.loop == new_loop) {
|
||||||
music_.state = MusicState::PLAYING;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!music_enabled_) return;
|
||||||
|
|
||||||
|
auto* resource = AudioResource::getMusic(name);
|
||||||
|
if (resource == nullptr) return;
|
||||||
|
|
||||||
|
if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) {
|
||||||
|
JA_CrossfadeMusic(resource, crossfade_ms, loop);
|
||||||
|
} else {
|
||||||
|
if (music_.state == MusicState::PLAYING) {
|
||||||
|
JA_StopMusic();
|
||||||
|
}
|
||||||
|
JA_PlayMusic(resource, loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
music_.name = name;
|
||||||
|
music_.loop = new_loop;
|
||||||
|
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
|
||||||
@@ -83,10 +126,17 @@ void Audio::stopMusic() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reproduce un sonido
|
// 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::get()->getSound(name), 0, static_cast<int>(group));
|
JA_PlaySound(AudioResource::getSound(name), 0, static_cast<int>(group));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reproduce un sonido por puntero directo
|
||||||
|
void Audio::playSound(JA_Sound_t* sound, Group group) const {
|
||||||
|
if (sound_enabled_ && sound != nullptr) {
|
||||||
|
JA_PlaySound(sound, 0, static_cast<int>(group));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,20 +170,20 @@ auto Audio::getRealMusicState() -> MusicState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Establece el volumen de los sonidos
|
// Establece el volumen de los sonidos (float 0.0..1.0)
|
||||||
void Audio::setSoundVolume(int 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);
|
||||||
const float CONVERTED_VOLUME = (sound_volume / 100.0F) * (Options::audio.volume / 100.0F);
|
const float CONVERTED_VOLUME = sound_volume * Options::audio.volume;
|
||||||
JA_SetSoundVolume(CONVERTED_VOLUME, static_cast<int>(group));
|
JA_SetSoundVolume(CONVERTED_VOLUME, static_cast<int>(group));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Establece el volumen de la música
|
// Establece el volumen de la música (float 0.0..1.0)
|
||||||
void Audio::setMusicVolume(int 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);
|
||||||
const float CONVERTED_VOLUME = (music_volume / 100.0F) * (Options::audio.volume / 100.0F);
|
const float CONVERTED_VOLUME = music_volume * Options::audio.volume;
|
||||||
JA_SetMusicVolume(CONVERTED_VOLUME);
|
JA_SetMusicVolume(CONVERTED_VOLUME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,111 +1,114 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint> // Para int8_t, uint8_t
|
||||||
#include <string> // Para string
|
#include <string> // Para string
|
||||||
#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.0–1.0; la capa de
|
||||||
|
// presentació (menús, notificacions) usa les helpers toPercent/fromPercent
|
||||||
|
// per mostrar 0–100 a l'usuari.
|
||||||
class Audio {
|
class Audio {
|
||||||
public:
|
public:
|
||||||
// --- Enums ---
|
// --- Enums ---
|
||||||
enum class Group : int {
|
enum class Group : std::int8_t {
|
||||||
ALL = -1, // Todos los grupos
|
ALL = -1, // Todos los grupos
|
||||||
GAME = 0, // Sonidos del juego
|
GAME = 0, // Sonidos del juego
|
||||||
INTERFACE = 1 // Sonidos de la interfaz
|
INTERFACE = 1 // Sonidos de la interfaz
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class MusicState {
|
enum class MusicState : std::uint8_t {
|
||||||
PLAYING, // Reproduciendo música
|
PLAYING, // Reproduciendo música
|
||||||
PAUSED, // Música pausada
|
PAUSED, // Música pausada
|
||||||
STOPPED, // Música detenida
|
STOPPED, // Música detenida
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Constantes ---
|
// --- Constantes ---
|
||||||
static constexpr int MAX_VOLUME = 100; // Volumen máximo
|
static constexpr float MAX_VOLUME = 1.0F; // Volumen máximo (float 0..1)
|
||||||
static constexpr int MIN_VOLUME = 0; // Volumen mínimo
|
static constexpr float MIN_VOLUME = 0.0F; // Volumen mínimo (float 0..1)
|
||||||
static constexpr int FREQUENCY = 48000; // Frecuencia de audio
|
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 DEFAULT_CROSSFADE_MS = 1500; // Duració del crossfade per defecte (ms)
|
||||||
|
|
||||||
// --- Métodos de singleton ---
|
// --- Singleton ---
|
||||||
static void init(); // Inicializa el objeto Audio
|
static void init(); // Inicializa el objeto Audio
|
||||||
static void destroy(); // Libera el objeto Audio
|
static void destroy(); // Libera el objeto Audio
|
||||||
static auto get() -> Audio*; // Obtiene el puntero al objeto Audio
|
static auto get() -> Audio*; // Obtiene el puntero al objeto Audio
|
||||||
Audio(const Audio&) = delete; // Evitar copia
|
Audio(const Audio&) = delete; // Evitar copia
|
||||||
auto operator=(const Audio&) -> Audio& = delete; // Evitar asignación
|
auto operator=(const Audio&) -> Audio& = delete; // Evitar asignación
|
||||||
|
|
||||||
// --- Método principal ---
|
static void update(); // Actualización del sistema de audio
|
||||||
static void update();
|
|
||||||
|
|
||||||
// --- Control de Música ---
|
// --- Control de música ---
|
||||||
void playMusic(const std::string& name, int loop = -1); // Reproducir música en bucle
|
void playMusic(const std::string& name, int loop = -1, int crossfade_ms = 0); // Reproducir música por nombre (con crossfade opcional)
|
||||||
void pauseMusic(); // Pausar reproducción de música
|
void playMusic(struct JA_Music_t* music, int loop = -1, int crossfade_ms = 0); // Reproducir música por puntero (con crossfade opcional)
|
||||||
void resumeMusic(); // Continua la música pausada
|
void pauseMusic(); // Pausar reproducción de música
|
||||||
void stopMusic(); // Detener completamente la música
|
void resumeMusic(); // Continua la música pausada
|
||||||
void fadeOutMusic(int milliseconds) const; // Fundido de salida de la música
|
void stopMusic(); // Detener completamente la música
|
||||||
|
void fadeOutMusic(int milliseconds) const; // Fundido de salida de la música
|
||||||
|
|
||||||
// --- Control de Sonidos ---
|
// --- Control de sonidos ---
|
||||||
void playSound(const std::string& name, Group group = Group::GAME) const; // Reproducir sonido puntual
|
void playSound(const std::string& name, Group group = Group::GAME) const; // Reproducir sonido puntual por nombre
|
||||||
void stopAllSounds() const; // Detener todos los sonidos
|
void playSound(struct JA_Sound_t* sound, Group group = Group::GAME) const; // Reproducir sonido puntual por puntero
|
||||||
|
void stopAllSounds() const; // Detener todos los sonidos
|
||||||
|
|
||||||
// --- Configuración General ---
|
// --- Control de volumen (API interna: float 0.0..1.0) ---
|
||||||
|
void setSoundVolume(float volume, Group group = Group::ALL) const; // Ajustar volumen de efectos
|
||||||
|
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 ---
|
||||||
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
|
||||||
void applySettings(); // Aplica la configuración
|
void applySettings(); // Aplica la configuración
|
||||||
|
|
||||||
// --- Configuración de Sonidos ---
|
// --- Configuración de sonidos ---
|
||||||
void enableSound() { sound_enabled_ = true; } // Habilitar sonidos
|
void enableSound() { sound_enabled_ = true; } // Habilitar sonidos
|
||||||
void disableSound() { sound_enabled_ = false; } // Deshabilitar sonidos
|
void disableSound() { sound_enabled_ = false; } // Deshabilitar sonidos
|
||||||
void enableSound(bool value) { sound_enabled_ = value; } // Establecer estado de sonidos
|
void enableSound(bool value) { sound_enabled_ = value; } // Establecer estado de sonidos
|
||||||
void toggleSound() { sound_enabled_ = !sound_enabled_; } // Alternar estado de sonidos
|
void toggleSound() { sound_enabled_ = !sound_enabled_; } // Alternar estado de sonidos
|
||||||
|
|
||||||
// --- Configuración de Música ---
|
// --- Configuración de música ---
|
||||||
void enableMusic() { music_enabled_ = true; } // Habilitar música
|
void enableMusic() { music_enabled_ = true; } // Habilitar música
|
||||||
void disableMusic() { music_enabled_ = false; } // Deshabilitar música
|
void disableMusic() { music_enabled_ = false; } // Deshabilitar música
|
||||||
void enableMusic(bool value) { music_enabled_ = value; } // Establecer estado de música
|
void enableMusic(bool value) { music_enabled_ = value; } // Establecer estado de música
|
||||||
void toggleMusic() { music_enabled_ = !music_enabled_; } // Alternar estado de música
|
void toggleMusic() { music_enabled_ = !music_enabled_; } // Alternar estado de música
|
||||||
|
|
||||||
// --- Control de Volumen ---
|
// --- Consultas de estado ---
|
||||||
void setSoundVolume(int volume, Group group = Group::ALL) const; // Ajustar volumen de efectos
|
|
||||||
void setMusicVolume(int volume) const; // Ajustar volumen de música
|
|
||||||
|
|
||||||
// --- Getters para debug ---
|
|
||||||
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; }
|
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; }
|
||||||
[[nodiscard]] auto isSoundEnabled() const -> bool { return sound_enabled_; }
|
[[nodiscard]] auto isSoundEnabled() const -> bool { return sound_enabled_; }
|
||||||
[[nodiscard]] auto isMusicEnabled() const -> bool { return music_enabled_; }
|
[[nodiscard]] auto isMusicEnabled() const -> bool { return music_enabled_; }
|
||||||
[[nodiscard]] auto getMusicState() const -> MusicState { return music_.state; }
|
[[nodiscard]] auto getMusicState() const -> MusicState { return music_.state; }
|
||||||
[[nodiscard]] static auto getRealMusicState() -> MusicState; // Consulta directamente a jailaudio
|
[[nodiscard]] static auto getRealMusicState() -> MusicState;
|
||||||
[[nodiscard]] auto getCurrentMusicName() const -> const std::string& { return music_.name; }
|
[[nodiscard]] auto getCurrentMusicName() const -> const std::string& { return music_.name; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// --- Estructuras privadas ---
|
// --- Tipos anidados ---
|
||||||
struct Music {
|
struct Music {
|
||||||
MusicState state; // Estado actual de la música (reproduciendo, detenido, en pausa)
|
MusicState state{MusicState::STOPPED}; // Estado actual de la música
|
||||||
std::string name; // Última pista de música reproducida
|
std::string name; // Última pista de música reproducida
|
||||||
bool loop; // Indica si la última pista de música se debe reproducir en bucle
|
bool loop{false}; // Indica si se reproduce en bucle
|
||||||
|
|
||||||
// Constructor para inicializar la música con valores predeterminados
|
|
||||||
Music()
|
|
||||||
: state(MusicState::STOPPED),
|
|
||||||
loop(false) {}
|
|
||||||
|
|
||||||
// Constructor para inicializar con valores específicos
|
|
||||||
Music(MusicState init_state, std::string init_name, bool init_loop)
|
|
||||||
: state(init_state),
|
|
||||||
name(std::move(init_name)),
|
|
||||||
loop(init_loop) {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Variables de estado ---
|
// --- Métodos ---
|
||||||
Music music_; // Estado de la música
|
Audio(); // Constructor privado
|
||||||
bool enabled_ = true; // Estado general del audio
|
~Audio(); // Destructor privado
|
||||||
bool sound_enabled_ = true; // Estado de los efectos de sonido
|
|
||||||
bool music_enabled_ = true; // Estado de la música
|
|
||||||
|
|
||||||
// --- Métodos internos ---
|
|
||||||
void initSDLAudio(); // Inicializa SDL Audio
|
void initSDLAudio(); // Inicializa SDL Audio
|
||||||
|
|
||||||
// --- Constructores y destructor privados (singleton) ---
|
// --- Variables miembro ---
|
||||||
Audio(); // Constructor privado
|
|
||||||
~Audio(); // Destructor privado
|
|
||||||
|
|
||||||
// --- Instancia singleton ---
|
|
||||||
static Audio* instance; // Instancia única de Audio
|
static Audio* instance; // Instancia única de Audio
|
||||||
|
|
||||||
|
Music music_; // Estado de la música
|
||||||
|
bool enabled_{true}; // Estado general del audio
|
||||||
|
bool sound_enabled_{true}; // Estado de los efectos de sonido
|
||||||
|
bool music_enabled_{true}; // Estado de la música
|
||||||
};
|
};
|
||||||
13
source/core/audio/audio_adapter.cpp
Normal file
13
source/core/audio/audio_adapter.cpp
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#include "core/audio/audio_adapter.hpp"
|
||||||
|
|
||||||
|
#include "core/resources/resource.hpp"
|
||||||
|
|
||||||
|
namespace AudioResource {
|
||||||
|
JA_Music_t* getMusic(const std::string& name) {
|
||||||
|
return Resource::get()->getMusic(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
JA_Sound_t* getSound(const std::string& name) {
|
||||||
|
return Resource::get()->getSound(name);
|
||||||
|
}
|
||||||
|
} // namespace AudioResource
|
||||||
17
source/core/audio/audio_adapter.hpp
Normal file
17
source/core/audio/audio_adapter.hpp
Normal 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
|
||||||
@@ -3,26 +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 <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_decode_memory
|
#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
|
||||||
@@ -31,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 {
|
||||||
@@ -46,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];
|
||||||
@@ -72,15 +90,27 @@ inline bool JA_musicEnabled{true};
|
|||||||
inline bool JA_soundEnabled{true};
|
inline bool JA_soundEnabled{true};
|
||||||
inline SDL_AudioDeviceID sdlAudioDevice{0};
|
inline SDL_AudioDeviceID sdlAudioDevice{0};
|
||||||
|
|
||||||
inline bool fading{false};
|
// --- Crossfade / Fade State ---
|
||||||
inline int fade_start_time{0};
|
struct JA_FadeState {
|
||||||
inline int fade_duration{0};
|
bool active{false};
|
||||||
inline float fade_initial_volume{0.0f}; // Corregido de 'int' a 'float'
|
Uint64 start_time{0};
|
||||||
|
int duration_ms{0};
|
||||||
|
float initial_volume{0.0f};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct JA_OutgoingMusic {
|
||||||
|
SDL_AudioStream* stream{nullptr};
|
||||||
|
JA_FadeState fade;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline JA_OutgoingMusic outgoing_music;
|
||||||
|
inline JA_FadeState incoming_fade;
|
||||||
|
|
||||||
// --- Forward Declarations ---
|
// --- Forward Declarations ---
|
||||||
inline void JA_StopMusic();
|
inline void JA_StopMusic();
|
||||||
inline void JA_StopChannel(const int channel);
|
inline void JA_StopChannel(const int channel);
|
||||||
inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int loop = 0, const int group = 0);
|
inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int loop = 0, const int group = 0);
|
||||||
|
inline void JA_CrossfadeMusic(JA_Music_t* music, int crossfade_ms, int loop = -1);
|
||||||
|
|
||||||
// --- Music streaming internals ---
|
// --- Music streaming internals ---
|
||||||
// Bytes-per-sample per canal (sempre s16)
|
// Bytes-per-sample per canal (sempre s16)
|
||||||
@@ -98,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;
|
||||||
}
|
}
|
||||||
@@ -133,20 +163,51 @@ inline void JA_PumpMusic(JA_Music_t* music) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pre-carrega `duration_ms` de so dins l'stream actual abans que l'stream
|
||||||
|
// siga robat per outgoing_music (crossfade o fade-out). Imprescindible amb
|
||||||
|
// streaming: l'stream robat no es pot re-alimentar perquè perd la referència
|
||||||
|
// al seu vorbis decoder. No aplica loop — si el vorbis s'esgota abans, parem.
|
||||||
|
inline void JA_PreFillOutgoing(JA_Music_t* music, int duration_ms) {
|
||||||
|
if (!music || !music->vorbis || !music->stream) return;
|
||||||
|
|
||||||
|
const int bytes_per_second = music->spec.freq * music->spec.channels * JA_MUSIC_BYTES_PER_SAMPLE;
|
||||||
|
const int needed_bytes = static_cast<int>((static_cast<int64_t>(duration_ms) * bytes_per_second) / 1000);
|
||||||
|
|
||||||
|
while (SDL_GetAudioStreamAvailable(music->stream) < needed_bytes) {
|
||||||
|
const int decoded = JA_FeedMusicChunk(music);
|
||||||
|
if (decoded <= 0) break; // EOF: deixem drenar el que hi haja
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Core Functions ---
|
// --- Core Functions ---
|
||||||
|
|
||||||
inline void JA_Update() {
|
inline void JA_Update() {
|
||||||
|
// --- Outgoing music fade-out (crossfade o fade-out a silencio) ---
|
||||||
|
if (outgoing_music.stream && outgoing_music.fade.active) {
|
||||||
|
Uint64 now = SDL_GetTicks();
|
||||||
|
Uint64 elapsed = now - outgoing_music.fade.start_time;
|
||||||
|
if (elapsed >= (Uint64)outgoing_music.fade.duration_ms) {
|
||||||
|
SDL_DestroyAudioStream(outgoing_music.stream);
|
||||||
|
outgoing_music.stream = nullptr;
|
||||||
|
outgoing_music.fade.active = false;
|
||||||
|
} else {
|
||||||
|
float percent = (float)elapsed / (float)outgoing_music.fade.duration_ms;
|
||||||
|
SDL_SetAudioStreamGain(outgoing_music.stream, outgoing_music.fade.initial_volume * (1.0f - percent));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Current music ---
|
||||||
if (JA_musicEnabled && current_music && current_music->state == JA_MUSIC_PLAYING) {
|
if (JA_musicEnabled && current_music && current_music->state == JA_MUSIC_PLAYING) {
|
||||||
if (fading) {
|
// Fade-in (parte de un crossfade)
|
||||||
int time = SDL_GetTicks();
|
if (incoming_fade.active) {
|
||||||
if (time > (fade_start_time + fade_duration)) {
|
Uint64 now = SDL_GetTicks();
|
||||||
fading = false;
|
Uint64 elapsed = now - incoming_fade.start_time;
|
||||||
JA_StopMusic();
|
if (elapsed >= (Uint64)incoming_fade.duration_ms) {
|
||||||
return;
|
incoming_fade.active = false;
|
||||||
|
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
|
||||||
} else {
|
} else {
|
||||||
const int time_passed = time - fade_start_time;
|
float percent = (float)elapsed / (float)incoming_fade.duration_ms;
|
||||||
const float percent = (float)time_passed / (float)fade_duration;
|
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume * percent);
|
||||||
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume * (1.0 - percent));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,12 +219,13 @@ inline void JA_Update() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Sound channels ---
|
||||||
if (JA_soundEnabled) {
|
if (JA_soundEnabled) {
|
||||||
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) {
|
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 {
|
||||||
@@ -174,12 +236,8 @@ 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) std::cout << "Failed to initialize SDL audio!" << '\n';
|
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;
|
||||||
@@ -187,7 +245,11 @@ inline void JA_Init(const int freq, const SDL_AudioFormat format, const int num_
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline void JA_Quit() {
|
inline void JA_Quit() {
|
||||||
if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice); // Corregido: !sdlAudioDevice -> sdlAudioDevice
|
if (outgoing_music.stream) {
|
||||||
|
SDL_DestroyAudioStream(outgoing_music.stream);
|
||||||
|
outgoing_music.stream = nullptr;
|
||||||
|
}
|
||||||
|
if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice);
|
||||||
sdlAudioDevice = 0;
|
sdlAudioDevice = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,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,
|
||||||
|
nullptr);
|
||||||
|
if (!music->vorbis) {
|
||||||
std::cout << "JA_LoadMusic: stb_vorbis_open_memory failed (error " << error << ")" << '\n';
|
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;
|
||||||
@@ -224,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);
|
||||||
@@ -280,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);
|
||||||
@@ -299,13 +367,21 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void JA_StopMusic() {
|
inline void JA_StopMusic() {
|
||||||
|
// Limpiar outgoing crossfade si existe
|
||||||
|
if (outgoing_music.stream) {
|
||||||
|
SDL_DestroyAudioStream(outgoing_music.stream);
|
||||||
|
outgoing_music.stream = nullptr;
|
||||||
|
outgoing_music.fade.active = false;
|
||||||
|
}
|
||||||
|
incoming_fade.active = false;
|
||||||
|
|
||||||
if (!current_music || current_music->state == JA_MUSIC_INVALID || current_music->state == JA_MUSIC_STOPPED) return;
|
if (!current_music || current_music->state == JA_MUSIC_INVALID || current_music->state == JA_MUSIC_STOPPED) return;
|
||||||
|
|
||||||
current_music->state = JA_MUSIC_STOPPED;
|
current_music->state = JA_MUSIC_STOPPED;
|
||||||
@@ -318,17 +394,73 @@ inline void JA_StopMusic() {
|
|||||||
if (current_music->vorbis) {
|
if (current_music->vorbis) {
|
||||||
stb_vorbis_seek_start(current_music->vorbis);
|
stb_vorbis_seek_start(current_music->vorbis);
|
||||||
}
|
}
|
||||||
// No liberem filename aquí; es fa en JA_DeleteMusic.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void JA_FadeOutMusic(const int milliseconds) {
|
inline void JA_FadeOutMusic(const int milliseconds) {
|
||||||
if (!JA_musicEnabled) return;
|
if (!JA_musicEnabled) return;
|
||||||
if (current_music == NULL || current_music->state == JA_MUSIC_INVALID) return;
|
if (!current_music || current_music->state != JA_MUSIC_PLAYING) return;
|
||||||
|
|
||||||
fading = true;
|
// Destruir outgoing anterior si existe
|
||||||
fade_start_time = SDL_GetTicks();
|
if (outgoing_music.stream) {
|
||||||
fade_duration = milliseconds;
|
SDL_DestroyAudioStream(outgoing_music.stream);
|
||||||
fade_initial_volume = JA_musicVolume;
|
outgoing_music.stream = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-omplim l'stream amb `milliseconds` de so: un cop robat, ja no
|
||||||
|
// tindrà accés al vorbis decoder i només podrà drenar el que tinga.
|
||||||
|
JA_PreFillOutgoing(current_music, milliseconds);
|
||||||
|
|
||||||
|
// Robar el stream del current_music al outgoing
|
||||||
|
outgoing_music.stream = current_music->stream;
|
||||||
|
outgoing_music.fade = {true, SDL_GetTicks(), milliseconds, JA_musicVolume};
|
||||||
|
|
||||||
|
// Dejar current_music sin stream (ya lo tiene outgoing)
|
||||||
|
current_music->stream = nullptr;
|
||||||
|
current_music->state = JA_MUSIC_STOPPED;
|
||||||
|
if (current_music->vorbis) stb_vorbis_seek_start(current_music->vorbis);
|
||||||
|
incoming_fade.active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void JA_CrossfadeMusic(JA_Music_t* music, const int crossfade_ms, const int loop) {
|
||||||
|
if (!JA_musicEnabled || !music || !music->vorbis) return;
|
||||||
|
|
||||||
|
// Destruir outgoing anterior si existe (crossfade durante crossfade)
|
||||||
|
if (outgoing_music.stream) {
|
||||||
|
SDL_DestroyAudioStream(outgoing_music.stream);
|
||||||
|
outgoing_music.stream = nullptr;
|
||||||
|
outgoing_music.fade.active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Robar el stream de la musica actual al outgoing para el fade-out.
|
||||||
|
// Pre-omplim amb `crossfade_ms` de so perquè no es quede en silenci
|
||||||
|
// abans d'acabar el fade (l'stream robat ja no pot alimentar-se).
|
||||||
|
if (current_music && current_music->state == JA_MUSIC_PLAYING && current_music->stream) {
|
||||||
|
JA_PreFillOutgoing(current_music, crossfade_ms);
|
||||||
|
outgoing_music.stream = current_music->stream;
|
||||||
|
outgoing_music.fade = {true, SDL_GetTicks(), crossfade_ms, JA_musicVolume};
|
||||||
|
current_music->stream = nullptr;
|
||||||
|
current_music->state = JA_MUSIC_STOPPED;
|
||||||
|
if (current_music->vorbis) stb_vorbis_seek_start(current_music->vorbis);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iniciar la nueva pista con gain=0 (el fade-in la sube gradualmente)
|
||||||
|
current_music = music;
|
||||||
|
current_music->state = JA_MUSIC_PLAYING;
|
||||||
|
current_music->times = loop;
|
||||||
|
|
||||||
|
stb_vorbis_seek_start(current_music->vorbis);
|
||||||
|
current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &JA_audioSpec);
|
||||||
|
if (!current_music->stream) {
|
||||||
|
std::cout << "Failed to create audio stream for crossfade!" << '\n';
|
||||||
|
current_music->state = JA_MUSIC_STOPPED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SDL_SetAudioStreamGain(current_music->stream, 0.0f);
|
||||||
|
JA_PumpMusic(current_music); // pre-carrega abans de bindejar
|
||||||
|
SDL_BindAudioStream(sdlAudioDevice, current_music->stream);
|
||||||
|
|
||||||
|
// Configurar fade-in
|
||||||
|
incoming_fade = {true, SDL_GetTicks(), crossfade_ms, 0.0f};
|
||||||
}
|
}
|
||||||
|
|
||||||
inline JA_Music_state JA_GetMusicState() {
|
inline JA_Music_state JA_GetMusicState() {
|
||||||
@@ -346,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 es libera aquí
|
// al destructor de JA_Music_t.
|
||||||
delete music;
|
delete music;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,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;
|
||||||
|
if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), 1, &sound->spec, &raw, &sound->length)) {
|
||||||
std::cout << "Failed to load WAV from memory: " << SDL_GetError() << '\n';
|
std::cout << "Failed to load WAV from memory: " << SDL_GetError() << '\n';
|
||||||
delete sound;
|
|
||||||
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;
|
||||||
|
if (!SDL_LoadWAV(filename, &sound->spec, &raw, &sound->length)) {
|
||||||
std::cout << "Failed to load WAV file: " << SDL_GetError() << '\n';
|
std::cout << "Failed to load WAV file: " << SDL_GetError() << '\n';
|
||||||
delete sound;
|
|
||||||
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) {
|
||||||
@@ -422,12 +545,12 @@ 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);
|
||||||
|
|
||||||
@@ -437,7 +560,7 @@ inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int
|
|||||||
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);
|
||||||
|
|
||||||
@@ -449,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,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) {
|
||||||
@@ -504,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -516,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) {
|
||||||
@@ -527,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) {
|
||||||
@@ -545,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,41 @@
|
|||||||
#include <unordered_map> // Para unordered_map, _Node_iterator, operator==, _Node_iterator_base, _Node_const_iterator
|
#include <unordered_map> // Para unordered_map, _Node_iterator, operator==, _Node_iterator_base, _Node_const_iterator
|
||||||
#include <utility> // Para pair, move
|
#include <utility> // Para pair, move
|
||||||
|
|
||||||
|
// Emscripten-only: SDL 3.4+ ja no casa el GUID dels mandos de Chrome Android
|
||||||
|
// amb gamecontrollerdb (el gamepad.id d'Android no porta Vendor/Product, el
|
||||||
|
// parser extreu valors escombraries, el GUID resultant no està a la db i el
|
||||||
|
// gamepad queda obert amb un mapping incorrecte). Com el W3C Gamepad API
|
||||||
|
// garanteix el layout estàndard quan el navegador reporta mapping=="standard",
|
||||||
|
// injectem un mapping SDL amb eixe layout per al GUID del joystick abans
|
||||||
|
// d'obrir-lo com gamepad. Fora d'Emscripten és un no-op.
|
||||||
|
static void installWebStandardMapping(SDL_JoystickID jid) {
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
SDL_GUID guid = SDL_GetJoystickGUIDForID(jid);
|
||||||
|
char guidStr[33];
|
||||||
|
SDL_GUIDToString(guid, guidStr, sizeof(guidStr));
|
||||||
|
const char* name = SDL_GetJoystickNameForID(jid);
|
||||||
|
if (!name || !*name) name = "Standard Gamepad";
|
||||||
|
|
||||||
|
char mapping[512];
|
||||||
|
SDL_snprintf(mapping, sizeof(mapping),
|
||||||
|
"%s,%s,"
|
||||||
|
"a:b0,b:b1,x:b2,y:b3,"
|
||||||
|
"leftshoulder:b4,rightshoulder:b5,"
|
||||||
|
"lefttrigger:b6,righttrigger:b7,"
|
||||||
|
"back:b8,start:b9,"
|
||||||
|
"leftstick:b10,rightstick:b11,"
|
||||||
|
"dpup:b12,dpdown:b13,dpleft:b14,dpright:b15,"
|
||||||
|
"guide:b16,"
|
||||||
|
"leftx:a0,lefty:a1,rightx:a2,righty:a3,"
|
||||||
|
"platform:Emscripten",
|
||||||
|
guidStr,
|
||||||
|
name);
|
||||||
|
SDL_AddGamepadMapping(mapping);
|
||||||
|
#else
|
||||||
|
(void)jid;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// Singleton
|
// Singleton
|
||||||
Input* Input::instance = nullptr;
|
Input* Input::instance = nullptr;
|
||||||
|
|
||||||
@@ -368,6 +403,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';
|
||||||
|
|||||||
@@ -105,10 +105,20 @@ class Input {
|
|||||||
std::string path;
|
std::string path;
|
||||||
std::unordered_map<Action, ButtonState> bindings;
|
std::unordered_map<Action, ButtonState> bindings;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
Gamepad(SDL_Gamepad* gamepad)
|
Gamepad(SDL_Gamepad* gamepad)
|
||||||
: pad(gamepad),
|
: pad(gamepad),
|
||||||
instance_id(SDL_GetJoystickID(SDL_GetGamepadJoystick(gamepad))),
|
instance_id(SDL_GetJoystickID(SDL_GetGamepadJoystick(gamepad))),
|
||||||
name(std::string(SDL_GetGamepadName(gamepad))),
|
name(trimName(SDL_GetGamepadName(gamepad))),
|
||||||
path(std::string(SDL_GetGamepadPath(pad))),
|
path(std::string(SDL_GetGamepadPath(pad))),
|
||||||
bindings{
|
bindings{
|
||||||
// Movimiento del jugador
|
// Movimiento del jugador
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
#include "core/rendering/screen.hpp"
|
#include "core/rendering/screen.hpp"
|
||||||
|
|
||||||
#include <SDL3/SDL.h> // Para SDL_SetRenderTarget, SDL_RenderTexture, SDL_SetRenderDrawColor, SDL_SetRenderVSync, SDL_LogCategory, SDL_GetError, SDL_LogError, SDL_LogInfo, SDL_RendererLogicalPresentation, SDL_SetRenderLogicalPresentation, SDL_CreateTexture, SDL_DestroyTexture, SDL_DestroyWindow, SDL_GetDisplayName, SDL_GetTicks, SDL_Quit, SDL_RENDERER_VSYNC_DISABLED, SDL_RenderClear, SDL_CreateRenderer, SDL_CreateWindow, SDL_DestroyRenderer, SDL_DisplayID, SDL_FRect, SDL_GetCurrentDisplayMode, SDL_GetDisplays, SDL_GetRenderTarget, SDL_GetWindowPosition, SDL_GetWindowSize, SDL_Init, SDL_LogWarn, SDL_PixelFormat, SDL_RenderFillRect, SDL_RenderPresent, SDL_SetHint, SDL_SetRenderDrawBlendMode, SDL_SetTextureScaleMode, SDL_SetWindowFullscreen, SDL_SetWindowPosition, SDL_SetWindowSize, SDL_TextureAccess, SDL_free, SDL_BLENDMODE_BLEND, SDL_HINT_RENDER_DRIVER, SDL_INIT_VIDEO, SDL_ScaleMode, SDL_WINDOW_FULLSCREEN, SDL_WindowFlags
|
#include <SDL3/SDL.h> // Para SDL_SetRenderTarget, SDL_RenderTexture, SDL_SetRenderDrawColor, SDL_SetRenderVSync, SDL_LogCategory, SDL_GetError, SDL_LogError, SDL_LogInfo, SDL_RendererLogicalPresentation, SDL_SetRenderLogicalPresentation, SDL_CreateTexture, SDL_DestroyTexture, SDL_DestroyWindow, SDL_GetDisplayName, SDL_GetTicks, SDL_Quit, SDL_RENDERER_VSYNC_DISABLED, SDL_RenderClear, SDL_CreateRenderer, SDL_CreateWindow, SDL_DestroyRenderer, SDL_DisplayID, SDL_FRect, SDL_GetCurrentDisplayMode, SDL_GetDisplays, SDL_GetRenderTarget, SDL_GetWindowPosition, SDL_GetWindowSize, SDL_Init, SDL_LogWarn, SDL_PixelFormat, SDL_RenderFillRect, SDL_RenderPresent, SDL_SetHint, SDL_SetRenderDrawBlendMode, SDL_SetTextureScaleMode, SDL_SetWindowFullscreen, SDL_SetWindowPosition, SDL_SetWindowSize, SDL_SyncWindow, SDL_TextureAccess, SDL_free, SDL_BLENDMODE_BLEND, SDL_HINT_RENDER_DRIVER, SDL_INIT_VIDEO, SDL_ScaleMode, SDL_WINDOW_FULLSCREEN, SDL_WindowFlags
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
#include <emscripten.h>
|
||||||
|
#include <emscripten/html5.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <algorithm> // Para min, max
|
#include <algorithm> // Para min, max
|
||||||
#include <cstring> // Para memcpy
|
#include <cstring> // Para memcpy
|
||||||
@@ -27,6 +31,43 @@
|
|||||||
// Singleton
|
// Singleton
|
||||||
Screen* Screen::instance = nullptr;
|
Screen* Screen::instance = nullptr;
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
// ============================================================================
|
||||||
|
// Restauración del canvas en wasm/Emscripten
|
||||||
|
// ============================================================================
|
||||||
|
// SDL3 + Emscripten no notifica de forma fiable los cambios de estado del
|
||||||
|
// canvas HTML (fullscreen exit vía Esc, rotación del dispositivo, etc.).
|
||||||
|
// Registramos callbacks nativos de Emscripten que delegan en
|
||||||
|
// Screen::handleCanvasResized(), el cual re-aplica el modo de fullscreen y
|
||||||
|
// reajusta la ventana para que SDL salga de su estado interno de fullscreen.
|
||||||
|
//
|
||||||
|
// Los callbacks difieren el trabajo con emscripten_async_call(0ms) porque el
|
||||||
|
// navegador todavía no ha acabado de redimensionar el canvas cuando el evento
|
||||||
|
// se dispara; posponer al siguiente tick garantiza valores estables.
|
||||||
|
//
|
||||||
|
// Referencias: libsdl-org/SDL#13300, libsdl-org/SDL#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 {
|
||||||
|
// Sincronizamos Options::video.fullscreen con el estado real del navegador
|
||||||
|
// antes de diferir la restauración: cuando el usuario sale con Esc no pasa
|
||||||
|
// por toggleFullscreen() y el estado interno quedaría desincronizado.
|
||||||
|
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
|
||||||
|
|
||||||
// Inicializa la instancia única del singleton
|
// Inicializa la instancia única del singleton
|
||||||
void Screen::init() {
|
void Screen::init() {
|
||||||
Screen::instance = new Screen();
|
Screen::instance = new Screen();
|
||||||
@@ -53,7 +94,7 @@ Screen::Screen()
|
|||||||
|
|
||||||
// Crea la textura de destino
|
// Crea la textura de destino
|
||||||
game_canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height);
|
game_canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height);
|
||||||
SDL_SetTextureScaleMode(game_canvas_, SDL_SCALEMODE_NEAREST);
|
SDL_SetTextureScaleMode(game_canvas_, Options::video.scale_mode);
|
||||||
|
|
||||||
// Inicializar buffer de píxeles para SDL3GPU
|
// Inicializar buffer de píxeles para SDL3GPU
|
||||||
pixel_buffer_.resize(static_cast<size_t>(param.game.width) * static_cast<size_t>(param.game.height));
|
pixel_buffer_.resize(static_cast<size_t>(param.game.width) * static_cast<size_t>(param.game.height));
|
||||||
@@ -69,6 +110,14 @@ Screen::Screen()
|
|||||||
// Renderizar una vez la textura vacía para que tenga contenido válido antes de inicializar los shaders (evita pantalla negra)
|
// Renderizar una vez la textura vacía para que tenga contenido válido antes de inicializar los shaders (evita pantalla negra)
|
||||||
SDL_RenderTexture(renderer_, game_canvas_, nullptr, nullptr);
|
SDL_RenderTexture(renderer_, game_canvas_, nullptr, nullptr);
|
||||||
|
|
||||||
|
// Aplicar la configuración inicial completa (vsync + logical presentation +
|
||||||
|
// fullscreen + tamaño de ventana). En Emscripten es necesario porque el
|
||||||
|
// canvas HTML tiene un tamaño propio y SDL_CreateWindow solo no basta para
|
||||||
|
// que SDL sincronice su viewport interno con el canvas real: sin este
|
||||||
|
// applySettings el canvas inicial sale descolocado con barras negras a los
|
||||||
|
// lados y el juego pequeño en el centro hasta el primer toggle de fullscreen.
|
||||||
|
applySettings();
|
||||||
|
|
||||||
// Limpiar renderer
|
// Limpiar renderer
|
||||||
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
|
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
|
||||||
SDL_RenderClear(renderer_);
|
SDL_RenderClear(renderer_);
|
||||||
@@ -145,10 +194,39 @@ void Screen::setFullscreenMode() {
|
|||||||
SDL_SetWindowFullscreen(window_, Options::video.fullscreen);
|
SDL_SetWindowFullscreen(window_, Options::video.fullscreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Camibia entre pantalla completa y ventana
|
// Cambia entre pantalla completa y ventana. Usamos applySettings en vez de
|
||||||
|
// setFullscreenMode porque applySettings también re-aplica la logical
|
||||||
|
// presentation — sin eso, al entrar en fullscreen SDL no recalcula el viewport
|
||||||
|
// y el juego se ve pequeño (especialmente en Android).
|
||||||
void Screen::toggleFullscreen() {
|
void Screen::toggleFullscreen() {
|
||||||
Options::video.fullscreen = !Options::video.fullscreen;
|
Options::video.fullscreen = !Options::video.fullscreen;
|
||||||
setFullscreenMode();
|
applySettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-sincroniza SDL con el estado real del canvas del navegador. Lo invocan los
|
||||||
|
// callbacks nativos de Emscripten (vegeu el bloc al principi del fitxer) cuando
|
||||||
|
// se detecta un fullscreenchange o un orientationchange. Re-aplica fullscreen y
|
||||||
|
// reajusta la ventana porque SDL no emite SDL_EVENT_WINDOW_LEAVE_FULLSCREEN en
|
||||||
|
// Emscripten y su estado interno queda desincronizado al salir con Esc.
|
||||||
|
// Fuera de Emscripten es un no-op (desktop sí emite los events correctamente).
|
||||||
|
void Screen::handleCanvasResized() {
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
// SDL_SetWindowFullscreen es imprescindible para sacar a SDL de su estado
|
||||||
|
// interno de fullscreen cuando el usuario ha salido sin pasar por
|
||||||
|
// toggleFullscreen (onEmFullscreenChange ya ha actualizado Options).
|
||||||
|
SDL_SetWindowFullscreen(window_, Options::video.fullscreen);
|
||||||
|
SDL_SyncWindow(window_);
|
||||||
|
adjustWindowSize();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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__
|
||||||
|
emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_TRUE, onEmFullscreenChange);
|
||||||
|
emscripten_set_orientationchange_callback(nullptr, EM_TRUE, onEmOrientationChange);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cambia el tamaño de la ventana
|
// Cambia el tamaño de la ventana
|
||||||
@@ -424,6 +502,8 @@ auto Screen::initSDLVideo() -> bool {
|
|||||||
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
|
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
|
||||||
SDL_SetRenderVSync(renderer_, Options::video.vsync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
SDL_SetRenderVSync(renderer_, Options::video.vsync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
||||||
|
|
||||||
|
registerEmscriptenEventCallbacks();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -590,6 +670,25 @@ void Screen::toggleVSync() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Aplica Options::video.scale_mode a la textura del canvas de juego
|
||||||
|
void Screen::applyFilter() {
|
||||||
|
SDL_SetTextureScaleMode(game_canvas_, Options::video.scale_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alterna el modo de filtrado entre nearest y linear
|
||||||
|
void Screen::toggleFilter() {
|
||||||
|
Options::video.scale_mode = (Options::video.scale_mode == SDL_SCALEMODE_NEAREST)
|
||||||
|
? SDL_SCALEMODE_LINEAR
|
||||||
|
: SDL_SCALEMODE_NEAREST;
|
||||||
|
applyFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Devuelve true si el backend SDL3GPU está activo y con aceleración hardware
|
||||||
|
auto Screen::isHardwareAccelerated() -> bool {
|
||||||
|
auto* self = Screen::get();
|
||||||
|
return self != nullptr && self->shader_backend_ && self->shader_backend_->isHardwareAccelerated();
|
||||||
|
}
|
||||||
|
|
||||||
// Establece el estado del V-Sync
|
// Establece el estado del V-Sync
|
||||||
void Screen::setVSync(bool enabled) {
|
void Screen::setVSync(bool enabled) {
|
||||||
Options::video.vsync = enabled;
|
Options::video.vsync = enabled;
|
||||||
@@ -611,12 +710,17 @@ void Screen::getSingletons() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aplica los valores de las opciones
|
// Aplica los valores de las opciones.
|
||||||
|
// IMPORTANTE: el orden importa. SDL_SetRenderLogicalPresentation calcula el
|
||||||
|
// viewport en función del tamaño actual de la ventana SDL, así que DEBE llamarse
|
||||||
|
// DESPUÉS de setFullscreenMode/adjustWindowSize — si no, al entrar en fullscreen
|
||||||
|
// el viewport queda cacheado al tamaño de la ventana pequeña previa y el juego
|
||||||
|
// se ve pequeño y centrado con barras negras alrededor.
|
||||||
void Screen::applySettings() {
|
void Screen::applySettings() {
|
||||||
SDL_SetRenderVSync(renderer_, Options::video.vsync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
SDL_SetRenderVSync(renderer_, Options::video.vsync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
||||||
SDL_SetRenderLogicalPresentation(Screen::get()->getRenderer(), param.game.width, param.game.height, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
|
||||||
setFullscreenMode();
|
setFullscreenMode();
|
||||||
adjustWindowSize();
|
adjustWindowSize();
|
||||||
|
SDL_SetRenderLogicalPresentation(renderer_, param.game.width, param.game.height, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crea el objeto de texto
|
// Crea el objeto de texto
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class Screen {
|
|||||||
// --- Configuración de ventana y render ---
|
// --- Configuración de ventana y render ---
|
||||||
void setFullscreenMode(); // Establece el modo de pantalla completa
|
void setFullscreenMode(); // Establece el modo de pantalla completa
|
||||||
void toggleFullscreen(); // Cambia entre pantalla completa y ventana
|
void toggleFullscreen(); // 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 setWindowZoom(int zoom); // Cambia el tamaño de la ventana
|
void setWindowZoom(int zoom); // Cambia el tamaño de la ventana
|
||||||
auto decWindowSize() -> bool; // Reduce el tamaño de la ventana
|
auto decWindowSize() -> bool; // Reduce el tamaño de la ventana
|
||||||
auto incWindowSize() -> bool; // Aumenta el tamaño de la ventana
|
auto incWindowSize() -> bool; // Aumenta el tamaño de la ventana
|
||||||
@@ -48,6 +49,8 @@ class Screen {
|
|||||||
static void nextPostFXPreset(); // Avanza al siguiente preset PostFX
|
static void nextPostFXPreset(); // Avanza al siguiente preset PostFX
|
||||||
static void nextCrtPiPreset(); // Avanza al siguiente preset CrtPi
|
static void nextCrtPiPreset(); // Avanza al siguiente preset CrtPi
|
||||||
static void toggleSupersampling(); // Alterna supersampling
|
static void toggleSupersampling(); // Alterna supersampling
|
||||||
|
void toggleFilter(); // Alterna SDL_SCALEMODE_NEAREST ↔ SDL_SCALEMODE_LINEAR
|
||||||
|
void applyFilter(); // Aplica Options::video.scale_mode a game_canvas_
|
||||||
void toggleIntegerScale();
|
void toggleIntegerScale();
|
||||||
void toggleVSync(); // Alterna entre activar y desactivar el V-Sync
|
void toggleVSync(); // Alterna entre activar y desactivar el V-Sync
|
||||||
void setVSync(bool enabled); // Establece el estado del V-Sync
|
void setVSync(bool enabled); // Establece el estado del V-Sync
|
||||||
@@ -59,6 +62,7 @@ class Screen {
|
|||||||
void hide() { SDL_HideWindow(window_); } // Oculta la ventana
|
void hide() { SDL_HideWindow(window_); } // Oculta la ventana
|
||||||
void getSingletons(); // Obtiene los punteros a los singletones
|
void getSingletons(); // Obtiene los punteros a los singletones
|
||||||
[[nodiscard]] static auto getVSync() -> bool { return Options::video.vsync; } // Obtiene el valor de V-Sync
|
[[nodiscard]] static auto getVSync() -> bool { return Options::video.vsync; } // Obtiene el valor de V-Sync
|
||||||
|
[[nodiscard]] static auto isHardwareAccelerated() -> bool; // True si SDL3GPU está activo
|
||||||
[[nodiscard]] auto getText() const -> std::shared_ptr<Text> { return text_; } // Obtiene el puntero al texto de Screen
|
[[nodiscard]] auto getText() const -> std::shared_ptr<Text> { return text_; } // Obtiene el puntero al texto de Screen
|
||||||
|
|
||||||
// --- Display Monitor getters ---
|
// --- Display Monitor getters ---
|
||||||
@@ -232,18 +236,19 @@ class Screen {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// --- Métodos internos ---
|
// --- Métodos internos ---
|
||||||
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
|
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
|
||||||
void renderFlash(); // Dibuja el efecto de flash en la pantalla
|
void registerEmscriptenEventCallbacks(); // Registra callbacks nativos para restaurar el canvas en wasm (no-op fuera de emscripten)
|
||||||
void renderShake(); // Aplica el efecto de agitar la pantalla
|
void renderFlash(); // Dibuja el efecto de flash en la pantalla
|
||||||
void renderInfo() const; // Muestra información por pantalla
|
void renderShake(); // Aplica el efecto de agitar la pantalla
|
||||||
void renderPresent(); // Selecciona y ejecuta el método de renderizado adecuado
|
void renderInfo() const; // Muestra información por pantalla
|
||||||
void applyCurrentPostFXPreset(); // Aplica el preset PostFX activo al backend
|
void renderPresent(); // Selecciona y ejecuta el método de renderizado adecuado
|
||||||
void applyCurrentCrtPiPreset(); // Aplica el preset CrtPi activo al backend
|
void applyCurrentPostFXPreset(); // Aplica el preset PostFX activo al backend
|
||||||
void adjustWindowSize(); // Calcula el tamaño de la ventana
|
void applyCurrentCrtPiPreset(); // Aplica el preset CrtPi activo al backend
|
||||||
void getDisplayInfo(); // Obtiene información sobre la pantalla
|
void adjustWindowSize(); // Calcula el tamaño de la ventana
|
||||||
void renderOverlays(); // Renderiza todos los overlays y efectos
|
void getDisplayInfo(); // Obtiene información sobre la pantalla
|
||||||
void renderAttenuate(); // Atenúa la pantalla
|
void renderOverlays(); // Renderiza todos los overlays y efectos
|
||||||
void createText(); // Crea el objeto de texto
|
void renderAttenuate(); // Atenúa la pantalla
|
||||||
|
void createText(); // Crea el objeto de texto
|
||||||
|
|
||||||
// --- Constructores y destructor privados (singleton) ---
|
// --- Constructores y destructor privados (singleton) ---
|
||||||
Screen(); // Constructor privado
|
Screen(); // Constructor privado
|
||||||
|
|||||||
@@ -8,11 +8,11 @@
|
|||||||
#include <iostream> // Para std::cout
|
#include <iostream> // Para 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__
|
||||||
|
|||||||
2
source/core/rendering/sdl3gpu/spv/.clang-format
Normal file
2
source/core/rendering/sdl3gpu/spv/.clang-format
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
DisableFormat: true
|
||||||
|
SortIncludes: Never
|
||||||
4
source/core/rendering/sdl3gpu/spv/.clang-tidy
Normal file
4
source/core/rendering/sdl3gpu/spv/.clang-tidy
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# source/core/rendering/sdl3gpu/spv/.clang-tidy
|
||||||
|
Checks: '-*'
|
||||||
|
WarningsAsErrors: ''
|
||||||
|
HeaderFilterRegex: ''
|
||||||
@@ -195,17 +195,17 @@ namespace Defaults::Video {
|
|||||||
|
|
||||||
namespace Defaults::Music {
|
namespace Defaults::Music {
|
||||||
constexpr bool ENABLED = true;
|
constexpr bool ENABLED = true;
|
||||||
constexpr int VOLUME = 100;
|
constexpr float VOLUME = 0.8F;
|
||||||
} // namespace Defaults::Music
|
} // namespace Defaults::Music
|
||||||
|
|
||||||
namespace Defaults::Sound {
|
namespace Defaults::Sound {
|
||||||
constexpr bool ENABLED = true;
|
constexpr bool ENABLED = true;
|
||||||
constexpr int VOLUME = 100;
|
constexpr float VOLUME = 1.0F;
|
||||||
} // namespace Defaults::Sound
|
} // namespace Defaults::Sound
|
||||||
|
|
||||||
namespace Defaults::Audio {
|
namespace Defaults::Audio {
|
||||||
constexpr bool ENABLED = true;
|
constexpr bool ENABLED = true;
|
||||||
constexpr int VOLUME = 100;
|
constexpr float VOLUME = 1.0F;
|
||||||
} // namespace Defaults::Audio
|
} // namespace Defaults::Audio
|
||||||
|
|
||||||
namespace Defaults::Settings {
|
namespace Defaults::Settings {
|
||||||
|
|||||||
5584
source/external/stb_vorbis.c
vendored
Normal file
5584
source/external/stb_vorbis.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
5631
source/external/stb_vorbis.h
vendored
5631
source/external/stb_vorbis.h
vendored
File diff suppressed because it is too large
Load Diff
@@ -459,7 +459,7 @@ namespace Options {
|
|||||||
}
|
}
|
||||||
if (aud.contains("volume")) {
|
if (aud.contains("volume")) {
|
||||||
try {
|
try {
|
||||||
audio.volume = std::clamp(aud["volume"].get_value<int>(), 0, 100);
|
audio.volume = std::clamp(aud["volume"].get_value<float>(), 0.0F, 1.0F);
|
||||||
} catch (...) {}
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
if (aud.contains("music")) {
|
if (aud.contains("music")) {
|
||||||
@@ -471,7 +471,7 @@ namespace Options {
|
|||||||
}
|
}
|
||||||
if (mus.contains("volume")) {
|
if (mus.contains("volume")) {
|
||||||
try {
|
try {
|
||||||
audio.music.volume = std::clamp(mus["volume"].get_value<int>(), 0, 100);
|
audio.music.volume = std::clamp(mus["volume"].get_value<float>(), 0.0F, 1.0F);
|
||||||
} catch (...) {}
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -484,7 +484,7 @@ namespace Options {
|
|||||||
}
|
}
|
||||||
if (snd.contains("volume")) {
|
if (snd.contains("volume")) {
|
||||||
try {
|
try {
|
||||||
audio.sound.volume = std::clamp(snd["volume"].get_value<int>(), 0, 100);
|
audio.sound.volume = std::clamp(snd["volume"].get_value<float>(), 0.0F, 1.0F);
|
||||||
} catch (...) {}
|
} catch (...) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,19 +94,19 @@ namespace Options {
|
|||||||
|
|
||||||
struct Music {
|
struct Music {
|
||||||
bool enabled = Defaults::Music::ENABLED; // Indica si la música suena o no
|
bool enabled = Defaults::Music::ENABLED; // Indica si la música suena o no
|
||||||
int volume = Defaults::Music::VOLUME; // Volumen de la música
|
float volume = Defaults::Music::VOLUME; // Volumen de la música (0.0..1.0)
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Sound {
|
struct Sound {
|
||||||
bool enabled = Defaults::Sound::ENABLED; // Indica si los sonidos suenan o no
|
bool enabled = Defaults::Sound::ENABLED; // Indica si los sonidos suenan o no
|
||||||
int volume = Defaults::Sound::VOLUME; // Volumen de los sonidos
|
float volume = Defaults::Sound::VOLUME; // Volumen de los sonidos (0.0..1.0)
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Audio {
|
struct Audio {
|
||||||
Music music; // Opciones para la música
|
Music music; // Opciones para la música
|
||||||
Sound sound; // Opciones para los efectos de sonido
|
Sound sound; // Opciones para los efectos de sonido
|
||||||
bool enabled = Defaults::Audio::ENABLED; // Indica si el audio está activo o no
|
bool enabled = Defaults::Audio::ENABLED; // Indica si el audio está activo o no
|
||||||
int volume = Defaults::Audio::VOLUME; // Volumen general del audio
|
float volume = Defaults::Audio::VOLUME; // Volumen general del audio (0.0..1.0)
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Loading {
|
struct Loading {
|
||||||
|
|||||||
@@ -106,6 +106,40 @@ class IntOption : public MenuOption {
|
|||||||
int min_value_, max_value_, step_value_;
|
int min_value_, max_value_, step_value_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// VolumeOption: emmagatzema un float 0.0..1.0 però es mostra/edita com a int 0..100.
|
||||||
|
// Pensat per als sliders de volum que l'usuari veu com percentatge però que
|
||||||
|
// internament viuen en float (API unificada del motor d'àudio).
|
||||||
|
class VolumeOption : public MenuOption {
|
||||||
|
public:
|
||||||
|
VolumeOption(const std::string& cap, ServiceMenu::SettingsGroup grp, float* var, int step_percent = 5)
|
||||||
|
: MenuOption(cap, grp),
|
||||||
|
linked_variable_(var),
|
||||||
|
step_value_(step_percent) {}
|
||||||
|
|
||||||
|
[[nodiscard]] auto getBehavior() const -> Behavior override { return Behavior::ADJUST; }
|
||||||
|
[[nodiscard]] auto getValueAsString() const -> std::string override {
|
||||||
|
int pct = static_cast<int>(*linked_variable_ * 100.0F + 0.5F);
|
||||||
|
return std::to_string(pct);
|
||||||
|
}
|
||||||
|
void adjustValue(bool adjust_up) override {
|
||||||
|
int current = static_cast<int>(*linked_variable_ * 100.0F + 0.5F);
|
||||||
|
int new_value = std::clamp(current + (adjust_up ? step_value_ : -step_value_), 0, 100);
|
||||||
|
*linked_variable_ = static_cast<float>(new_value) / 100.0F;
|
||||||
|
}
|
||||||
|
auto getMaxValueWidth(Text* text_renderer) const -> int override {
|
||||||
|
int max_width = 0;
|
||||||
|
for (int value = 0; value <= 100; value += step_value_) {
|
||||||
|
int width = text_renderer->length(std::to_string(value), -2);
|
||||||
|
max_width = std::max(max_width, width);
|
||||||
|
}
|
||||||
|
return max_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
float* linked_variable_;
|
||||||
|
int step_value_;
|
||||||
|
};
|
||||||
|
|
||||||
class ListOption : public MenuOption {
|
class ListOption : public MenuOption {
|
||||||
public:
|
public:
|
||||||
ListOption(const std::string& cap, ServiceMenu::SettingsGroup grp, std::vector<std::string> values, std::function<std::string()> current_value_getter, std::function<void(const std::string&)> new_value_setter)
|
ListOption(const std::string& cap, ServiceMenu::SettingsGroup grp, std::vector<std::string> values, std::function<std::string()> current_value_getter, std::function<void(const std::string&)> new_value_setter)
|
||||||
|
|||||||
@@ -466,34 +466,46 @@ void ServiceMenu::initializeOptions() {
|
|||||||
SettingsGroup::VIDEO,
|
SettingsGroup::VIDEO,
|
||||||
&Options::video.integer_scale));
|
&Options::video.integer_scale));
|
||||||
|
|
||||||
|
// FILTER: Nearest / Linear (solo visible en el fallback SDL, sin GPU acelerada)
|
||||||
|
{
|
||||||
|
std::vector<std::string> filter_values = {"Nearest", "Linear"};
|
||||||
|
auto filter_getter = []() -> std::string {
|
||||||
|
return (Options::video.scale_mode == SDL_SCALEMODE_LINEAR) ? "Linear" : "Nearest";
|
||||||
|
};
|
||||||
|
auto filter_setter = [](const std::string& val) {
|
||||||
|
Options::video.scale_mode = (val == "Linear") ? SDL_SCALEMODE_LINEAR : SDL_SCALEMODE_NEAREST;
|
||||||
|
if (Screen::get() != nullptr) { Screen::get()->applyFilter(); }
|
||||||
|
};
|
||||||
|
options_.push_back(std::make_unique<ListOption>(
|
||||||
|
Lang::getText("[SERVICE_MENU] FILTER"),
|
||||||
|
SettingsGroup::VIDEO,
|
||||||
|
filter_values,
|
||||||
|
filter_getter,
|
||||||
|
filter_setter));
|
||||||
|
}
|
||||||
|
|
||||||
// AUDIO
|
// AUDIO
|
||||||
options_.push_back(std::make_unique<BoolOption>(
|
options_.push_back(std::make_unique<BoolOption>(
|
||||||
Lang::getText("[SERVICE_MENU] AUDIO"),
|
Lang::getText("[SERVICE_MENU] AUDIO"),
|
||||||
SettingsGroup::AUDIO,
|
SettingsGroup::AUDIO,
|
||||||
&Options::audio.enabled));
|
&Options::audio.enabled));
|
||||||
|
|
||||||
options_.push_back(std::make_unique<IntOption>(
|
options_.push_back(std::make_unique<VolumeOption>(
|
||||||
Lang::getText("[SERVICE_MENU] MAIN_VOLUME"),
|
Lang::getText("[SERVICE_MENU] MAIN_VOLUME"),
|
||||||
SettingsGroup::AUDIO,
|
SettingsGroup::AUDIO,
|
||||||
&Options::audio.volume,
|
&Options::audio.volume,
|
||||||
0,
|
|
||||||
100,
|
|
||||||
5));
|
5));
|
||||||
|
|
||||||
options_.push_back(std::make_unique<IntOption>(
|
options_.push_back(std::make_unique<VolumeOption>(
|
||||||
Lang::getText("[SERVICE_MENU] MUSIC_VOLUME"),
|
Lang::getText("[SERVICE_MENU] MUSIC_VOLUME"),
|
||||||
SettingsGroup::AUDIO,
|
SettingsGroup::AUDIO,
|
||||||
&Options::audio.music.volume,
|
&Options::audio.music.volume,
|
||||||
0,
|
|
||||||
100,
|
|
||||||
5));
|
5));
|
||||||
|
|
||||||
options_.push_back(std::make_unique<IntOption>(
|
options_.push_back(std::make_unique<VolumeOption>(
|
||||||
Lang::getText("[SERVICE_MENU] SFX_VOLUME"),
|
Lang::getText("[SERVICE_MENU] SFX_VOLUME"),
|
||||||
SettingsGroup::AUDIO,
|
SettingsGroup::AUDIO,
|
||||||
&Options::audio.sound.volume,
|
&Options::audio.sound.volume,
|
||||||
0,
|
|
||||||
100,
|
|
||||||
5));
|
5));
|
||||||
|
|
||||||
// SETTINGS
|
// SETTINGS
|
||||||
@@ -644,26 +656,44 @@ void ServiceMenu::setHiddenOptions() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Las opciones de shader solo tienen sentido cuando SDL3GPU está activo.
|
||||||
|
// Las opciones de filtro SDL solo tienen sentido en el fallback sin GPU.
|
||||||
|
// Ambos checks son runtime para cubrir tanto builds sin shaders como fallos de inicialización.
|
||||||
|
const bool HW_ACCEL = Screen::isHardwareAccelerated();
|
||||||
|
|
||||||
|
{
|
||||||
|
auto* option = getOptionByCaption(Lang::getText("[SERVICE_MENU] FILTER"));
|
||||||
|
if (option != nullptr) {
|
||||||
|
option->setHidden(HW_ACCEL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto* option = getOptionByCaption(Lang::getText("[SERVICE_MENU] SHADER"));
|
||||||
|
if (option != nullptr) {
|
||||||
|
option->setHidden(!HW_ACCEL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto* option = getOptionByCaption(Lang::getText("[SERVICE_MENU] SHADER_PRESET"));
|
auto* option = getOptionByCaption(Lang::getText("[SERVICE_MENU] SHADER_PRESET"));
|
||||||
if (option != nullptr) {
|
if (option != nullptr) {
|
||||||
option->setHidden(!Options::video.shader.enabled);
|
option->setHidden(!HW_ACCEL || !Options::video.shader.enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto* option = getOptionByCaption(Lang::getText("[SERVICE_MENU] SUPERSAMPLING"));
|
auto* option = getOptionByCaption(Lang::getText("[SERVICE_MENU] SUPERSAMPLING"));
|
||||||
if (option != nullptr) {
|
if (option != nullptr) {
|
||||||
option->setHidden(!Options::video.shader.enabled || Options::video.shader.current_shader != Rendering::ShaderType::POSTFX);
|
option->setHidden(!HW_ACCEL || !Options::video.shader.enabled || Options::video.shader.current_shader != Rendering::ShaderType::POSTFX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __EMSCRIPTEN__
|
#ifdef __EMSCRIPTEN__
|
||||||
// En la versión web no tiene sentido exponer: shaders (build sin shaders),
|
// En la versión web no tiene sentido exponer: permitir apagar el sistema
|
||||||
// permitir apagar el sistema (no aplica al navegador) ni salir del juego
|
// (no aplica al navegador) ni salir del juego (lo gestiona el navegador).
|
||||||
// (lo gestiona el navegador).
|
// SHADER ya queda oculto por el check de HW_ACCEL de arriba.
|
||||||
for (const auto& caption : {
|
for (const auto& caption : {
|
||||||
Lang::getText("[SERVICE_MENU] SHADER"),
|
|
||||||
Lang::getText("[SERVICE_MENU] ENABLE_SHUTDOWN"),
|
Lang::getText("[SERVICE_MENU] ENABLE_SHUTDOWN"),
|
||||||
Lang::getText("[SERVICE_MENU] QUIT")}) {
|
Lang::getText("[SERVICE_MENU] QUIT")}) {
|
||||||
auto* option = getOptionByCaption(caption);
|
auto* option = getOptionByCaption(caption);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
namespace Texts {
|
namespace Texts {
|
||||||
constexpr const char* VERSION = "2026-04-14"; // Versión del juego (también usada por el Makefile)
|
constexpr const char* VERSION = "2026.04.14"; // Versión del juego (también usada por el Makefile)
|
||||||
} // namespace Texts
|
} // namespace Texts
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
# Makefile para herramientas de Coffee Crisis Arcade Edition
|
|
||||||
# =========================================================
|
|
||||||
|
|
||||||
# Variables
|
|
||||||
CXX := g++
|
|
||||||
CXXFLAGS := -std=c++20 -Wall -Os -I../../source
|
|
||||||
SOURCES := pack_resources.cpp ../../source/core/resources/resource_pack.cpp
|
|
||||||
TARGET := pack_resources
|
|
||||||
CLEAN_FILES := pack_resources *.pack *.o
|
|
||||||
|
|
||||||
# Detectar sistema operativo
|
|
||||||
ifeq ($(OS),Windows_NT)
|
|
||||||
DETECTED_OS := Windows
|
|
||||||
TARGET := $(TARGET).exe
|
|
||||||
CLEAN_CMD := del /Q
|
|
||||||
FixPath = $(subst /,\\,$1)
|
|
||||||
else
|
|
||||||
DETECTED_OS := $(shell uname -s)
|
|
||||||
CLEAN_CMD := rm -f
|
|
||||||
FixPath = $1
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Reglas principales
|
|
||||||
.PHONY: all pack_tool pack clean help test_pack
|
|
||||||
|
|
||||||
# Compilar herramienta de empaquetado
|
|
||||||
all: pack_tool
|
|
||||||
|
|
||||||
pack_tool:
|
|
||||||
@echo "Compilando herramienta de empaquetado para $(DETECTED_OS)..."
|
|
||||||
$(CXX) $(CXXFLAGS) $(SOURCES) -o $(TARGET)
|
|
||||||
@echo "✓ Herramienta compilada: $(TARGET)"
|
|
||||||
|
|
||||||
# Limpiar archivos generados
|
|
||||||
clean:
|
|
||||||
@echo "Limpiando archivos generados..."
|
|
||||||
$(CLEAN_CMD) $(call FixPath,$(CLEAN_FILES))
|
|
||||||
@echo "✓ Archivos limpiados"
|
|
||||||
|
|
||||||
# Crear pack de recursos final (invocado desde Makefile raíz)
|
|
||||||
pack: pack_tool
|
|
||||||
ifeq ($(OS),Windows_NT)
|
|
||||||
.\$(TARGET) ..\..\data ..\..\resources.pack
|
|
||||||
else
|
|
||||||
./$(TARGET) ../../data ../../resources.pack
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Crear pack de recursos de prueba
|
|
||||||
test_pack: pack_tool
|
|
||||||
@echo "Creando pack de recursos de prueba..."
|
|
||||||
ifeq ($(OS),Windows_NT)
|
|
||||||
.\$(TARGET) ..\..\data test_resources.pack
|
|
||||||
else
|
|
||||||
./$(TARGET) ../../data test_resources.pack
|
|
||||||
endif
|
|
||||||
@echo "✓ Pack de prueba creado: test_resources.pack"
|
|
||||||
|
|
||||||
# Crear pack de recursos final
|
|
||||||
create_pack: pack_tool
|
|
||||||
@echo "Creando pack de recursos final..."
|
|
||||||
ifeq ($(OS),Windows_NT)
|
|
||||||
.\$(TARGET) ..\..\data ..\..\resources.pack
|
|
||||||
else
|
|
||||||
./$(TARGET) ../../data ../../resources.pack
|
|
||||||
endif
|
|
||||||
@echo "✓ Pack final creado: ../../resources.pack"
|
|
||||||
|
|
||||||
# Mostrar ayuda
|
|
||||||
help:
|
|
||||||
@echo "Makefile para herramientas de Coffee Crisis Arcade Edition"
|
|
||||||
@echo "========================================================="
|
|
||||||
@echo ""
|
|
||||||
@echo "Comandos disponibles:"
|
|
||||||
@echo " all - Compilar herramienta de empaquetado (por defecto)"
|
|
||||||
@echo " pack_tool - Compilar herramienta de empaquetado"
|
|
||||||
@echo " test_pack - Crear pack de recursos de prueba"
|
|
||||||
@echo " create_pack - Crear pack de recursos final"
|
|
||||||
@echo " clean - Limpiar archivos generados"
|
|
||||||
@echo " help - Mostrar esta ayuda"
|
|
||||||
@echo ""
|
|
||||||
@echo "Ejemplos de uso:"
|
|
||||||
@echo " make # Compilar herramienta"
|
|
||||||
@echo " make test_pack # Crear pack de prueba"
|
|
||||||
@echo " make create_pack # Crear pack final"
|
|
||||||
@echo " make clean # Limpiar archivos"
|
|
||||||
101
tools/shaders/compile_spirv.cmake
Normal file
101
tools/shaders/compile_spirv.cmake
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# compile_spirv.cmake
|
||||||
|
# Compila shaders GLSL a SPIR-V y genera headers C++ embebibles.
|
||||||
|
# Multiplataforma: Windows, macOS, Linux (no requiere bash, xxd ni /tmp/).
|
||||||
|
#
|
||||||
|
# Invocado por CMakeLists.txt con:
|
||||||
|
# cmake -D GLSLC=<path> -D SHADERS_DIR=<path> -D HEADERS_DIR=<path> -P compile_spirv.cmake
|
||||||
|
#
|
||||||
|
# También puede ejecutarse manualmente desde la raíz del proyecto:
|
||||||
|
# cmake -D GLSLC=glslc -D SHADERS_DIR=data/shaders -D HEADERS_DIR=source/core/rendering/sdl3gpu -P tools/shaders/compile_spirv.cmake
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
cmake_policy(SET CMP0007 NEW)
|
||||||
|
|
||||||
|
# Lista de shaders: fuente relativa a SHADERS_DIR
|
||||||
|
set(SHADER_SOURCES
|
||||||
|
"postfx.vert"
|
||||||
|
"postfx.frag"
|
||||||
|
"upscale.frag"
|
||||||
|
"downscale.frag"
|
||||||
|
"crtpi_frag.glsl"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Nombre de la variable C++ para cada shader (mismo orden)
|
||||||
|
set(SHADER_VARS
|
||||||
|
"kpostfx_vert_spv"
|
||||||
|
"kpostfx_frag_spv"
|
||||||
|
"kupscale_frag_spv"
|
||||||
|
"kdownscale_frag_spv"
|
||||||
|
"kcrtpi_frag_spv"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Flags extra de glslc para cada shader (vacío si no hay)
|
||||||
|
set(SHADER_FLAGS
|
||||||
|
""
|
||||||
|
""
|
||||||
|
""
|
||||||
|
""
|
||||||
|
"-fshader-stage=frag"
|
||||||
|
)
|
||||||
|
|
||||||
|
list(LENGTH SHADER_SOURCES NUM_SHADERS)
|
||||||
|
math(EXPR LAST_IDX "${NUM_SHADERS} - 1")
|
||||||
|
|
||||||
|
foreach(IDX RANGE ${LAST_IDX})
|
||||||
|
list(GET SHADER_SOURCES ${IDX} SRC_NAME)
|
||||||
|
list(GET SHADER_VARS ${IDX} VAR)
|
||||||
|
list(GET SHADER_FLAGS ${IDX} EXTRA_FLAG)
|
||||||
|
|
||||||
|
# Derivar nombre del header desde la variable: kpostfx_vert_spv → postfx_vert_spv.h
|
||||||
|
string(REGEX REPLACE "^k" "" HDR_BASE "${VAR}")
|
||||||
|
set(SRC "${SHADERS_DIR}/${SRC_NAME}")
|
||||||
|
set(SPV "${HEADERS_DIR}/${HDR_BASE}.spv")
|
||||||
|
set(HDR "${HEADERS_DIR}/${HDR_BASE}.h")
|
||||||
|
|
||||||
|
message(STATUS "Compilando ${SRC} ...")
|
||||||
|
|
||||||
|
if(EXTRA_FLAG)
|
||||||
|
execute_process(
|
||||||
|
COMMAND "${GLSLC}" "${EXTRA_FLAG}" "${SRC}" -o "${SPV}"
|
||||||
|
RESULT_VARIABLE GLSLC_RESULT
|
||||||
|
ERROR_VARIABLE GLSLC_ERROR
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
execute_process(
|
||||||
|
COMMAND "${GLSLC}" "${SRC}" -o "${SPV}"
|
||||||
|
RESULT_VARIABLE GLSLC_RESULT
|
||||||
|
ERROR_VARIABLE GLSLC_ERROR
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT GLSLC_RESULT EQUAL 0)
|
||||||
|
message(FATAL_ERROR "glslc falló para ${SRC}:\n${GLSLC_ERROR}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Leer binario SPV como hex (sin separadores: "0302230700...")
|
||||||
|
file(READ "${SPV}" HEX_DATA HEX)
|
||||||
|
# Dividir en pares de caracteres hex → lista de bytes
|
||||||
|
string(REGEX MATCHALL ".." BYTES "${HEX_DATA}")
|
||||||
|
list(LENGTH BYTES NUM_BYTES)
|
||||||
|
|
||||||
|
# Construir el cuerpo del array C++ con un byte por línea
|
||||||
|
set(ARRAY_BODY "")
|
||||||
|
foreach(BYTE ${BYTES})
|
||||||
|
string(APPEND ARRAY_BODY " 0x${BYTE},\n")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
file(WRITE "${HDR}"
|
||||||
|
"#pragma once\n"
|
||||||
|
"#include <cstddef>\n"
|
||||||
|
"#include <cstdint>\n"
|
||||||
|
"static const uint8_t ${VAR}[] = {\n"
|
||||||
|
"${ARRAY_BODY}"
|
||||||
|
"};\n"
|
||||||
|
"static const size_t ${VAR}_size = ${NUM_BYTES};\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
file(REMOVE "${SPV}")
|
||||||
|
message(STATUS " -> ${HDR} (${NUM_BYTES} bytes)")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
message(STATUS "Shaders SPIR-V compilados correctamente.")
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Compile Vulkan GLSL shaders to SPIR-V and update the C++ headers used by SDL3GPUShader.
|
|
||||||
# Required: glslc (from Vulkan SDK or: brew install glslang / apt install glslang-tools)
|
|
||||||
#
|
|
||||||
# Run from the project root: tools/shaders/compile_spirv.sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
SHADERS_DIR="data/shaders"
|
|
||||||
HEADERS_DIR="source/core/rendering/sdl3gpu"
|
|
||||||
|
|
||||||
if ! command -v glslc &> /dev/null; then
|
|
||||||
echo "ERROR: glslc not found. Install Vulkan SDK or run:"
|
|
||||||
echo " macOS: brew install glslang"
|
|
||||||
echo " Linux: sudo apt install glslang-tools"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Compiling SPIR-V shaders..."
|
|
||||||
|
|
||||||
glslc "${SHADERS_DIR}/postfx.vert" -o /tmp/postfx.vert.spv
|
|
||||||
glslc "${SHADERS_DIR}/postfx.frag" -o /tmp/postfx.frag.spv
|
|
||||||
glslc -fshader-stage=fragment "${SHADERS_DIR}/crtpi_frag.glsl" -o /tmp/crtpi_frag.spv
|
|
||||||
glslc "${SHADERS_DIR}/upscale.frag" -o /tmp/upscale.frag.spv
|
|
||||||
glslc "${SHADERS_DIR}/downscale.frag" -o /tmp/downscale.frag.spv
|
|
||||||
|
|
||||||
echo "Generating C++ headers..."
|
|
||||||
|
|
||||||
xxd -i /tmp/postfx.vert.spv | \
|
|
||||||
sed 's/unsigned char .*postfx_vert_spv\[\]/static const uint8_t kpostfx_vert_spv[]/' | \
|
|
||||||
sed 's/unsigned int .*postfx_vert_spv_len/static const size_t kpostfx_vert_spv_size/' \
|
|
||||||
> "${HEADERS_DIR}/postfx_vert_spv.h"
|
|
||||||
|
|
||||||
xxd -i /tmp/postfx.frag.spv | \
|
|
||||||
sed 's/unsigned char .*postfx_frag_spv\[\]/static const uint8_t kpostfx_frag_spv[]/' | \
|
|
||||||
sed 's/unsigned int .*postfx_frag_spv_len/static const size_t kpostfx_frag_spv_size/' \
|
|
||||||
> "${HEADERS_DIR}/postfx_frag_spv.h"
|
|
||||||
|
|
||||||
xxd -i /tmp/crtpi_frag.spv | \
|
|
||||||
sed 's/unsigned char .*crtpi_frag_spv\[\]/static const uint8_t kcrtpi_frag_spv[]/' | \
|
|
||||||
sed 's/unsigned int .*crtpi_frag_spv_len/static const size_t kcrtpi_frag_spv_size/' \
|
|
||||||
> "${HEADERS_DIR}/crtpi_frag_spv.h"
|
|
||||||
|
|
||||||
xxd -i /tmp/upscale.frag.spv | \
|
|
||||||
sed 's/unsigned char .*upscale_frag_spv\[\]/static const uint8_t kupscale_frag_spv[]/' | \
|
|
||||||
sed 's/unsigned int .*upscale_frag_spv_len/static const size_t kupscale_frag_spv_size/' \
|
|
||||||
> "${HEADERS_DIR}/upscale_frag_spv.h"
|
|
||||||
|
|
||||||
xxd -i /tmp/downscale.frag.spv | \
|
|
||||||
sed 's/unsigned char .*downscale_frag_spv\[\]/static const uint8_t kdownscale_frag_spv[]/' | \
|
|
||||||
sed 's/unsigned int .*downscale_frag_spv_len/static const size_t kdownscale_frag_spv_size/' \
|
|
||||||
> "${HEADERS_DIR}/downscale_frag_spv.h"
|
|
||||||
|
|
||||||
# Prepend required includes to the headers
|
|
||||||
for f in "${HEADERS_DIR}/postfx_vert_spv.h" "${HEADERS_DIR}/postfx_frag_spv.h" "${HEADERS_DIR}/crtpi_frag_spv.h" "${HEADERS_DIR}/upscale_frag_spv.h" "${HEADERS_DIR}/downscale_frag_spv.h"; do
|
|
||||||
echo -e "#pragma once\n#include <cstdint>\n#include <cstddef>\n$(cat "$f")" > "$f"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Done. Headers updated in ${HEADERS_DIR}/"
|
|
||||||
echo " postfx_vert_spv.h"
|
|
||||||
echo " postfx_frag_spv.h"
|
|
||||||
echo " crtpi_frag_spv.h"
|
|
||||||
echo " upscale_frag_spv.h"
|
|
||||||
echo " downscale_frag_spv.h"
|
|
||||||
echo "Rebuild the project to use the new shaders."
|
|
||||||
Reference in New Issue
Block a user