Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0647eceab7 | |||
| a903343385 | |||
| 17341f923d | |||
| fcd2718794 | |||
| c9d16959d0 | |||
| ce662609f3 | |||
| 583d901162 | |||
| 61d35374e0 | |||
| 194c8187f9 | |||
| 490d5c923b | |||
| 0e49f35d3b | |||
| b9cee1bc70 | |||
| 3390d01ef6 | |||
| 561028ff04 | |||
| 671583ebbe | |||
| e941502615 | |||
| 8bab2da2ed | |||
| 5ef278ce70 | |||
| 14103175a9 |
+71
-44
@@ -8,73 +8,100 @@ Checks:
|
|||||||
- -bugprone-integer-division
|
- -bugprone-integer-division
|
||||||
- -bugprone-easily-swappable-parameters
|
- -bugprone-easily-swappable-parameters
|
||||||
- -bugprone-narrowing-conversions
|
- -bugprone-narrowing-conversions
|
||||||
- -modernize-avoid-c-arrays,-warnings-as-errors
|
- -modernize-avoid-c-arrays
|
||||||
|
|
||||||
WarningsAsErrors: '*'
|
WarningsAsErrors: '*'
|
||||||
# Solo incluir archivos de tu código fuente (external tiene su propio .clang-tidy)
|
# Headers nostres (excloem source/external/ que conté dependències de tercers no editables)
|
||||||
# Excluye los headers SPIR-V generados en rendering/sdl3gpu/
|
HeaderFilterRegex: 'source/(core|game|utils)/'
|
||||||
HeaderFilterRegex: 'source/(?!external/|rendering/sdl3gpu/.*_spv\.h).*'
|
|
||||||
FormatStyle: file
|
FormatStyle: file
|
||||||
|
|
||||||
CheckOptions:
|
CheckOptions:
|
||||||
# bugprone-empty-catch: aceptar catches vacíos marcados con @INTENTIONAL en un comentario
|
# bugprone-empty-catch: aceptar catches vacíos marcados con @INTENTIONAL en un comentario
|
||||||
- { key: bugprone-empty-catch.IgnoreCatchWithKeywords, value: '@INTENTIONAL' }
|
- { key: bugprone-empty-catch.IgnoreCatchWithKeywords, value: '@INTENTIONAL' }
|
||||||
|
|
||||||
# Variables locales en snake_case
|
# =====================================================================
|
||||||
|
# CONSTANTES → UPPER_CASE (compile-time y runtime, en cualquier scope)
|
||||||
|
# =====================================================================
|
||||||
|
# Todo lo que sea const o constexpr se identifica visualmente en UPPER_CASE,
|
||||||
|
# sin importar si es global, local, miembro o static.
|
||||||
|
|
||||||
|
# constexpr en cualquier scope (globales y locales)
|
||||||
|
- { key: readability-identifier-naming.ConstexprVariableCase, value: UPPER_CASE }
|
||||||
|
|
||||||
|
# Constantes globales (const no-constexpr)
|
||||||
|
- { key: readability-identifier-naming.GlobalConstantCase, value: UPPER_CASE }
|
||||||
|
|
||||||
|
# Constantes locales (const en función)
|
||||||
|
- { key: readability-identifier-naming.LocalConstantCase, value: UPPER_CASE }
|
||||||
|
|
||||||
|
# Static const a nivel de archivo/namespace
|
||||||
|
- { key: readability-identifier-naming.StaticConstantCase, value: UPPER_CASE }
|
||||||
|
|
||||||
|
# Miembros static const/constexpr de clase (p.ej. static constexpr int MAX = 100;)
|
||||||
|
- { key: readability-identifier-naming.ClassConstantCase, value: UPPER_CASE }
|
||||||
|
|
||||||
|
# Miembros const no-static de clase (p.ej. const int limit;)
|
||||||
|
- { key: readability-identifier-naming.ConstantMemberCase, value: UPPER_CASE }
|
||||||
|
|
||||||
|
# Valores de enums
|
||||||
|
- { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE }
|
||||||
|
|
||||||
|
# NOTA: Los parámetros const NO se tratan como constantes aquí.
|
||||||
|
# Un parámetro sigue siendo un parámetro aunque sea const → hereda ParameterCase.
|
||||||
|
|
||||||
|
# =====================================================================
|
||||||
|
# VARIABLES NO-CONST
|
||||||
|
# =====================================================================
|
||||||
|
|
||||||
|
# Variables locales
|
||||||
- { key: readability-identifier-naming.VariableCase, value: lower_case }
|
- { key: readability-identifier-naming.VariableCase, value: lower_case }
|
||||||
|
- { key: readability-identifier-naming.LocalVariableCase, value: lower_case }
|
||||||
|
|
||||||
# Miembros privados en snake_case con sufijo _
|
# Parámetros de función
|
||||||
- { key: readability-identifier-naming.PrivateMemberCase, value: lower_case }
|
- { key: readability-identifier-naming.ParameterCase, value: lower_case }
|
||||||
- { key: readability-identifier-naming.PrivateMemberSuffix, value: _ }
|
|
||||||
|
|
||||||
# Miembros protegidos en snake_case con sufijo _
|
# Variables estáticas no-const (static locales, static file-scope,
|
||||||
- { key: readability-identifier-naming.ProtectedMemberCase, value: lower_case }
|
# y static members no-const de clase como el instance_ de un Singleton).
|
||||||
- { key: readability-identifier-naming.ProtectedMemberSuffix, value: _ }
|
# Sufijo _ para marcar que tienen storage estático.
|
||||||
|
|
||||||
# Miembros públicos en snake_case (sin sufijo)
|
|
||||||
- { key: readability-identifier-naming.PublicMemberCase, value: lower_case }
|
|
||||||
|
|
||||||
# Namespaces en CamelCase
|
|
||||||
- { key: readability-identifier-naming.NamespaceCase, value: CamelCase }
|
|
||||||
|
|
||||||
# Variables estáticas privadas como miembros privados
|
|
||||||
- { key: readability-identifier-naming.StaticVariableCase, value: lower_case }
|
- { key: readability-identifier-naming.StaticVariableCase, value: lower_case }
|
||||||
- { key: readability-identifier-naming.StaticVariableSuffix, value: _ }
|
- { key: readability-identifier-naming.StaticVariableSuffix, value: _ }
|
||||||
|
|
||||||
# Constantes estáticas sin sufijo
|
# =====================================================================
|
||||||
- { key: readability-identifier-naming.StaticConstantCase, value: UPPER_CASE }
|
# MIEMBROS DE CLASE NO-CONST
|
||||||
|
# =====================================================================
|
||||||
|
# Privados: snake_case con sufijo _
|
||||||
|
- { key: readability-identifier-naming.PrivateMemberCase, value: lower_case }
|
||||||
|
- { key: readability-identifier-naming.PrivateMemberSuffix, value: _ }
|
||||||
|
|
||||||
# Constantes globales en UPPER_CASE
|
# Protegidos: snake_case con sufijo _
|
||||||
- { key: readability-identifier-naming.GlobalConstantCase, value: UPPER_CASE }
|
- { key: readability-identifier-naming.ProtectedMemberCase, value: lower_case }
|
||||||
|
- { key: readability-identifier-naming.ProtectedMemberSuffix, value: _ }
|
||||||
|
|
||||||
# Variables constexpr globales en UPPER_CASE
|
# Públicos: snake_case sin sufijo
|
||||||
- { key: readability-identifier-naming.ConstexprVariableCase, value: UPPER_CASE }
|
- { key: readability-identifier-naming.PublicMemberCase, value: lower_case }
|
||||||
|
|
||||||
# Constantes locales en UPPER_CASE
|
# =====================================================================
|
||||||
- { key: readability-identifier-naming.LocalConstantCase, value: UPPER_CASE }
|
# TIPOS
|
||||||
|
# =====================================================================
|
||||||
# Constexpr miembros en UPPER_CASE (sin sufijo)
|
|
||||||
- { key: readability-identifier-naming.ConstexprMemberCase, value: UPPER_CASE }
|
|
||||||
|
|
||||||
# Constexpr miembros privados/protegidos con sufijo _
|
|
||||||
- { key: readability-identifier-naming.ConstexprMethodCase, value: UPPER_CASE }
|
|
||||||
|
|
||||||
# Clases, structs y enums en CamelCase
|
|
||||||
- { key: readability-identifier-naming.ClassCase, value: CamelCase }
|
- { key: readability-identifier-naming.ClassCase, value: CamelCase }
|
||||||
- { key: readability-identifier-naming.StructCase, value: CamelCase }
|
- { key: readability-identifier-naming.StructCase, value: CamelCase }
|
||||||
- { key: readability-identifier-naming.EnumCase, value: CamelCase }
|
- { key: readability-identifier-naming.EnumCase, value: CamelCase }
|
||||||
|
- { key: readability-identifier-naming.UnionCase, value: CamelCase }
|
||||||
|
- { key: readability-identifier-naming.TypeAliasCase, value: CamelCase }
|
||||||
|
- { key: readability-identifier-naming.TypedefCase, value: CamelCase }
|
||||||
|
- { key: readability-identifier-naming.TemplateParameterCase, value: CamelCase }
|
||||||
|
|
||||||
# Valores de enums en UPPER_CASE
|
# Namespaces
|
||||||
- { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE }
|
- { key: readability-identifier-naming.NamespaceCase, value: CamelCase }
|
||||||
|
|
||||||
# Métodos en camelBack (sin sufijos)
|
# =====================================================================
|
||||||
|
# FUNCIONES Y MÉTODOS (incluyendo constexpr)
|
||||||
|
# =====================================================================
|
||||||
|
# Un método/función constexpr es un invocable, no una constante → camelBack.
|
||||||
|
- { key: readability-identifier-naming.FunctionCase, value: camelBack }
|
||||||
|
- { key: readability-identifier-naming.ConstexprFunctionCase, value: camelBack }
|
||||||
- { key: readability-identifier-naming.MethodCase, value: camelBack }
|
- { key: readability-identifier-naming.MethodCase, value: camelBack }
|
||||||
- { key: readability-identifier-naming.PrivateMethodCase, value: camelBack }
|
- { key: readability-identifier-naming.PrivateMethodCase, value: camelBack }
|
||||||
- { key: readability-identifier-naming.ProtectedMethodCase, value: camelBack }
|
- { key: readability-identifier-naming.ProtectedMethodCase, value: camelBack }
|
||||||
- { key: readability-identifier-naming.PublicMethodCase, value: camelBack }
|
- { key: readability-identifier-naming.PublicMethodCase, value: camelBack }
|
||||||
|
- { key: readability-identifier-naming.ConstexprMethodCase, value: camelBack }
|
||||||
# Funciones en camelBack
|
|
||||||
- { key: readability-identifier-naming.FunctionCase, value: camelBack }
|
|
||||||
|
|
||||||
# Parámetros en lower_case
|
|
||||||
- { key: readability-identifier-naming.ParameterCase, value: lower_case }
|
|
||||||
|
|||||||
Executable
+92
@@ -0,0 +1,92 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Pre-commit hook: aplica clang-format als fitxers C++ staged abans del commit.
|
||||||
|
# - Només toca fitxers staged dins source/ (exclou source/external/).
|
||||||
|
# - Avorta el commit si hi ha canvis NO staged en aquests fitxers (per no incloure'ls sense voler).
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if ! command -v clang-format >/dev/null 2>&1; then
|
||||||
|
echo "pre-commit: clang-format no trobat — saltant format check" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
mapfile -t STAGED < <(git diff --cached --name-only --diff-filter=ACMR \
|
||||||
|
| grep -E '^source/.*\.(cpp|hpp|h)$' \
|
||||||
|
| grep -vE '^source/external/' || true)
|
||||||
|
|
||||||
|
if [ ${#STAGED[@]} -eq 0 ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
UNSTAGED_DIRTY=()
|
||||||
|
for f in "${STAGED[@]}"; do
|
||||||
|
if ! git diff --quiet -- "$f"; then
|
||||||
|
UNSTAGED_DIRTY+=("$f")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ${#UNSTAGED_DIRTY[@]} -gt 0 ]; then
|
||||||
|
echo "pre-commit: aquests fitxers tenen canvis NO staged i estan al commit." >&2
|
||||||
|
echo " Fes 'git add' o 'git stash' abans de continuar:" >&2
|
||||||
|
printf ' %s\n' "${UNSTAGED_DIRTY[@]}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
clang-format -i "${STAGED[@]}"
|
||||||
|
git add -- "${STAGED[@]}"
|
||||||
|
|
||||||
|
# --- clang-tidy només sobre els fitxers staged ---
|
||||||
|
if ! command -v clang-tidy >/dev/null 2>&1; then
|
||||||
|
echo "pre-commit: clang-tidy no trobat — saltant tidy" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
||||||
|
BUILD_DIR="$REPO_ROOT/build"
|
||||||
|
|
||||||
|
if [ ! -f "$BUILD_DIR/compile_commands.json" ]; then
|
||||||
|
echo "pre-commit: generant compile_commands.json (build dir buit)..." >&2
|
||||||
|
cmake -S "$REPO_ROOT" -B "$BUILD_DIR" >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "pre-commit: clang-tidy sobre ${#STAGED[@]} fitxer(s)..." >&2
|
||||||
|
if ! clang-tidy -p "$BUILD_DIR" --quiet "${STAGED[@]}"; then
|
||||||
|
echo "pre-commit: clang-tidy ha trobat errors — commit avortat" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- cppcheck només sobre els .cpp staged ---
|
||||||
|
if ! command -v cppcheck >/dev/null 2>&1; then
|
||||||
|
echo "pre-commit: cppcheck no trobat — saltant cppcheck" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
CPP_STAGED=()
|
||||||
|
for f in "${STAGED[@]}"; do
|
||||||
|
[[ "$f" == *.cpp ]] && CPP_STAGED+=("$f")
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ${#CPP_STAGED[@]} -eq 0 ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "pre-commit: cppcheck sobre ${#CPP_STAGED[@]} fitxer(s)..." >&2
|
||||||
|
if ! cppcheck \
|
||||||
|
--enable=warning,style,performance,portability \
|
||||||
|
--std=c++20 \
|
||||||
|
--language=c++ \
|
||||||
|
--inline-suppr \
|
||||||
|
--suppress=missingIncludeSystem \
|
||||||
|
--suppress=toomanyconfigs \
|
||||||
|
--suppress='*:*source/external/*' \
|
||||||
|
--suppress='*:*source/core/rendering/sdl3gpu/spv/*' \
|
||||||
|
--suppress=normalCheckLevelMaxBranches \
|
||||||
|
-D_DEBUG \
|
||||||
|
-DLINUX_BUILD \
|
||||||
|
--quiet \
|
||||||
|
--error-exitcode=1 \
|
||||||
|
-I "$REPO_ROOT/source" \
|
||||||
|
"${CPP_STAGED[@]}"; then
|
||||||
|
echo "pre-commit: cppcheck ha trobat errors — commit avortat" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
Vendored
+51
@@ -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
|
||||||
|
}
|
||||||
+92
-51
@@ -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)
|
||||||
@@ -152,56 +158,45 @@ endif()
|
|||||||
if(NOT APPLE AND NOT EMSCRIPTEN)
|
if(NOT APPLE AND NOT EMSCRIPTEN)
|
||||||
find_program(GLSLC_EXE NAMES glslc)
|
find_program(GLSLC_EXE NAMES glslc)
|
||||||
|
|
||||||
set(SHADER_VERT_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.vert")
|
set(SHADERS_DIR "${CMAKE_SOURCE_DIR}/data/shaders")
|
||||||
set(SHADER_FRAG_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.frag")
|
set(HEADERS_DIR "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv")
|
||||||
set(SHADER_CRTPI_SRC "${CMAKE_SOURCE_DIR}/data/shaders/crtpi_frag.glsl")
|
|
||||||
set(SHADER_UPSCALE_SRC "${CMAKE_SOURCE_DIR}/data/shaders/upscale.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(ALL_SHADER_HEADERS
|
||||||
set(SHADER_FRAG_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/postfx_frag_spv.h")
|
"${HEADERS_DIR}/postfx_vert_spv.h"
|
||||||
set(SHADER_CRTPI_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/crtpi_frag_spv.h")
|
"${HEADERS_DIR}/postfx_frag_spv.h"
|
||||||
set(SHADER_UPSCALE_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/upscale_frag_spv.h")
|
"${HEADERS_DIR}/upscale_frag_spv.h"
|
||||||
set(SHADER_DOWNSCALE_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/downscale_frag_spv.h")
|
"${HEADERS_DIR}/downscale_frag_spv.h"
|
||||||
|
"${HEADERS_DIR}/crtpi_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_HEADERS "${SHADER_VERT_H}" "${SHADER_FRAG_H}" "${SHADER_CRTPI_H}" "${SHADER_UPSCALE_H}" "${SHADER_DOWNSCALE_H}")
|
set(ALL_SHADER_SOURCES
|
||||||
|
"${SHADERS_DIR}/postfx.vert"
|
||||||
|
"${SHADERS_DIR}/postfx.frag"
|
||||||
|
"${SHADERS_DIR}/upscale.frag"
|
||||||
|
"${SHADERS_DIR}/downscale.frag"
|
||||||
|
"${SHADERS_DIR}/crtpi_frag.glsl"
|
||||||
|
)
|
||||||
|
|
||||||
if(GLSLC_EXE)
|
if(GLSLC_EXE)
|
||||||
set(COMPILE_SHADER_SCRIPT "${CMAKE_SOURCE_DIR}/tools/shaders/compile_shader.cmake")
|
add_custom_command(
|
||||||
|
OUTPUT ${ALL_SHADER_HEADERS}
|
||||||
macro(add_shader SRC_FILE OUT_H VAR_NAME)
|
COMMAND ${CMAKE_COMMAND}
|
||||||
cmake_parse_arguments(S "" "STAGE" "" ${ARGN})
|
-D GLSLC=${GLSLC_EXE}
|
||||||
add_custom_command(
|
-D SHADERS_DIR=${SHADERS_DIR}
|
||||||
OUTPUT "${OUT_H}"
|
-D HEADERS_DIR=${HEADERS_DIR}
|
||||||
COMMAND ${CMAKE_COMMAND}
|
-P ${CMAKE_SOURCE_DIR}/tools/shaders/compile_spirv.cmake
|
||||||
"-DGLSLC=${GLSLC_EXE}"
|
DEPENDS ${ALL_SHADER_SOURCES}
|
||||||
"-DSRC=${SRC_FILE}"
|
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||||
"-DOUT_H=${OUT_H}"
|
COMMENT "Compilando shaders SPIR-V..."
|
||||||
"-DVAR=${VAR_NAME}"
|
)
|
||||||
"-DSTAGE=${S_STAGE}"
|
|
||||||
-P "${COMPILE_SHADER_SCRIPT}"
|
|
||||||
DEPENDS "${SRC_FILE}" "${COMPILE_SHADER_SCRIPT}"
|
|
||||||
COMMENT "Compilando shader: ${VAR_NAME}"
|
|
||||||
)
|
|
||||||
endmacro()
|
|
||||||
|
|
||||||
add_shader("${SHADER_VERT_SRC}" "${SHADER_VERT_H}" "postfx_vert_spv")
|
|
||||||
add_shader("${SHADER_FRAG_SRC}" "${SHADER_FRAG_H}" "postfx_frag_spv")
|
|
||||||
add_shader("${SHADER_CRTPI_SRC}" "${SHADER_CRTPI_H}" "crtpi_frag_spv" STAGE fragment)
|
|
||||||
add_shader("${SHADER_UPSCALE_SRC}" "${SHADER_UPSCALE_H}" "upscale_frag_spv")
|
|
||||||
add_shader("${SHADER_DOWNSCALE_SRC}" "${SHADER_DOWNSCALE_H}" "downscale_frag_spv")
|
|
||||||
|
|
||||||
add_custom_target(shaders DEPENDS ${ALL_SHADER_HEADERS})
|
add_custom_target(shaders DEPENDS ${ALL_SHADER_HEADERS})
|
||||||
message(STATUS "glslc encontrado: shaders se compilarán automáticamente")
|
message(STATUS "glslc encontrado: shaders se compilarán automáticamente")
|
||||||
else()
|
else()
|
||||||
foreach(_h IN LISTS ALL_SHADER_HEADERS)
|
foreach(HDR ${ALL_SHADER_HEADERS})
|
||||||
if(NOT EXISTS "${_h}")
|
if(NOT EXISTS "${HDR}")
|
||||||
message(FATAL_ERROR
|
message(FATAL_ERROR
|
||||||
"glslc no encontrado y header SPIR-V no existe: ${_h}\n"
|
"glslc no encontrado y header SPIR-V no existe: ${HDR}\n"
|
||||||
" Instala glslc: sudo apt install glslang-tools (Linux)\n"
|
" Instala glslc: sudo apt install glslang-tools (Linux)\n"
|
||||||
" choco install vulkan-sdk (Windows)\n"
|
" choco install vulkan-sdk (Windows)"
|
||||||
" O genera los headers manualmente: tools/shaders/compile_spirv.sh"
|
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
endforeach()
|
endforeach()
|
||||||
@@ -289,13 +284,6 @@ elseif(UNIX AND NOT APPLE)
|
|||||||
target_compile_definitions(${PROJECT_NAME} PRIVATE LINUX_BUILD)
|
target_compile_definitions(${PROJECT_NAME} PRIVATE LINUX_BUILD)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Especificar la ubicación del ejecutable
|
|
||||||
if(EMSCRIPTEN)
|
|
||||||
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
|
||||||
else()
|
|
||||||
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# --- 5. STATIC ANALYSIS TARGETS ---
|
# --- 5. STATIC ANALYSIS TARGETS ---
|
||||||
|
|
||||||
find_program(CLANG_TIDY_EXE NAMES clang-tidy)
|
find_program(CLANG_TIDY_EXE NAMES clang-tidy)
|
||||||
@@ -316,7 +304,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)
|
||||||
@@ -385,6 +372,8 @@ if(CPPCHECK_EXE)
|
|||||||
--inline-suppr
|
--inline-suppr
|
||||||
--suppress=missingIncludeSystem
|
--suppress=missingIncludeSystem
|
||||||
--suppress=toomanyconfigs
|
--suppress=toomanyconfigs
|
||||||
|
--suppress=*:*/source/external/*
|
||||||
|
--suppress=*:*/source/core/rendering/sdl3gpu/spv/*
|
||||||
-D_DEBUG
|
-D_DEBUG
|
||||||
-DLINUX_BUILD
|
-DLINUX_BUILD
|
||||||
--quiet
|
--quiet
|
||||||
@@ -396,3 +385,55 @@ 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_BINARY_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)
|
||||||
|
|
||||||
|
# --- CÒPIA DE gamecontrollerdb.txt AL COSTAT DEL BINARI ---
|
||||||
|
# SDL_AddGamepadMappingsFromFile només llegeix del filesystem real (no del
|
||||||
|
# pack), així que el fitxer ha de viure al directori del binari. Es copia
|
||||||
|
# només si existeix per no fallar la build d'algú que encara no ha fet
|
||||||
|
# `make controllerdb`.
|
||||||
|
if(EXISTS "${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt")
|
||||||
|
set(CONTROLLER_DB "${CMAKE_BINARY_DIR}/gamecontrollerdb.txt")
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${CONTROLLER_DB}
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt"
|
||||||
|
"${CONTROLLER_DB}"
|
||||||
|
DEPENDS "${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt"
|
||||||
|
COMMENT "Copiant gamecontrollerdb.txt → build/"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
add_custom_target(controller_db ALL DEPENDS ${CONTROLLER_DB})
|
||||||
|
add_dependencies(${PROJECT_NAME} controller_db)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|||||||
@@ -2,17 +2,13 @@
|
|||||||
# DIRECTORIES
|
# DIRECTORIES
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
DIR_ROOT := $(dir $(abspath $(MAKEFILE_LIST)))
|
DIR_ROOT := $(dir $(abspath $(MAKEFILE_LIST)))
|
||||||
DIR_TOOLS := $(addsuffix /, $(DIR_ROOT)tools)
|
BUILDDIR := build
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# TARGET NAMES
|
# TARGET NAMES
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
TARGET_NAME := coffee_crisis_arcade_edition
|
TARGET_NAME := coffee_crisis_arcade_edition
|
||||||
ifeq ($(OS),Windows_NT)
|
TARGET_FILE := $(BUILDDIR)/$(TARGET_NAME)
|
||||||
TARGET_FILE := $(DIR_ROOT)$(TARGET_NAME).exe
|
|
||||||
else
|
|
||||||
TARGET_FILE := $(DIR_ROOT)$(TARGET_NAME)
|
|
||||||
endif
|
|
||||||
APP_NAME := Coffee Crisis Arcade Edition
|
APP_NAME := Coffee Crisis Arcade Edition
|
||||||
DIST_DIR := dist
|
DIST_DIR := dist
|
||||||
RELEASE_FOLDER := dist/_tmp
|
RELEASE_FOLDER := dist/_tmp
|
||||||
@@ -22,8 +18,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)
|
||||||
@@ -56,11 +58,15 @@ endif
|
|||||||
# WINDOWS-SPECIFIC VARIABLES
|
# WINDOWS-SPECIFIC VARIABLES
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
ifeq ($(OS),Windows_NT)
|
ifeq ($(OS),Windows_NT)
|
||||||
WIN_TARGET_FILE := $(DIR_ROOT)$(APP_NAME)
|
WIN_TARGET_FILE := $(BUILDDIR)/$(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 +76,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,57 +92,98 @@ else
|
|||||||
UNAME_S := $(shell uname -s)
|
UNAME_S := $(shell uname -s)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# CMAKE GENERATOR (usa Ninja si está disponible; si no, MinGW Makefiles en
|
||||||
|
# Windows / generador por defecto en Linux/macOS). Ninja paraleliza mejor.
|
||||||
|
# ==============================================================================
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
# Dins MSYS2/Git Bash/MinGW, $(shell ...) usa sh.exe i "NUL" NO és
|
||||||
|
# dispositiu — un redirect "2>NUL" crearia un fitxer literal anomenat
|
||||||
|
# NUL al cwd. Detectem MSYSTEM per usar /dev/null en aquests entorns.
|
||||||
|
ifneq ($(MSYSTEM),)
|
||||||
|
NULDEV := /dev/null
|
||||||
|
else
|
||||||
|
NULDEV := NUL
|
||||||
|
endif
|
||||||
|
HAS_NINJA := $(shell ninja --version 2>$(NULDEV))
|
||||||
|
ifneq ($(HAS_NINJA),)
|
||||||
|
CMAKE_GEN := -G "Ninja"
|
||||||
|
else
|
||||||
|
CMAKE_GEN := -G "MinGW Makefiles"
|
||||||
|
endif
|
||||||
|
else
|
||||||
|
HAS_NINJA := $(shell ninja --version 2>/dev/null)
|
||||||
|
ifneq ($(HAS_NINJA),)
|
||||||
|
CMAKE_GEN := -G "Ninja"
|
||||||
|
else
|
||||||
|
CMAKE_GEN :=
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# COMPILACIÓN CON CMAKE
|
# COMPILACIÓN CON CMAKE
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
all:
|
all:
|
||||||
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
|
||||||
@cmake --build build
|
@cmake --build build
|
||||||
|
|
||||||
debug:
|
debug:
|
||||||
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
|
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Debug -DGIT_HASH=$(GIT_HASH)
|
||||||
@cmake --build build
|
@cmake --build build
|
||||||
|
|
||||||
|
run: all
|
||||||
|
@./$(TARGET_FILE)
|
||||||
|
|
||||||
|
run-debug: debug
|
||||||
|
@./$(TARGET_FILE)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@rm -rf $(BUILDDIR)
|
||||||
|
|
||||||
|
rebuild: clean all
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# RELEASE AUTOMÁTICO (detecta SO)
|
# RELEASE AUTOMÁTICO (detecta SO)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
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 build/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 -DGIT_HASH=$(GIT_HASH)
|
||||||
@cmake --build build
|
@cmake --build build
|
||||||
|
|
||||||
# Crea carpeta de distribución y carpeta temporal 'RELEASE_FOLDER'
|
# Crea carpeta de distribución y carpeta temporal 'RELEASE_FOLDER'
|
||||||
@@ -147,13 +193,13 @@ windows_release:
|
|||||||
|
|
||||||
# Copia la carpeta 'config' y el archivo 'resources.pack'
|
# Copia la carpeta 'config' y el archivo 'resources.pack'
|
||||||
@powershell -Command "Copy-Item -Path 'config' -Destination '$(RELEASE_FOLDER)' -recurse -Force"
|
@powershell -Command "Copy-Item -Path 'config' -Destination '$(RELEASE_FOLDER)' -recurse -Force"
|
||||||
@powershell -Command "Copy-Item -Path 'resources.pack' -Destination '$(RELEASE_FOLDER)'"
|
@powershell -Command "Copy-Item -Path 'build/resources.pack' -Destination '$(RELEASE_FOLDER)'"
|
||||||
|
|
||||||
# Copia los ficheros que estan en la raíz del proyecto
|
# Copia los ficheros que estan en la raíz del proyecto
|
||||||
@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).exe' -Destination '$(WIN_RELEASE_FILE_PS).exe'"
|
||||||
strip -s -R .comment -R .gnu.version "$(WIN_RELEASE_FILE).exe" --strip-unneeded
|
strip -s -R .comment -R .gnu.version "$(WIN_RELEASE_FILE).exe" --strip-unneeded
|
||||||
|
|
||||||
# Crea el fichero .zip
|
# Crea el fichero .zip
|
||||||
@@ -167,16 +213,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)"
|
||||||
@@ -193,38 +255,64 @@ macos_release:
|
|||||||
|
|
||||||
# Copia carpetas y ficheros
|
# Copia carpetas y ficheros
|
||||||
cp -R config "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
cp -R config "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
||||||
cp resources.pack "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
cp build/resources.pack "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
||||||
cp -R release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64/SDL3.framework "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks"
|
cp -R release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64/SDL3.framework "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks"
|
||||||
cp release/icons/*.icns "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
cp release/icons/*.icns "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
||||||
cp release/macos/Info.plist "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents"
|
cp release/macos/Info.plist "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents"
|
||||||
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 \
|
-DGIT_HASH=$(GIT_HASH) \
|
||||||
--icon "$(APP_NAME).app" 278 102 \
|
&& cmake --build build/intel; then \
|
||||||
--icon "LICENSE" 441 102 \
|
cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)"; \
|
||||||
--icon "README.md" 604 102 \
|
codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app"; \
|
||||||
--app-drop-link 115 102 \
|
echo "Creando DMG Intel con iconos de 96x96..."; \
|
||||||
--hide-extension "$(APP_NAME).app" \
|
create-dmg \
|
||||||
"$(MACOS_INTEL_RELEASE)" \
|
--volname "$(APP_NAME)" \
|
||||||
"$(RELEASE_FOLDER)" || true
|
--window-pos 200 120 \
|
||||||
@echo "Release Intel creado: $(MACOS_INTEL_RELEASE)"
|
--window-size 720 300 \
|
||||||
|
--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
|
||||||
@cmake -S . -B build/arm -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 -DMACOS_BUNDLE=ON
|
@echo ""
|
||||||
|
@echo "============================================"
|
||||||
|
@echo " Compilando version Apple Silicon (arm64)"
|
||||||
|
@echo "============================================"
|
||||||
|
@cmake -S . -B build/arm -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 -DMACOS_BUNDLE=ON -DGIT_HASH=$(GIT_HASH)
|
||||||
@cmake --build build/arm
|
@cmake --build build/arm
|
||||||
cp "$(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 +345,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 -DGIT_HASH=$(GIT_HASH)
|
||||||
@cmake --build build
|
@cmake --build build
|
||||||
|
|
||||||
# Elimina carpeta temporal previa y la recrea (crea dist/ si no existe)
|
# Elimina carpeta temporal previa y la recrea (crea dist/ si no existe)
|
||||||
@@ -271,7 +359,7 @@ linux_release:
|
|||||||
|
|
||||||
# Copia ficheros
|
# Copia ficheros
|
||||||
cp -R config "$(RELEASE_FOLDER)"
|
cp -R config "$(RELEASE_FOLDER)"
|
||||||
cp resources.pack "$(RELEASE_FOLDER)"
|
cp build/resources.pack "$(RELEASE_FOLDER)"
|
||||||
cp LICENSE "$(RELEASE_FOLDER)"
|
cp LICENSE "$(RELEASE_FOLDER)"
|
||||||
cp README.md "$(RELEASE_FOLDER)"
|
cp README.md "$(RELEASE_FOLDER)"
|
||||||
cp "$(TARGET_FILE)" "$(RELEASE_FILE)"
|
cp "$(TARGET_FILE)" "$(RELEASE_FILE)"
|
||||||
@@ -285,145 +373,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,30 +390,70 @@ 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 -DGIT_HASH=$(GIT_HASH)
|
||||||
@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 -DGIT_HASH=$(GIT_HASH)
|
||||||
@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 -DGIT_HASH=$(GIT_HASH)
|
||||||
@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 -DGIT_HASH=$(GIT_HASH)
|
||||||
@cmake --build build --target tidy-fix
|
@cmake --build build --target tidy-fix
|
||||||
|
|
||||||
|
cppcheck:
|
||||||
|
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
|
||||||
|
@cmake --build build --target cppcheck
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# GIT HOOKS
|
||||||
|
# ==============================================================================
|
||||||
|
hooks-install:
|
||||||
|
@git config core.hooksPath .githooks
|
||||||
|
@echo "Git hooks activats: $(shell pwd)/.githooks"
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# 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
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
show_version:
|
show-version:
|
||||||
@echo "Version actual: $(VERSION)"
|
@echo "Version actual: $(VERSION)"
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@@ -468,28 +464,31 @@ help:
|
|||||||
@echo " make - Compilar con cmake (Release)"
|
@echo " make - Compilar con cmake (Release)"
|
||||||
@echo " make debug - Compilar con cmake (Debug)"
|
@echo " make debug - Compilar con cmake (Debug)"
|
||||||
@echo ""
|
@echo ""
|
||||||
|
@echo " Ejecucion:"
|
||||||
|
@echo " make run - Compilar (Release) y ejecutar"
|
||||||
|
@echo " make run-debug - Compilar (Debug) y ejecutar"
|
||||||
|
@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 $(BUILDDIR)/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 clean - Borrar carpeta $(BUILDDIR)/"
|
||||||
|
@echo " make rebuild - clean + all"
|
||||||
|
@echo " make show-version - Mostrar version actual ($(VERSION))"
|
||||||
|
@echo " make hooks-install - Activar git hooks del proyecto"
|
||||||
@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 run run-debug clean rebuild release _windows-release _macos-release _linux-release wasm wasm-debug pack compile-shaders format format-check tidy tidy-fix cppcheck controllerdb hooks-install 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",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+90
-39
@@ -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,10 @@
|
|||||||
// 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"
|
// NOLINTNEXTLINE(bugprone-suspicious-include) — stb_vorbis és single-file: el TU principal inclou el .c per portar la implementació.
|
||||||
// stb_vorbis.h filtra les macros L, C i R (i PLAYBACK_*) al TU. Les netegem
|
#include "external/stb_vorbis.c"
|
||||||
// perquè xocarien amb noms de paràmetres de plantilla en json.hpp i altres.
|
// stb_vorbis.c filtra les macros L, C i R (i PLAYBACK_*) al TU. Les netegem
|
||||||
|
// perquè xocarien amb noms de paràmetres de plantilla en altres headers.
|
||||||
#undef L
|
#undef L
|
||||||
#undef C
|
#undef C
|
||||||
#undef R
|
#undef R
|
||||||
@@ -19,9 +20,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 +31,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; }
|
||||||
@@ -40,29 +44,69 @@ Audio::Audio() { initSDLAudio(); }
|
|||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
Audio::~Audio() {
|
Audio::~Audio() {
|
||||||
JA_Quit();
|
Ja::quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 != nullptr && instance->music_.state == MusicState::PLAYING && Ja::getMusicState() != Ja::MusicState::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* music, const int loop, const int crossfade_ms) {
|
||||||
|
if (!music_enabled_ || music == nullptr) { return; }
|
||||||
|
|
||||||
|
if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) {
|
||||||
|
Ja::crossfadeMusic(music, crossfade_ms, loop);
|
||||||
|
} else {
|
||||||
|
if (music_.state == MusicState::PLAYING) {
|
||||||
|
Ja::stopMusic();
|
||||||
|
}
|
||||||
|
Ja::playMusic(music, loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
music_.name.clear(); // nom desconegut quan es passa per punter
|
||||||
|
music_.loop = (loop != 0);
|
||||||
|
music_.state = MusicState::PLAYING;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pausa la música
|
// Pausa la música
|
||||||
void Audio::pauseMusic() {
|
void Audio::pauseMusic() {
|
||||||
if (music_enabled_ && music_.state == MusicState::PLAYING) {
|
if (music_enabled_ && music_.state == MusicState::PLAYING) {
|
||||||
JA_PauseMusic();
|
Ja::pauseMusic();
|
||||||
music_.state = MusicState::PAUSED;
|
music_.state = MusicState::PAUSED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,7 +114,7 @@ void Audio::pauseMusic() {
|
|||||||
// Continua la música pausada
|
// Continua la música pausada
|
||||||
void Audio::resumeMusic() {
|
void Audio::resumeMusic() {
|
||||||
if (music_enabled_ && music_.state == MusicState::PAUSED) {
|
if (music_enabled_ && music_.state == MusicState::PAUSED) {
|
||||||
JA_ResumeMusic();
|
Ja::resumeMusic();
|
||||||
music_.state = MusicState::PLAYING;
|
music_.state = MusicState::PLAYING;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,63 +122,70 @@ void Audio::resumeMusic() {
|
|||||||
// Detiene la música
|
// Detiene la música
|
||||||
void Audio::stopMusic() {
|
void Audio::stopMusic() {
|
||||||
if (music_enabled_) {
|
if (music_enabled_) {
|
||||||
JA_StopMusic();
|
Ja::stopMusic();
|
||||||
music_.state = MusicState::STOPPED;
|
music_.state = MusicState::STOPPED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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* sound, Group group) const {
|
||||||
|
if (sound_enabled_ && sound != nullptr) {
|
||||||
|
Ja::playSound(sound, 0, static_cast<int>(group));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detiene todos los sonidos
|
// Detiene todos los sonidos
|
||||||
void Audio::stopAllSounds() const {
|
void Audio::stopAllSounds() const {
|
||||||
if (sound_enabled_) {
|
if (sound_enabled_) {
|
||||||
JA_StopChannel(-1);
|
Ja::stopChannel(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Realiza un fundido de salida de la música
|
// Realiza un fundido de salida de la música
|
||||||
void Audio::fadeOutMusic(int milliseconds) const {
|
void Audio::fadeOutMusic(int milliseconds) const {
|
||||||
if (music_enabled_ && getRealMusicState() == MusicState::PLAYING) {
|
if (music_enabled_ && getRealMusicState() == MusicState::PLAYING) {
|
||||||
JA_FadeOutMusic(milliseconds);
|
Ja::fadeOutMusic(milliseconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consulta directamente el estado real de la música en jailaudio
|
// Consulta directamente el estado real de la música en jailaudio
|
||||||
auto Audio::getRealMusicState() -> MusicState {
|
auto Audio::getRealMusicState() -> MusicState {
|
||||||
JA_Music_state ja_state = JA_GetMusicState();
|
Ja::MusicState ja_state = Ja::getMusicState();
|
||||||
switch (ja_state) {
|
switch (ja_state) {
|
||||||
case JA_MUSIC_PLAYING:
|
case Ja::MusicState::PLAYING:
|
||||||
return MusicState::PLAYING;
|
return MusicState::PLAYING;
|
||||||
case JA_MUSIC_PAUSED:
|
case Ja::MusicState::PAUSED:
|
||||||
return MusicState::PAUSED;
|
return MusicState::PAUSED;
|
||||||
case JA_MUSIC_STOPPED:
|
case Ja::MusicState::STOPPED:
|
||||||
case JA_MUSIC_INVALID:
|
case Ja::MusicState::INVALID:
|
||||||
case JA_MUSIC_DISABLED:
|
case Ja::MusicState::DISABLED:
|
||||||
default:
|
default:
|
||||||
return MusicState::STOPPED;
|
return MusicState::STOPPED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +207,7 @@ void Audio::initSDLAudio() {
|
|||||||
if (!SDL_Init(SDL_INIT_AUDIO)) {
|
if (!SDL_Init(SDL_INIT_AUDIO)) {
|
||||||
std::cout << "SDL_AUDIO could not initialize! SDL Error: " << SDL_GetError() << '\n';
|
std::cout << "SDL_AUDIO could not initialize! SDL Error: " << SDL_GetError() << '\n';
|
||||||
} else {
|
} else {
|
||||||
JA_Init(FREQUENCY, SDL_AUDIO_S16LE, 2);
|
Ja::init(FREQUENCY, SDL_AUDIO_S16LE, 2);
|
||||||
enable(Options::audio.enabled);
|
enable(Options::audio.enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+61
-53
@@ -1,111 +1,119 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cmath> // Para std::lround
|
||||||
|
#include <cstdint> // Para int8_t, uint8_t
|
||||||
#include <string> // Para string
|
#include <string> // Para string
|
||||||
#include <utility> // Para move
|
|
||||||
|
namespace Ja {
|
||||||
|
struct Music;
|
||||||
|
struct Sound;
|
||||||
|
} // namespace Ja
|
||||||
|
|
||||||
// --- 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(Ja::Music* 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 playSound(Ja::Sound* sound, Group group = Group::GAME) const; // Reproducir sonido puntual por puntero
|
||||||
void stopAllSounds() const; // Detener todos los sonidos
|
void stopAllSounds() const; // Detener todos los sonidos
|
||||||
|
|
||||||
// --- 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 auto toPercent(float volume) -> int {
|
||||||
|
return static_cast<int>(std::lround(volume * 100.0F));
|
||||||
|
}
|
||||||
|
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
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#include "core/audio/audio_adapter.hpp"
|
||||||
|
|
||||||
|
#include "core/resources/resource.hpp"
|
||||||
|
|
||||||
|
namespace AudioResource {
|
||||||
|
auto getMusic(const std::string& name) -> Ja::Music* {
|
||||||
|
return Resource::get()->getMusic(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto getSound(const std::string& name) -> Ja::Sound* {
|
||||||
|
return Resource::get()->getSound(name);
|
||||||
|
}
|
||||||
|
} // namespace AudioResource
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// --- Audio Resource Adapter ---
|
||||||
|
// Aquest fitxer exposa una interfície comuna a Audio per obtenir Ja::Music* /
|
||||||
|
// Ja::Sound* 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
|
||||||
|
|
||||||
|
namespace Ja {
|
||||||
|
struct Music;
|
||||||
|
struct Sound;
|
||||||
|
} // namespace Ja
|
||||||
|
|
||||||
|
namespace AudioResource {
|
||||||
|
auto getMusic(const std::string& name) -> Ja::Music*;
|
||||||
|
auto getSound(const std::string& name) -> Ja::Sound*;
|
||||||
|
} // namespace AudioResource
|
||||||
+641
-499
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,10 @@
|
|||||||
#include "core/input/define_buttons.hpp"
|
#include "core/input/define_buttons.hpp"
|
||||||
|
|
||||||
#include <algorithm> // Para __all_of_fn, all_of
|
#include <algorithm> // Para __all_of_fn, all_of, ranges::transform
|
||||||
|
#include <iterator> // Para back_inserter
|
||||||
#include <memory> // Para unique_ptr, allocator, shared_ptr, operator==, make_unique
|
#include <memory> // Para unique_ptr, allocator, shared_ptr, operator==, make_unique
|
||||||
|
|
||||||
#include "core/input/input.hpp" // Para Input
|
#include "core/input/input.hpp" // Para Input
|
||||||
#include "core/input/input_types.hpp" // Para InputAction
|
|
||||||
#include "core/locale/lang.hpp" // Para getText
|
#include "core/locale/lang.hpp" // Para getText
|
||||||
#include "core/resources/resource.hpp" // Para Resource
|
#include "core/resources/resource.hpp" // Para Resource
|
||||||
#include "game/options.hpp" // Para Gamepad
|
#include "game/options.hpp" // Para Gamepad
|
||||||
@@ -16,10 +16,9 @@ DefineButtons::DefineButtons()
|
|||||||
: input_(Input::get()) {
|
: input_(Input::get()) {
|
||||||
clearButtons();
|
clearButtons();
|
||||||
|
|
||||||
auto gamepads = input_->getGamepads();
|
const auto GAMEPADS = input_->getGamepads();
|
||||||
for (const auto& gamepad : gamepads) {
|
controller_names_.reserve(GAMEPADS.size());
|
||||||
controller_names_.emplace_back(Input::getControllerName(gamepad));
|
std::ranges::transform(GAMEPADS, std::back_inserter(controller_names_), Input::getControllerName);
|
||||||
}
|
|
||||||
|
|
||||||
// Crear la ventana de mensaje
|
// Crear la ventana de mensaje
|
||||||
WindowMessage::Config config(param.service_menu.window_message);
|
WindowMessage::Config config(param.service_menu.window_message);
|
||||||
|
|||||||
@@ -2,14 +2,12 @@
|
|||||||
|
|
||||||
#include <algorithm> // Para __any_of_fn, any_of
|
#include <algorithm> // Para __any_of_fn, any_of
|
||||||
#include <functional> // Para function
|
#include <functional> // Para function
|
||||||
#include <iterator> // Para pair
|
|
||||||
#include <string> // Para basic_string, operator+, allocator, char_traits, string, to_string
|
#include <string> // Para basic_string, operator+, allocator, char_traits, string, to_string
|
||||||
#include <utility> // Para pair
|
#include <utility> // Para pair
|
||||||
#include <vector> // Para vector
|
#include <vector> // Para vector
|
||||||
|
|
||||||
#include "core/audio/audio.hpp" // Para Audio
|
#include "core/audio/audio.hpp" // Para Audio
|
||||||
#include "core/input/input.hpp" // Para Input
|
#include "core/input/input.hpp" // Para Input
|
||||||
#include "core/input/input_types.hpp" // Para InputAction
|
|
||||||
#include "core/locale/lang.hpp" // Para getText, getLangFile, getLangName, getNextLangCode, loadFromFile
|
#include "core/locale/lang.hpp" // Para getText, getLangFile, getLangName, getNextLangCode, loadFromFile
|
||||||
#include "core/rendering/screen.hpp" // Para Screen
|
#include "core/rendering/screen.hpp" // Para Screen
|
||||||
#include "core/system/section.hpp" // Para Name, name, Options, options, AttractMode, attract_mode
|
#include "core/system/section.hpp" // Para Name, name, Options, options, AttractMode, attract_mode
|
||||||
@@ -84,12 +82,12 @@ namespace GlobalInputs {
|
|||||||
void nextPreset() {
|
void nextPreset() {
|
||||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||||
Screen::nextCrtPiPreset();
|
Screen::nextCrtPiPreset();
|
||||||
const std::string name = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset)).name;
|
const std::string NAME = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset)).name;
|
||||||
Notifier::get()->show({"CrtPi: " + name});
|
Notifier::get()->show({"CrtPi: " + NAME});
|
||||||
} else {
|
} else {
|
||||||
Screen::nextPostFXPreset();
|
Screen::nextPostFXPreset();
|
||||||
const std::string name = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset)).name;
|
const std::string NAME = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset)).name;
|
||||||
Notifier::get()->show({"PostFX: " + name});
|
Notifier::get()->show({"PostFX: " + NAME});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+62
-71
@@ -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;
|
||||||
|
|
||||||
@@ -83,53 +118,22 @@ auto Input::checkAction(Action action, bool repeat, bool check_keyboard, const s
|
|||||||
|
|
||||||
// Comprueba si hay almenos una acción activa
|
// Comprueba si hay almenos una acción activa
|
||||||
auto Input::checkAnyInput(bool check_keyboard, const std::shared_ptr<Gamepad>& gamepad) -> bool {
|
auto Input::checkAnyInput(bool check_keyboard, const std::shared_ptr<Gamepad>& gamepad) -> bool {
|
||||||
// Obtenemos el número total de acciones posibles para iterar sobre ellas.
|
const auto JUST_PRESSED = [](const auto& pair) { return pair.second.just_pressed; };
|
||||||
|
|
||||||
// --- Comprobación del Teclado ---
|
if (check_keyboard && std::ranges::any_of(keyboard_.bindings, JUST_PRESSED)) {
|
||||||
if (check_keyboard) {
|
return true;
|
||||||
for (const auto& pair : keyboard_.bindings) {
|
|
||||||
// Simplemente leemos el estado pre-calculado por Input::update().
|
|
||||||
// Ya no se llama a SDL_GetKeyboardState ni se modifica el estado '.active'.
|
|
||||||
if (pair.second.just_pressed) {
|
|
||||||
return true; // Se encontró una acción recién pulsada.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return gamepad != nullptr && std::ranges::any_of(gamepad->bindings, JUST_PRESSED);
|
||||||
// --- Comprobación del Mando ---
|
|
||||||
// Comprobamos si hay mandos y si el índice solicitado es válido.
|
|
||||||
if (gamepad != nullptr) {
|
|
||||||
// Iteramos sobre todas las acciones, no sobre el número de mandos.
|
|
||||||
for (const auto& pair : gamepad->bindings) {
|
|
||||||
// Leemos el estado pre-calculado para el mando y la acción específicos.
|
|
||||||
if (pair.second.just_pressed) {
|
|
||||||
return true; // Se encontró una acción recién pulsada en el mando.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Si llegamos hasta aquí, no se detectó ninguna nueva pulsación.
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comprueba si hay algún botón pulsado
|
// Comprueba si hay algún botón pulsado
|
||||||
auto Input::checkAnyButton(bool repeat) -> bool {
|
auto Input::checkAnyButton(bool repeat) -> bool {
|
||||||
// Solo comprueba los botones definidos previamente
|
return std::ranges::any_of(BUTTON_INPUTS, [this, repeat](auto bi) {
|
||||||
for (auto bi : BUTTON_INPUTS) {
|
if (checkAction(bi, repeat, CHECK_KEYBOARD)) { return true; }
|
||||||
// Comprueba el teclado
|
return std::ranges::any_of(gamepads_, [this, bi, repeat](const auto& gamepad) {
|
||||||
if (checkAction(bi, repeat, CHECK_KEYBOARD)) {
|
return checkAction(bi, repeat, DO_NOT_CHECK_KEYBOARD, gamepad);
|
||||||
return true;
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
// Comprueba los mandos
|
|
||||||
for (const auto& gamepad : gamepads_) {
|
|
||||||
if (checkAction(bi, repeat, DO_NOT_CHECK_KEYBOARD, gamepad)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comprueba si hay algun mando conectado
|
// Comprueba si hay algun mando conectado
|
||||||
@@ -143,9 +147,8 @@ auto Input::getControllerName(const std::shared_ptr<Gamepad>& gamepad) -> std::s
|
|||||||
// Obtiene la lista de nombres de mandos
|
// Obtiene la lista de nombres de mandos
|
||||||
auto Input::getControllerNames() const -> std::vector<std::string> {
|
auto Input::getControllerNames() const -> std::vector<std::string> {
|
||||||
std::vector<std::string> names;
|
std::vector<std::string> names;
|
||||||
for (const auto& gamepad : gamepads_) {
|
names.reserve(gamepads_.size());
|
||||||
names.push_back(gamepad->name);
|
std::ranges::transform(gamepads_, std::back_inserter(names), [](const auto& gamepad) { return gamepad->name; });
|
||||||
}
|
|
||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,21 +157,15 @@ auto Input::getNumGamepads() const -> int { return gamepads_.size(); }
|
|||||||
|
|
||||||
// Obtiene el gamepad a partir de un event.id
|
// Obtiene el gamepad a partir de un event.id
|
||||||
auto Input::getGamepad(SDL_JoystickID id) const -> std::shared_ptr<Input::Gamepad> {
|
auto Input::getGamepad(SDL_JoystickID id) const -> std::shared_ptr<Input::Gamepad> {
|
||||||
for (const auto& gamepad : gamepads_) {
|
const auto IT = std::ranges::find_if(gamepads_,
|
||||||
if (gamepad->instance_id == id) {
|
[id](const auto& gamepad) { return gamepad->instance_id == id; });
|
||||||
return gamepad;
|
return IT != gamepads_.end() ? *IT : nullptr;
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Input::getGamepadByName(const std::string& name) const -> std::shared_ptr<Input::Gamepad> {
|
auto Input::getGamepadByName(const std::string& name) const -> std::shared_ptr<Input::Gamepad> {
|
||||||
for (const auto& gamepad : gamepads_) {
|
const auto IT = std::ranges::find_if(gamepads_,
|
||||||
if (gamepad && gamepad->name == name) {
|
[&name](const auto& gamepad) { return gamepad && gamepad->name == name; });
|
||||||
return gamepad;
|
return IT != gamepads_.end() ? *IT : nullptr;
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtiene el SDL_GamepadButton asignado a un action
|
// Obtiene el SDL_GamepadButton asignado a un action
|
||||||
@@ -324,7 +321,7 @@ void Input::resetInputStates() {
|
|||||||
key.second.just_pressed = false;
|
key.second.just_pressed = false;
|
||||||
}
|
}
|
||||||
// Resetear todos los ControllerBindings.active a false
|
// Resetear todos los ControllerBindings.active a false
|
||||||
for (auto& gamepad : gamepads_) {
|
for (const auto& gamepad : gamepads_) {
|
||||||
for (auto& binding : gamepad->bindings) {
|
for (auto& binding : gamepad->bindings) {
|
||||||
binding.second.is_held = false;
|
binding.second.is_held = false;
|
||||||
binding.second.just_pressed = false;
|
binding.second.just_pressed = false;
|
||||||
@@ -363,11 +360,13 @@ auto Input::handleEvent(const SDL_Event& event) -> std::string {
|
|||||||
return addGamepad(event.gdevice.which);
|
return addGamepad(event.gdevice.which);
|
||||||
case SDL_EVENT_GAMEPAD_REMOVED:
|
case SDL_EVENT_GAMEPAD_REMOVED:
|
||||||
return removeGamepad(event.gdevice.which);
|
return removeGamepad(event.gdevice.which);
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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';
|
||||||
@@ -532,19 +531,11 @@ auto Input::findAvailableGamepadByName(const std::string& gamepad_name) -> std::
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Buscar por nombre
|
// Buscar por nombre
|
||||||
for (const auto& gamepad : gamepads_) {
|
auto by_name = std::ranges::find_if(gamepads_,
|
||||||
if (gamepad && gamepad->name == gamepad_name) {
|
[&gamepad_name](const auto& gamepad) { return gamepad && gamepad->name == gamepad_name; });
|
||||||
return gamepad;
|
if (by_name != gamepads_.end()) { return *by_name; }
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Si no se encuentra por nombre, devolver el primer gamepad válido
|
// Si no se encuentra por nombre, devolver el primer gamepad válido
|
||||||
for (const auto& gamepad : gamepads_) {
|
auto first_valid = std::ranges::find_if(gamepads_, [](const auto& gamepad) { return gamepad != nullptr; });
|
||||||
if (gamepad) {
|
return first_valid != gamepads_.end() ? *first_valid : nullptr;
|
||||||
return gamepad;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Si llegamos aquí, no hay gamepads válidos
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
#include <memory> // Para shared_ptr
|
#include <memory> // Para shared_ptr
|
||||||
#include <string> // Para string, basic_string
|
#include <string> // Para string, basic_string
|
||||||
#include <unordered_map> // Para unordered_map
|
#include <unordered_map> // Para unordered_map
|
||||||
#include <utility> // Para pair
|
|
||||||
#include <vector> // Para vector
|
#include <vector> // Para vector
|
||||||
|
|
||||||
#include "core/input/gamepad_config_manager.hpp" // for GamepadConfig (ptr only), GamepadConfigs
|
#include "core/input/gamepad_config_manager.hpp" // for GamepadConfig (ptr only), GamepadConfigs
|
||||||
@@ -32,7 +31,7 @@ class Input {
|
|||||||
bool is_held; // Está pulsada ahora mismo
|
bool is_held; // Está pulsada ahora mismo
|
||||||
bool just_pressed; // Se acaba de pulsar en este fotograma
|
bool just_pressed; // Se acaba de pulsar en este fotograma
|
||||||
|
|
||||||
KeyState(Uint8 scancode = 0, bool is_held = false, bool just_pressed = false)
|
explicit KeyState(Uint8 scancode = 0, bool is_held = false, bool just_pressed = false)
|
||||||
: scancode(scancode),
|
: scancode(scancode),
|
||||||
is_held(is_held),
|
is_held(is_held),
|
||||||
just_pressed(just_pressed) {}
|
just_pressed(just_pressed) {}
|
||||||
@@ -45,7 +44,7 @@ class Input {
|
|||||||
bool axis_active; // Estado del eje
|
bool axis_active; // Estado del eje
|
||||||
bool trigger_active{false}; // Estado del trigger como botón digital
|
bool trigger_active{false}; // Estado del trigger como botón digital
|
||||||
|
|
||||||
ButtonState(int btn = static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID), bool is_held = false, bool just_pressed = false, bool axis_act = false)
|
explicit ButtonState(int btn = static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID), bool is_held = false, bool just_pressed = false, bool axis_act = false)
|
||||||
: button(btn),
|
: button(btn),
|
||||||
is_held(is_held),
|
is_held(is_held),
|
||||||
just_pressed(just_pressed),
|
just_pressed(just_pressed),
|
||||||
@@ -105,10 +104,20 @@ class Input {
|
|||||||
std::string path;
|
std::string path;
|
||||||
std::unordered_map<Action, ButtonState> bindings;
|
std::unordered_map<Action, ButtonState> bindings;
|
||||||
|
|
||||||
Gamepad(SDL_Gamepad* gamepad)
|
// Recorta el nombre del mando hasta el primer '(' o '[' y elimina espacios finales.
|
||||||
|
// Evita nombres como "Retroid Controller (vendor: 1001) ..." en las notificaciones.
|
||||||
|
static auto trimName(const char* raw) -> std::string {
|
||||||
|
std::string s(raw != nullptr ? raw : "");
|
||||||
|
const auto POS = s.find_first_of("([");
|
||||||
|
if (POS != std::string::npos) { s.erase(POS); }
|
||||||
|
while (!s.empty() && s.back() == ' ') { s.pop_back(); }
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit Gamepad(SDL_Gamepad* gamepad)
|
||||||
: pad(gamepad),
|
: 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
|
||||||
@@ -138,7 +147,7 @@ class Input {
|
|||||||
|
|
||||||
// Reasigna un botón a una acción
|
// Reasigna un botón a una acción
|
||||||
void rebindAction(Action action, SDL_GamepadButton new_button) {
|
void rebindAction(Action action, SDL_GamepadButton new_button) {
|
||||||
bindings[action] = static_cast<int>(new_button);
|
bindings[action] = ButtonState{static_cast<int>(new_button)};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
// --- Enums ---
|
// --- Enums ---
|
||||||
enum class InputAction : int { // Acciones de entrada posibles en el juego
|
enum class InputAction : std::uint8_t { // Acciones de entrada posibles en el juego
|
||||||
// Inputs de movimiento
|
// Inputs de movimiento
|
||||||
UP,
|
UP,
|
||||||
DOWN,
|
DOWN,
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
// --- Clase PauseManager: maneja el sistema de pausa del juego ---
|
// --- Clase PauseManager: maneja el sistema de pausa del juego ---
|
||||||
class PauseManager {
|
class PauseManager {
|
||||||
@@ -48,7 +50,7 @@ class PauseManager {
|
|||||||
|
|
||||||
// --- Métodos principales ---
|
// --- Métodos principales ---
|
||||||
void setFlag(Source source, bool enable) { // Establece/quita una fuente de pausa específica
|
void setFlag(Source source, bool enable) { // Establece/quita una fuente de pausa específica
|
||||||
bool was_paused = isPaused();
|
const bool WAS_PAUSED = isPaused();
|
||||||
|
|
||||||
if (enable) {
|
if (enable) {
|
||||||
flags_ |= source;
|
flags_ |= source;
|
||||||
@@ -56,7 +58,8 @@ class PauseManager {
|
|||||||
flags_ &= ~source; // Ahora funciona: Source &= uint8_t
|
flags_ &= ~source; // Ahora funciona: Source &= uint8_t
|
||||||
}
|
}
|
||||||
|
|
||||||
if (was_paused != isPaused()) {
|
// cppcheck-suppress knownConditionTrueFalse // false-positive: flags_ ha estat modificat entre les dues crides a isPaused()
|
||||||
|
if (WAS_PAUSED != isPaused()) {
|
||||||
notifyPauseChanged();
|
notifyPauseChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,30 +91,16 @@ class PauseManager {
|
|||||||
return "Active";
|
return "Active";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> parts;
|
||||||
|
if (hasFlag(Source::PLAYER)) { parts.emplace_back("Player"); }
|
||||||
|
if (hasFlag(Source::SERVICE_MENU)) { parts.emplace_back("ServiceMenu"); }
|
||||||
|
if (hasFlag(Source::FOCUS_LOST)) { parts.emplace_back("FocusLoss"); }
|
||||||
|
|
||||||
std::string result = "Paused by: ";
|
std::string result = "Paused by: ";
|
||||||
bool first = true;
|
for (size_t i = 0; i < parts.size(); ++i) {
|
||||||
|
if (i > 0) { result += ", "; }
|
||||||
if (hasFlag(Source::PLAYER)) {
|
result += parts[i];
|
||||||
if (!first) {
|
|
||||||
result += ", ";
|
|
||||||
}
|
|
||||||
result += "Player";
|
|
||||||
first = false;
|
|
||||||
}
|
}
|
||||||
if (hasFlag(Source::SERVICE_MENU)) {
|
|
||||||
if (!first) {
|
|
||||||
result += ", ";
|
|
||||||
}
|
|
||||||
result += "ServiceMenu";
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
if (hasFlag(Source::FOCUS_LOST)) {
|
|
||||||
if (!first) {
|
|
||||||
result += ", ";
|
|
||||||
}
|
|
||||||
result += "FocusLoss";
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
void setCallback(std::function<void(bool)> callback) { // Permite cambiar el callback en runtime
|
void setCallback(std::function<void(bool)> callback) { // Permite cambiar el callback en runtime
|
||||||
|
|||||||
+17
-31
@@ -1,5 +1,6 @@
|
|||||||
#include "core/locale/lang.hpp"
|
#include "core/locale/lang.hpp"
|
||||||
|
|
||||||
|
#include <algorithm> // Para ranges::find_if
|
||||||
#include <cstddef> // Para size_t
|
#include <cstddef> // Para size_t
|
||||||
#include <exception> // Para exception
|
#include <exception> // Para exception
|
||||||
#include <fstream> // Para basic_ifstream, basic_istream, ifstream
|
#include <fstream> // Para basic_ifstream, basic_istream, ifstream
|
||||||
@@ -13,7 +14,7 @@
|
|||||||
#include "game/gameplay/difficulty.hpp" // Para Difficulty
|
#include "game/gameplay/difficulty.hpp" // Para Difficulty
|
||||||
#include "game/options.hpp" // Para SettingsOpt...
|
#include "game/options.hpp" // Para SettingsOpt...
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using Json = nlohmann::json;
|
||||||
|
|
||||||
namespace Lang {
|
namespace Lang {
|
||||||
std::unordered_map<std::string, std::string> texts;
|
std::unordered_map<std::string, std::string> texts;
|
||||||
@@ -32,12 +33,12 @@ namespace Lang {
|
|||||||
auto resource_data = ResourceHelper::loadFile(file_path);
|
auto resource_data = ResourceHelper::loadFile(file_path);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
json j;
|
Json j;
|
||||||
|
|
||||||
if (!resource_data.empty()) {
|
if (!resource_data.empty()) {
|
||||||
// Cargar desde datos del pack
|
// Cargar desde datos del pack
|
||||||
std::string content(resource_data.begin(), resource_data.end());
|
std::string content(resource_data.begin(), resource_data.end());
|
||||||
j = json::parse(content);
|
j = Json::parse(content);
|
||||||
} else {
|
} else {
|
||||||
// Fallback a filesystem directo
|
// Fallback a filesystem directo
|
||||||
std::ifstream rfile(file_path);
|
std::ifstream rfile(file_path);
|
||||||
@@ -80,35 +81,23 @@ namespace Lang {
|
|||||||
|
|
||||||
// Obtiene un idioma del vector de idiomas a partir de un código
|
// Obtiene un idioma del vector de idiomas a partir de un código
|
||||||
auto getLanguage(Code code) -> Language {
|
auto getLanguage(Code code) -> Language {
|
||||||
for (const auto& lang : languages) {
|
const auto IT = std::ranges::find_if(languages,
|
||||||
if (lang.code == code) {
|
[code](const auto& lang) { return lang.code == code; });
|
||||||
return lang;
|
return IT != languages.end() ? *IT : languages[0];
|
||||||
}
|
|
||||||
}
|
|
||||||
// Si no se encuentra, devuelve el primero por defecto
|
|
||||||
return languages[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Devuelve el código de un idioma a partir de un nombre
|
// Devuelve el código de un idioma a partir de un nombre
|
||||||
auto getCodeFromName(const std::string& name) -> Code {
|
auto getCodeFromName(const std::string& name) -> Code {
|
||||||
for (const auto& lang : languages) {
|
const auto IT = std::ranges::find_if(languages,
|
||||||
if (lang.name == name) {
|
[&name](const auto& lang) { return lang.name == name; });
|
||||||
return lang.code;
|
return IT != languages.end() ? IT->code : languages[0].code;
|
||||||
}
|
|
||||||
}
|
|
||||||
// Si no se encuentra, devuelve el primero por defecto
|
|
||||||
return languages[0].code;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Devuelve el nombre de un idioma a partir de un código
|
// Devuelve el nombre de un idioma a partir de un código
|
||||||
auto getNameFromCode(Code code) -> std::string {
|
auto getNameFromCode(Code code) -> std::string {
|
||||||
for (const auto& lang : languages) {
|
const auto IT = std::ranges::find_if(languages,
|
||||||
if (lang.code == code) {
|
[code](const auto& lang) { return lang.code == code; });
|
||||||
return lang.name;
|
return IT != languages.end() ? IT->name : languages[0].name;
|
||||||
}
|
|
||||||
}
|
|
||||||
// Si no se encuentra, devuelve el nombre del primer idioma por defecto
|
|
||||||
return languages[0].name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualiza los nombres de los idiomas
|
// Actualiza los nombres de los idiomas
|
||||||
@@ -155,13 +144,10 @@ namespace Lang {
|
|||||||
|
|
||||||
// Obtiene una fichero a partir de un lang::Code
|
// Obtiene una fichero a partir de un lang::Code
|
||||||
auto getLanguageFileName(Lang::Code code) -> std::string {
|
auto getLanguageFileName(Lang::Code code) -> std::string {
|
||||||
for (const auto& lang : languages) {
|
const auto IT = std::ranges::find_if(languages,
|
||||||
if (lang.code == code) {
|
[code](const auto& lang) { return lang.code == code; });
|
||||||
return Asset::get()->getPath(lang.file_name);
|
const auto& file = (IT != languages.end()) ? IT->file_name : languages[0].file_name;
|
||||||
}
|
return Asset::get()->getPath(file);
|
||||||
}
|
|
||||||
// Si no se encuentra, devuelve el fichero del primer idioma por defecto
|
|
||||||
return Asset::get()->getPath(languages[0].file_name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Establece el idioma
|
// Establece el idioma
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint> // Para std::uint8_t
|
||||||
#include <string> // Para string, basic_string
|
#include <string> // Para string, basic_string
|
||||||
#include <utility> // Para move
|
#include <utility> // Para move
|
||||||
|
|
||||||
// --- Namespace Lang: gestión de idiomas y textos ---
|
// --- Namespace Lang: gestión de idiomas y textos ---
|
||||||
namespace Lang {
|
namespace Lang {
|
||||||
// --- Enums ---
|
// --- Enums ---
|
||||||
enum class Code : int {
|
enum class Code : std::uint8_t {
|
||||||
SPANISH = 0, // Español
|
SPANISH = 0, // Español
|
||||||
VALENCIAN = 1, // Valenciano
|
VALENCIAN = 1, // Valenciano
|
||||||
ENGLISH = 2 // Inglés
|
ENGLISH = 2 // Inglés
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ Background::Background(float total_progress_to_complete)
|
|||||||
moon_texture_(Resource::get()->getTexture("game_moon.png")),
|
moon_texture_(Resource::get()->getTexture("game_moon.png")),
|
||||||
grass_sprite_(std::make_unique<AnimatedSprite>(Resource::get()->getTexture("game_grass.png"), Resource::get()->getAnimation("game_grass.ani"))),
|
grass_sprite_(std::make_unique<AnimatedSprite>(Resource::get()->getTexture("game_grass.png"), Resource::get()->getAnimation("game_grass.ani"))),
|
||||||
|
|
||||||
total_progress_to_complete_(total_progress_to_complete),
|
TOTAL_PROGRESS_TO_COMPLETE(total_progress_to_complete),
|
||||||
progress_per_stage_(total_progress_to_complete_ / STAGES),
|
PROGRESS_PER_STAGE(TOTAL_PROGRESS_TO_COMPLETE / STAGES),
|
||||||
sun_completion_progress_(total_progress_to_complete_ * SUN_COMPLETION_FACTOR),
|
SUM_COMPLETION_PROGRESS(TOTAL_PROGRESS_TO_COMPLETE * SUN_COMPLETION_FACTOR),
|
||||||
minimum_completed_progress_(total_progress_to_complete_ * MINIMUM_COMPLETED_PROGRESS_PERCENTAGE),
|
MINIMUM_COMPLETED_PROGRESS(TOTAL_PROGRESS_TO_COMPLETE * MINIMUM_COMPLETED_PROGRESS_PERCENTAGE),
|
||||||
|
|
||||||
rect_(SDL_FRect{.x = 0, .y = 0, .w = static_cast<float>(gradients_texture_->getWidth() / 2), .h = static_cast<float>(gradients_texture_->getHeight() / 2)}),
|
rect_(SDL_FRect{.x = 0, .y = 0, .w = static_cast<float>(gradients_texture_->getWidth() / 2), .h = static_cast<float>(gradients_texture_->getHeight() / 2)}),
|
||||||
src_rect_({.x = 0, .y = 0, .w = 320, .h = 240}),
|
src_rect_({.x = 0, .y = 0, .w = 320, .h = 240}),
|
||||||
@@ -167,7 +167,7 @@ void Background::incrementProgress(float amount) {
|
|||||||
if (state_ == State::NORMAL) {
|
if (state_ == State::NORMAL) {
|
||||||
float old_progress = progress_;
|
float old_progress = progress_;
|
||||||
progress_ += amount;
|
progress_ += amount;
|
||||||
progress_ = std::min(progress_, total_progress_to_complete_);
|
progress_ = std::min(progress_, TOTAL_PROGRESS_TO_COMPLETE);
|
||||||
|
|
||||||
// Notifica el cambio si hay callback y el progreso cambió
|
// Notifica el cambio si hay callback y el progreso cambió
|
||||||
if (progress_callback_ && progress_ != old_progress) {
|
if (progress_callback_ && progress_ != old_progress) {
|
||||||
@@ -179,7 +179,7 @@ void Background::incrementProgress(float amount) {
|
|||||||
// Establece la progresión absoluta
|
// Establece la progresión absoluta
|
||||||
void Background::setProgress(float absolute_progress) {
|
void Background::setProgress(float absolute_progress) {
|
||||||
float old_progress = progress_;
|
float old_progress = progress_;
|
||||||
progress_ = std::clamp(absolute_progress, 0.0F, total_progress_to_complete_);
|
progress_ = std::clamp(absolute_progress, 0.0F, TOTAL_PROGRESS_TO_COMPLETE);
|
||||||
|
|
||||||
// Notifica el cambio si hay callback y el progreso cambió
|
// Notifica el cambio si hay callback y el progreso cambió
|
||||||
if (progress_callback_ && progress_ != old_progress) {
|
if (progress_callback_ && progress_ != old_progress) {
|
||||||
@@ -282,27 +282,27 @@ void Background::updateProgression(float delta_time) {
|
|||||||
float eased_t = easeOutCubic(static_cast<double>(t));
|
float eased_t = easeOutCubic(static_cast<double>(t));
|
||||||
|
|
||||||
// Interpolación desde progreso inicial hasta mínimo
|
// Interpolación desde progreso inicial hasta mínimo
|
||||||
float progress_range = completion_initial_progress_ - minimum_completed_progress_;
|
float progress_range = completion_initial_progress_ - MINIMUM_COMPLETED_PROGRESS;
|
||||||
progress_ = completion_initial_progress_ - (progress_range * eased_t);
|
progress_ = completion_initial_progress_ - (progress_range * eased_t);
|
||||||
} else {
|
} else {
|
||||||
// Transición completada, fijar al valor mínimo
|
// Transición completada, fijar al valor mínimo
|
||||||
progress_ = minimum_completed_progress_;
|
progress_ = MINIMUM_COMPLETED_PROGRESS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calcula la transición de los diferentes fondos
|
// Calcula la transición de los diferentes fondos
|
||||||
const float GRADIENT_NUMBER_FLOAT = std::min(progress_ / progress_per_stage_, 3.0F);
|
const float GRADIENT_NUMBER_FLOAT = std::min(progress_ / PROGRESS_PER_STAGE, 3.0F);
|
||||||
const float PERCENT = GRADIENT_NUMBER_FLOAT - static_cast<int>(GRADIENT_NUMBER_FLOAT);
|
const float PERCENT = GRADIENT_NUMBER_FLOAT - static_cast<int>(GRADIENT_NUMBER_FLOAT);
|
||||||
|
|
||||||
gradient_number_ = static_cast<size_t>(GRADIENT_NUMBER_FLOAT);
|
gradient_number_ = static_cast<size_t>(GRADIENT_NUMBER_FLOAT);
|
||||||
transition_ = PERCENT;
|
transition_ = PERCENT;
|
||||||
|
|
||||||
// Calcula la posición del sol
|
// Calcula la posición del sol
|
||||||
const float SUN_PROGRESSION = std::min(progress_ / sun_completion_progress_, 1.0F);
|
const float SUN_PROGRESSION = std::min(progress_ / SUM_COMPLETION_PROGRESS, 1.0F);
|
||||||
sun_index_ = static_cast<size_t>(SUN_PROGRESSION * (sun_path_.size() - 1));
|
sun_index_ = static_cast<size_t>(SUN_PROGRESSION * (sun_path_.size() - 1));
|
||||||
|
|
||||||
// Calcula la posición de la luna
|
// Calcula la posición de la luna
|
||||||
const float MOON_PROGRESSION = std::min(progress_ / total_progress_to_complete_, 1.0F);
|
const float MOON_PROGRESSION = std::min(progress_ / TOTAL_PROGRESS_TO_COMPLETE, 1.0F);
|
||||||
moon_index_ = static_cast<size_t>(MOON_PROGRESSION * (moon_path_.size() - 1));
|
moon_index_ = static_cast<size_t>(MOON_PROGRESSION * (moon_path_.size() - 1));
|
||||||
|
|
||||||
// Actualiza la velocidad de las nubes
|
// Actualiza la velocidad de las nubes
|
||||||
@@ -318,12 +318,12 @@ void Background::updateCloudsSpeed() {
|
|||||||
|
|
||||||
// Velocidad base según progreso (de -3.0 a -120.0 píxeles/segundo, igual que la versión original)
|
// Velocidad base según progreso (de -3.0 a -120.0 píxeles/segundo, igual que la versión original)
|
||||||
float base_clouds_speed = (-CLOUDS_INITIAL_SPEED_PX_PER_S) +
|
float base_clouds_speed = (-CLOUDS_INITIAL_SPEED_PX_PER_S) +
|
||||||
(-CLOUDS_FINAL_SPEED_RANGE_PX_PER_S * (progress_ / total_progress_to_complete_));
|
(-CLOUDS_FINAL_SPEED_RANGE_PX_PER_S * (progress_ / TOTAL_PROGRESS_TO_COMPLETE));
|
||||||
|
|
||||||
// En estado completado, las nubes se ralentizan gradualmente
|
// En estado completado, las nubes se ralentizan gradualmente
|
||||||
if (state_ == State::COMPLETED) {
|
if (state_ == State::COMPLETED) {
|
||||||
float completion_factor = (progress_ - minimum_completed_progress_) /
|
float completion_factor = (progress_ - MINIMUM_COMPLETED_PROGRESS) /
|
||||||
(total_progress_to_complete_ - minimum_completed_progress_);
|
(TOTAL_PROGRESS_TO_COMPLETE - MINIMUM_COMPLETED_PROGRESS);
|
||||||
completion_factor = std::max(0.1F, completion_factor);
|
completion_factor = std::max(0.1F, completion_factor);
|
||||||
base_clouds_speed *= completion_factor;
|
base_clouds_speed *= completion_factor;
|
||||||
}
|
}
|
||||||
@@ -562,14 +562,13 @@ void Background::createMoonPath() {
|
|||||||
const int FREEZE_START_INDEX = static_cast<int>(NUM_STEPS * (1.0F - FREEZE_PERCENTAGE));
|
const int FREEZE_START_INDEX = static_cast<int>(NUM_STEPS * (1.0F - FREEZE_PERCENTAGE));
|
||||||
|
|
||||||
for (int i = 0; i < NUM_STEPS; ++i) {
|
for (int i = 0; i < NUM_STEPS; ++i) {
|
||||||
double theta = i * STEP;
|
|
||||||
float x = CENTER_X + (RADIUS * cos(theta));
|
|
||||||
float y = CENTER_Y - (RADIUS * sin(theta));
|
|
||||||
|
|
||||||
if (i >= FREEZE_START_INDEX && !moon_path_.empty()) {
|
if (i >= FREEZE_START_INDEX && !moon_path_.empty()) {
|
||||||
moon_path_.push_back(moon_path_.back()); // Repite el último punto válido
|
moon_path_.push_back(moon_path_.back()); // Repite el último punto válido
|
||||||
} else {
|
} else {
|
||||||
moon_path_.push_back({.x = x, .y = y});
|
const double THETA = i * STEP;
|
||||||
|
const float X = CENTER_X + (RADIUS * cos(THETA));
|
||||||
|
const float Y = CENTER_Y - (RADIUS * sin(THETA));
|
||||||
|
moon_path_.push_back({.x = X, .y = Y});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <array> // Para array
|
#include <array> // Para array
|
||||||
#include <cstddef> // Para size_t
|
#include <cstddef> // Para size_t
|
||||||
|
#include <cstdint> // Para std::uint8_t
|
||||||
#include <functional> // Para function
|
#include <functional> // Para function
|
||||||
#include <memory> // Para unique_ptr, shared_ptr
|
#include <memory> // Para unique_ptr, shared_ptr
|
||||||
#include <vector> // Para vector
|
#include <vector> // Para vector
|
||||||
@@ -19,7 +20,7 @@ class AnimatedSprite;
|
|||||||
class Background {
|
class Background {
|
||||||
public:
|
public:
|
||||||
// --- Enums ---
|
// --- Enums ---
|
||||||
enum class State {
|
enum class State : std::uint8_t {
|
||||||
NORMAL, // Progresión normal del día
|
NORMAL, // Progresión normal del día
|
||||||
COMPLETED // Reducción gradual de la actividad
|
COMPLETED // Reducción gradual de la actividad
|
||||||
};
|
};
|
||||||
@@ -28,8 +29,8 @@ class Background {
|
|||||||
using ProgressCallback = std::function<void(float)>; // Callback para sincronización
|
using ProgressCallback = std::function<void(float)>; // Callback para sincronización
|
||||||
|
|
||||||
// --- Constructor y destructor ---
|
// --- Constructor y destructor ---
|
||||||
Background(float total_progress_to_complete = 6100.0F); // Constructor principal
|
explicit Background(float total_progress_to_complete = 6100.0F); // Constructor principal
|
||||||
~Background(); // Destructor
|
~Background(); // Destructor
|
||||||
|
|
||||||
// --- Métodos principales ---
|
// --- Métodos principales ---
|
||||||
void update(float delta_time); // Actualiza la lógica del objeto
|
void update(float delta_time); // Actualiza la lógica del objeto
|
||||||
@@ -87,10 +88,10 @@ class Background {
|
|||||||
std::unique_ptr<AnimatedSprite> grass_sprite_; // Sprite con la hierba
|
std::unique_ptr<AnimatedSprite> grass_sprite_; // Sprite con la hierba
|
||||||
|
|
||||||
// --- Variables de configuración ---
|
// --- Variables de configuración ---
|
||||||
const float total_progress_to_complete_; // Progreso total para completar
|
const float TOTAL_PROGRESS_TO_COMPLETE; // Progreso total para completar
|
||||||
const float progress_per_stage_; // Progreso por etapa
|
const float PROGRESS_PER_STAGE; // Progreso por etapa
|
||||||
const float sun_completion_progress_; // Progreso de completado del sol
|
const float SUM_COMPLETION_PROGRESS; // Progreso de completado del sol
|
||||||
const float minimum_completed_progress_; // Progreso mínimo calculado dinámicamente
|
const float MINIMUM_COMPLETED_PROGRESS; // Progreso mínimo calculado dinámicamente
|
||||||
ProgressCallback progress_callback_; // Callback para notificar cambios de progreso
|
ProgressCallback progress_callback_; // Callback para notificar cambios de progreso
|
||||||
|
|
||||||
// --- Variables de estado ---
|
// --- Variables de estado ---
|
||||||
|
|||||||
@@ -481,7 +481,7 @@ void Fade::activate() {
|
|||||||
case Type::DIAGONAL: {
|
case Type::DIAGONAL: {
|
||||||
rect1_ = {.x = 0, .y = 0, .w = static_cast<float>(param.game.width / num_squares_width_), .h = static_cast<float>(param.game.height / num_squares_height_)};
|
rect1_ = {.x = 0, .y = 0, .w = static_cast<float>(param.game.width / num_squares_width_), .h = static_cast<float>(param.game.height / num_squares_height_)};
|
||||||
square_.clear();
|
square_.clear();
|
||||||
square_age_.assign(num_squares_width_ * num_squares_height_, -1);
|
square_age_.assign(static_cast<size_t>(num_squares_width_) * num_squares_height_, -1);
|
||||||
for (int i = 0; i < num_squares_width_ * num_squares_height_; ++i) {
|
for (int i = 0; i < num_squares_width_ * num_squares_height_; ++i) {
|
||||||
rect1_.x = (i % num_squares_width_) * rect1_.w;
|
rect1_.x = (i % num_squares_width_) * rect1_.w;
|
||||||
rect1_.y = (i / num_squares_width_) * rect1_.h;
|
rect1_.y = (i / num_squares_width_) * rect1_.h;
|
||||||
|
|||||||
+197
-185
@@ -8,145 +8,233 @@
|
|||||||
#include <string> // Para char_traits, operator==, basic_string, string
|
#include <string> // Para char_traits, operator==, basic_string, string
|
||||||
|
|
||||||
namespace GIF {
|
namespace GIF {
|
||||||
inline void readBytes(const uint8_t *&buffer, void *dst, size_t size) {
|
namespace {
|
||||||
std::memcpy(dst, buffer, size);
|
inline void readBytes(const uint8_t *&buffer, void *dst, size_t size) {
|
||||||
buffer += size;
|
std::memcpy(dst, buffer, size);
|
||||||
}
|
buffer += size;
|
||||||
|
|
||||||
void Gif::decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out) {
|
|
||||||
if (code_length < 2 || code_length > 12) {
|
|
||||||
std::cout << "Invalid LZW code length: " << code_length << '\n';
|
|
||||||
throw std::runtime_error("Invalid LZW code length");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int i, bit;
|
// Llavor del diccionari LZW: 0..N-1 com a entrades base, i salta 2 (clear_code + stop_code).
|
||||||
int prev = -1;
|
void resetDictionary(std::vector<DictionaryEntry> &dict, int code_length, int &dictionary_ind) {
|
||||||
std::vector<DictionaryEntry> dictionary;
|
dict.resize(1 << (code_length + 1));
|
||||||
int dictionary_ind;
|
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++) {
|
||||||
unsigned int mask = 0x01;
|
dict[dictionary_ind].byte = static_cast<uint8_t>(dictionary_ind);
|
||||||
int reset_code_length = code_length;
|
dict[dictionary_ind].prev = -1;
|
||||||
int clear_code = 1 << code_length;
|
dict[dictionary_ind].len = 1;
|
||||||
int stop_code = clear_code + 1;
|
}
|
||||||
int match_len = 0;
|
dictionary_ind += 2;
|
||||||
|
|
||||||
dictionary.resize(1 << (code_length + 1));
|
|
||||||
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++) {
|
|
||||||
dictionary[dictionary_ind].byte = static_cast<uint8_t>(dictionary_ind);
|
|
||||||
dictionary[dictionary_ind].prev = -1;
|
|
||||||
dictionary[dictionary_ind].len = 1;
|
|
||||||
}
|
}
|
||||||
dictionary_ind += 2;
|
|
||||||
|
|
||||||
while (input_length > 0) {
|
// Llig `code_length + 1` bits LSB-first del flux d'entrada. Llança si s'acaba el buffer.
|
||||||
|
auto readNextCode(const uint8_t *&input, int &input_length, int code_length, unsigned int &mask) -> int {
|
||||||
int code = 0;
|
int code = 0;
|
||||||
for (i = 0; i < (code_length + 1); i++) {
|
for (int i = 0; i < code_length + 1; i++) {
|
||||||
if (input_length <= 0) {
|
if (input_length <= 0) {
|
||||||
std::cout << "Unexpected end of input in decompress" << '\n';
|
std::cout << "Unexpected end of input in decompress" << '\n';
|
||||||
throw std::runtime_error("Unexpected end of input in decompress");
|
throw std::runtime_error("Unexpected end of input in decompress");
|
||||||
}
|
}
|
||||||
bit = ((*input & mask) != 0) ? 1 : 0;
|
const int BIT = ((*input & mask) != 0) ? 1 : 0;
|
||||||
mask <<= 1;
|
mask <<= 1;
|
||||||
if (mask == 0x100) {
|
if (mask == 0x100) {
|
||||||
mask = 0x01;
|
mask = 0x01;
|
||||||
input++;
|
input++;
|
||||||
input_length--;
|
input_length--;
|
||||||
}
|
}
|
||||||
code |= (bit << i);
|
code |= (BIT << i);
|
||||||
}
|
}
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
if (code == clear_code) {
|
// Afig una nova entrada al diccionari. Resol el cas especial KwKwK (code == dictionary_ind)
|
||||||
code_length = reset_code_length;
|
// començant la cadena des de `prev` en lloc de des de `code`.
|
||||||
dictionary.resize(1 << (code_length + 1));
|
void addDictionaryEntry(std::vector<DictionaryEntry> &dict, int dictionary_ind, int code, int prev) {
|
||||||
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++) {
|
int ptr = (code == dictionary_ind) ? prev : code;
|
||||||
dictionary[dictionary_ind].byte = static_cast<uint8_t>(dictionary_ind);
|
while (dict[ptr].prev != -1) {
|
||||||
dictionary[dictionary_ind].prev = -1;
|
ptr = dict[ptr].prev;
|
||||||
dictionary[dictionary_ind].len = 1;
|
|
||||||
}
|
|
||||||
dictionary_ind += 2;
|
|
||||||
prev = -1;
|
|
||||||
continue;
|
|
||||||
} else if (code == stop_code) {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
dict[dictionary_ind].byte = dict[ptr].byte;
|
||||||
|
dict[dictionary_ind].prev = prev;
|
||||||
|
dict[dictionary_ind].len = dict[prev].len + 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (prev > -1 && code_length < 12) {
|
// Escriu la cadena de bytes associada a `code` en `out` (en ordre invers seguint .prev).
|
||||||
if (code > dictionary_ind) {
|
// Retorna la longitud del match per avançar el cursor de l'eixida.
|
||||||
std::cout << "LZW error: code (" << code << ") exceeds dictionary_ind (" << dictionary_ind << ")" << '\n';
|
auto emitMatch(const std::vector<DictionaryEntry> &dict, int code, uint8_t *out) -> int {
|
||||||
throw std::runtime_error("LZW error: code exceeds dictionary_ind.");
|
const int MATCH_LEN = dict[code].len;
|
||||||
}
|
int cur_code = code;
|
||||||
|
while (cur_code != -1) {
|
||||||
int ptr;
|
out[dict[cur_code].len - 1] = dict[cur_code].byte;
|
||||||
if (code == dictionary_ind) {
|
if (dict[cur_code].prev == cur_code) {
|
||||||
ptr = prev;
|
|
||||||
while (dictionary[ptr].prev != -1)
|
|
||||||
ptr = dictionary[ptr].prev;
|
|
||||||
dictionary[dictionary_ind].byte = dictionary[ptr].byte;
|
|
||||||
} else {
|
|
||||||
ptr = code;
|
|
||||||
while (dictionary[ptr].prev != -1)
|
|
||||||
ptr = dictionary[ptr].prev;
|
|
||||||
dictionary[dictionary_ind].byte = dictionary[ptr].byte;
|
|
||||||
}
|
|
||||||
dictionary[dictionary_ind].prev = prev;
|
|
||||||
dictionary[dictionary_ind].len = dictionary[prev].len + 1;
|
|
||||||
dictionary_ind++;
|
|
||||||
|
|
||||||
if ((dictionary_ind == (1 << (code_length + 1))) && (code_length < 11)) {
|
|
||||||
code_length++;
|
|
||||||
dictionary.resize(1 << (code_length + 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
prev = code;
|
|
||||||
|
|
||||||
if (code < 0 || static_cast<size_t>(code) >= dictionary.size()) {
|
|
||||||
std::cout << "Invalid LZW code " << code << ", dictionary size " << static_cast<unsigned long>(dictionary.size()) << '\n';
|
|
||||||
throw std::runtime_error("LZW error: invalid code encountered");
|
|
||||||
}
|
|
||||||
|
|
||||||
int curCode = code;
|
|
||||||
match_len = dictionary[curCode].len;
|
|
||||||
while (curCode != -1) {
|
|
||||||
out[dictionary[curCode].len - 1] = dictionary[curCode].byte;
|
|
||||||
if (dictionary[curCode].prev == curCode) {
|
|
||||||
std::cout << "Internal error; self-reference detected." << '\n';
|
std::cout << "Internal error; self-reference detected." << '\n';
|
||||||
throw std::runtime_error("Internal error in decompress: self-reference");
|
throw std::runtime_error("Internal error in decompress: self-reference");
|
||||||
}
|
}
|
||||||
curCode = dictionary[curCode].prev;
|
cur_code = dict[cur_code].prev;
|
||||||
}
|
}
|
||||||
out += match_len;
|
return MATCH_LEN;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint8_t> Gif::readSubBlocks(const uint8_t *&buffer) {
|
// Descompone (uncompress) el bloque comprimido usando LZW.
|
||||||
std::vector<uint8_t> data;
|
void decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out) {
|
||||||
uint8_t block_size = *buffer;
|
if (code_length < 2 || code_length > 12) {
|
||||||
buffer++;
|
std::cout << "Invalid LZW code length: " << code_length << '\n';
|
||||||
while (block_size != 0) {
|
throw std::runtime_error("Invalid LZW code length");
|
||||||
data.insert(data.end(), buffer, buffer + block_size);
|
}
|
||||||
buffer += block_size;
|
|
||||||
block_size = *buffer;
|
int prev = -1;
|
||||||
|
std::vector<DictionaryEntry> dictionary;
|
||||||
|
int dictionary_ind = 0;
|
||||||
|
unsigned int mask = 0x01;
|
||||||
|
const int RESET_CODE_LENGTH = code_length;
|
||||||
|
const int CLEAR_CODE = 1 << code_length;
|
||||||
|
const int STOP_CODE = CLEAR_CODE + 1;
|
||||||
|
|
||||||
|
resetDictionary(dictionary, code_length, dictionary_ind);
|
||||||
|
|
||||||
|
while (input_length > 0) {
|
||||||
|
const int CODE = readNextCode(input, input_length, code_length, mask);
|
||||||
|
|
||||||
|
if (CODE == CLEAR_CODE) {
|
||||||
|
code_length = RESET_CODE_LENGTH;
|
||||||
|
resetDictionary(dictionary, code_length, dictionary_ind);
|
||||||
|
prev = -1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (CODE == STOP_CODE) { break; }
|
||||||
|
|
||||||
|
if (prev > -1 && code_length < 12) {
|
||||||
|
if (CODE > dictionary_ind) {
|
||||||
|
std::cout << "LZW error: code (" << CODE << ") exceeds dictionary_ind (" << dictionary_ind << ")" << '\n';
|
||||||
|
throw std::runtime_error("LZW error: code exceeds dictionary_ind.");
|
||||||
|
}
|
||||||
|
addDictionaryEntry(dictionary, dictionary_ind, CODE, prev);
|
||||||
|
dictionary_ind++;
|
||||||
|
|
||||||
|
if ((dictionary_ind == (1 << (code_length + 1))) && (code_length < 11)) {
|
||||||
|
code_length++;
|
||||||
|
dictionary.resize(1 << (code_length + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prev = CODE;
|
||||||
|
|
||||||
|
if (CODE < 0 || static_cast<size_t>(CODE) >= dictionary.size()) {
|
||||||
|
std::cout << "Invalid LZW code " << CODE << ", dictionary size " << static_cast<unsigned long>(dictionary.size()) << '\n';
|
||||||
|
throw std::runtime_error("LZW error: invalid code encountered");
|
||||||
|
}
|
||||||
|
|
||||||
|
out += emitMatch(dictionary, CODE, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lee los sub-bloques de datos y los acumula en un std::vector<uint8_t>.
|
||||||
|
auto readSubBlocks(const uint8_t *&buffer) -> std::vector<uint8_t> {
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
uint8_t block_size = *buffer;
|
||||||
buffer++;
|
buffer++;
|
||||||
|
while (block_size != 0) {
|
||||||
|
data.insert(data.end(), buffer, buffer + block_size);
|
||||||
|
buffer += block_size;
|
||||||
|
block_size = *buffer;
|
||||||
|
buffer++;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint8_t> Gif::processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits) {
|
// Procesa el Image Descriptor y retorna el vector de datos sin comprimir.
|
||||||
ImageDescriptor image_descriptor;
|
auto processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits) -> std::vector<uint8_t> {
|
||||||
readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor));
|
ImageDescriptor image_descriptor;
|
||||||
|
readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor));
|
||||||
|
|
||||||
uint8_t lzw_code_size;
|
uint8_t lzw_code_size;
|
||||||
readBytes(buffer, &lzw_code_size, sizeof(uint8_t));
|
readBytes(buffer, &lzw_code_size, sizeof(uint8_t));
|
||||||
|
|
||||||
std::vector<uint8_t> compressed_data = readSubBlocks(buffer);
|
std::vector<uint8_t> compressed_data = readSubBlocks(buffer);
|
||||||
int uncompressed_data_length = image_descriptor.image_width * image_descriptor.image_height;
|
int uncompressed_data_length = image_descriptor.image_width * image_descriptor.image_height;
|
||||||
std::vector<uint8_t> uncompressed_data(uncompressed_data_length);
|
std::vector<uint8_t> uncompressed_data(uncompressed_data_length);
|
||||||
|
|
||||||
decompress(lzw_code_size, compressed_data.data(), static_cast<int>(compressed_data.size()), uncompressed_data.data());
|
decompress(lzw_code_size, compressed_data.data(), static_cast<int>(compressed_data.size()), uncompressed_data.data());
|
||||||
return uncompressed_data;
|
return uncompressed_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint32_t> Gif::loadPalette(const uint8_t *buffer) {
|
// Procesa el stream completo del GIF y devuelve los datos sin comprimir.
|
||||||
|
auto processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h) -> std::vector<uint8_t> {
|
||||||
|
uint8_t header[6];
|
||||||
|
std::memcpy(header, buffer, 6);
|
||||||
|
buffer += 6;
|
||||||
|
|
||||||
|
std::string header_str(reinterpret_cast<char *>(header), 6);
|
||||||
|
if (header_str != "GIF87a" && header_str != "GIF89a") {
|
||||||
|
std::cout << "Formato de archivo GIF inválido: " << header_str << '\n';
|
||||||
|
throw std::runtime_error("Formato de archivo GIF inválido.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ScreenDescriptor screen_descriptor;
|
||||||
|
readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor));
|
||||||
|
|
||||||
|
w = screen_descriptor.width;
|
||||||
|
h = screen_descriptor.height;
|
||||||
|
|
||||||
|
int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1;
|
||||||
|
std::vector<RGB> global_color_table;
|
||||||
|
if ((screen_descriptor.fields & 0x80) != 0) {
|
||||||
|
const size_t GLOBAL_COLOR_TABLE_SIZE = 1U << (((screen_descriptor.fields & 0x07) + 1));
|
||||||
|
global_color_table.resize(GLOBAL_COLOR_TABLE_SIZE);
|
||||||
|
std::memcpy(global_color_table.data(), buffer, 3 * GLOBAL_COLOR_TABLE_SIZE);
|
||||||
|
buffer += 3 * GLOBAL_COLOR_TABLE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t block_type = *buffer++;
|
||||||
|
while (block_type != TRAILER) {
|
||||||
|
if (block_type == EXTENSION_INTRODUCER) {
|
||||||
|
uint8_t extension_label = *buffer++;
|
||||||
|
switch (extension_label) {
|
||||||
|
case GRAPHIC_CONTROL: {
|
||||||
|
uint8_t block_size = *buffer++;
|
||||||
|
buffer += block_size;
|
||||||
|
uint8_t sub_block_size = *buffer++;
|
||||||
|
while (sub_block_size != 0) {
|
||||||
|
buffer += sub_block_size;
|
||||||
|
sub_block_size = *buffer++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case APPLICATION_EXTENSION:
|
||||||
|
case COMMENT_EXTENSION:
|
||||||
|
case PLAINTEXT_EXTENSION: {
|
||||||
|
uint8_t block_size = *buffer++;
|
||||||
|
buffer += block_size;
|
||||||
|
uint8_t sub_block_size = *buffer++;
|
||||||
|
while (sub_block_size != 0) {
|
||||||
|
buffer += sub_block_size;
|
||||||
|
sub_block_size = *buffer++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
uint8_t block_size = *buffer++;
|
||||||
|
buffer += block_size;
|
||||||
|
uint8_t sub_block_size = *buffer++;
|
||||||
|
while (sub_block_size != 0) {
|
||||||
|
buffer += sub_block_size;
|
||||||
|
sub_block_size = *buffer++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (block_type == IMAGE_DESCRIPTOR) {
|
||||||
|
return processImageDescriptor(buffer, global_color_table, color_resolution_bits);
|
||||||
|
} else {
|
||||||
|
std::cout << "Unrecognized block type: 0x" << std::hex << static_cast<int>(block_type) << std::dec << '\n';
|
||||||
|
return std::vector<uint8_t>{};
|
||||||
|
}
|
||||||
|
block_type = *buffer++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::vector<uint8_t>{};
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
auto loadPalette(const uint8_t *buffer) -> std::vector<uint32_t> {
|
||||||
uint8_t header[6];
|
uint8_t header[6];
|
||||||
std::memcpy(header, buffer, 6);
|
std::memcpy(header, buffer, 6);
|
||||||
buffer += 6;
|
buffer += 6;
|
||||||
@@ -156,7 +244,7 @@ namespace GIF {
|
|||||||
buffer += sizeof(ScreenDescriptor);
|
buffer += sizeof(ScreenDescriptor);
|
||||||
|
|
||||||
std::vector<uint32_t> global_color_table;
|
std::vector<uint32_t> global_color_table;
|
||||||
if (screen_descriptor.fields & 0x80) {
|
if ((screen_descriptor.fields & 0x80) != 0) {
|
||||||
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
|
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
|
||||||
global_color_table.resize(global_color_table_size);
|
global_color_table.resize(global_color_table_size);
|
||||||
for (int i = 0; i < global_color_table_size; ++i) {
|
for (int i = 0; i < global_color_table_size; ++i) {
|
||||||
@@ -170,83 +258,7 @@ namespace GIF {
|
|||||||
return global_color_table;
|
return global_color_table;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> Gif::processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h) {
|
auto loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h) -> std::vector<uint8_t> {
|
||||||
uint8_t header[6];
|
|
||||||
std::memcpy(header, buffer, 6);
|
|
||||||
buffer += 6;
|
|
||||||
|
|
||||||
std::string headerStr(reinterpret_cast<char *>(header), 6);
|
|
||||||
if (headerStr != "GIF87a" && headerStr != "GIF89a") {
|
|
||||||
std::cout << "Formato de archivo GIF inválido: " << headerStr << '\n';
|
|
||||||
throw std::runtime_error("Formato de archivo GIF inválido.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ScreenDescriptor screen_descriptor;
|
|
||||||
readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor));
|
|
||||||
|
|
||||||
w = screen_descriptor.width;
|
|
||||||
h = screen_descriptor.height;
|
|
||||||
|
|
||||||
int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1;
|
|
||||||
std::vector<RGB> global_color_table;
|
|
||||||
if (screen_descriptor.fields & 0x80) {
|
|
||||||
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
|
|
||||||
global_color_table.resize(global_color_table_size);
|
|
||||||
std::memcpy(global_color_table.data(), buffer, 3 * global_color_table_size);
|
|
||||||
buffer += 3 * global_color_table_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t block_type = *buffer++;
|
|
||||||
while (block_type != TRAILER) {
|
|
||||||
if (block_type == EXTENSION_INTRODUCER) {
|
|
||||||
uint8_t extension_label = *buffer++;
|
|
||||||
switch (extension_label) {
|
|
||||||
case GRAPHIC_CONTROL: {
|
|
||||||
uint8_t blockSize = *buffer++;
|
|
||||||
buffer += blockSize;
|
|
||||||
uint8_t subBlockSize = *buffer++;
|
|
||||||
while (subBlockSize != 0) {
|
|
||||||
buffer += subBlockSize;
|
|
||||||
subBlockSize = *buffer++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APPLICATION_EXTENSION:
|
|
||||||
case COMMENT_EXTENSION:
|
|
||||||
case PLAINTEXT_EXTENSION: {
|
|
||||||
uint8_t blockSize = *buffer++;
|
|
||||||
buffer += blockSize;
|
|
||||||
uint8_t subBlockSize = *buffer++;
|
|
||||||
while (subBlockSize != 0) {
|
|
||||||
buffer += subBlockSize;
|
|
||||||
subBlockSize = *buffer++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
uint8_t blockSize = *buffer++;
|
|
||||||
buffer += blockSize;
|
|
||||||
uint8_t subBlockSize = *buffer++;
|
|
||||||
while (subBlockSize != 0) {
|
|
||||||
buffer += subBlockSize;
|
|
||||||
subBlockSize = *buffer++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (block_type == IMAGE_DESCRIPTOR) {
|
|
||||||
return processImageDescriptor(buffer, global_color_table, color_resolution_bits);
|
|
||||||
} else {
|
|
||||||
std::cout << "Unrecognized block type: 0x" << std::hex << static_cast<int>(block_type) << std::dec << '\n';
|
|
||||||
return std::vector<uint8_t>{};
|
|
||||||
}
|
|
||||||
block_type = *buffer++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::vector<uint8_t>{};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint8_t> Gif::loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h) {
|
|
||||||
return processGifStream(buffer, w, h);
|
return processGifStream(buffer, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,29 +64,12 @@ namespace GIF {
|
|||||||
uint8_t foreground_color, background_color;
|
uint8_t foreground_color, background_color;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Gif {
|
// Carga la paleta (global color table) a partir de un buffer,
|
||||||
public:
|
// retornándola en un vector de uint32_t (cada color se compone de R, G, B).
|
||||||
// Descompone (uncompress) el bloque comprimido usando LZW.
|
auto loadPalette(const uint8_t *buffer) -> std::vector<uint32_t>;
|
||||||
// Este método puede lanzar std::runtime_error en caso de error.
|
|
||||||
void decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out);
|
|
||||||
|
|
||||||
// Carga la paleta (global color table) a partir de un buffer,
|
// Carga el stream GIF; devuelve un vector con los datos de imagen sin comprimir y
|
||||||
// retornándola en un vector de uint32_t (cada color se compone de R, G, B).
|
// asigna el ancho y alto mediante referencias.
|
||||||
std::vector<uint32_t> loadPalette(const uint8_t *buffer);
|
auto loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h) -> std::vector<uint8_t>;
|
||||||
|
|
||||||
// Carga el stream GIF; devuelve un vector con los datos de imagen sin comprimir y
|
|
||||||
// asigna el ancho y alto mediante referencias.
|
|
||||||
std::vector<uint8_t> loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h);
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Lee los sub-bloques de datos y los acumula en un std::vector<uint8_t>.
|
|
||||||
std::vector<uint8_t> readSubBlocks(const uint8_t *&buffer);
|
|
||||||
|
|
||||||
// Procesa el Image Descriptor y retorna el vector de datos sin comprimir.
|
|
||||||
std::vector<uint8_t> processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits);
|
|
||||||
|
|
||||||
// Procesa el stream completo del GIF y devuelve los datos sin comprimir.
|
|
||||||
std::vector<uint8_t> processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace GIF
|
} // namespace GIF
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -242,49 +320,50 @@ void Screen::renderShake() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
|
// Compone la línia d'informació de debug: "fps - driver - shader preset"
|
||||||
|
auto Screen::buildDebugInfoText() const -> std::string {
|
||||||
|
std::string info_text = std::to_string(fps_.last_value) + " fps";
|
||||||
|
|
||||||
|
// Driver GPU
|
||||||
|
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||||
|
const std::string DRIVER = shader_backend_->getDriverName();
|
||||||
|
info_text += DRIVER.empty() ? "" : " - " + toLower(DRIVER);
|
||||||
|
} else {
|
||||||
|
info_text += " - sdl";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shader + preset (només si està activat)
|
||||||
|
if (!Options::video.shader.enabled) { return info_text; }
|
||||||
|
|
||||||
|
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||||
|
const std::string PRESET_NAME = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset)).name;
|
||||||
|
info_text += " - crtpi " + toLower(PRESET_NAME);
|
||||||
|
} else {
|
||||||
|
const std::string PRESET_NAME = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset)).name;
|
||||||
|
info_text += " - postfx " + toLower(PRESET_NAME);
|
||||||
|
if (Options::video.supersampling.enabled) { info_text += " (ss)"; }
|
||||||
|
}
|
||||||
|
return info_text;
|
||||||
|
}
|
||||||
|
|
||||||
// Muestra información por pantalla
|
// Muestra información por pantalla
|
||||||
void Screen::renderInfo() const {
|
void Screen::renderInfo() const {
|
||||||
if (debug_info_.show) {
|
if (!debug_info_.show) { return; }
|
||||||
const Color GOLD(0xFF, 0xD7, 0x00);
|
|
||||||
const Color GOLD_SHADOW = GOLD.DARKEN(150);
|
|
||||||
|
|
||||||
// Construir texto: fps - driver - preset
|
const Color GOLD(0xFF, 0xD7, 0x00);
|
||||||
std::string info_text = std::to_string(fps_.last_value) + " fps";
|
const Color GOLD_SHADOW = GOLD.darken(150);
|
||||||
|
|
||||||
// Driver GPU
|
const std::string INFO_TEXT = buildDebugInfoText();
|
||||||
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
const int TEXT_WIDTH = debug_info_.text->length(INFO_TEXT);
|
||||||
const std::string DRIVER = shader_backend_->getDriverName();
|
const int X_POS = (static_cast<int>(param.game.width) - TEXT_WIDTH) / 2;
|
||||||
if (!DRIVER.empty()) {
|
debug_info_.text->writeDX(Text::COLOR | Text::STROKE, X_POS, 1, INFO_TEXT, 1, GOLD, 1, GOLD_SHADOW);
|
||||||
info_text += " - " + toLower(DRIVER);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
info_text += " - sdl";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shader + preset
|
|
||||||
if (Options::video.shader.enabled) {
|
|
||||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
|
||||||
const std::string PRESET_NAME = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset)).name;
|
|
||||||
info_text += " - crtpi " + toLower(PRESET_NAME);
|
|
||||||
} else {
|
|
||||||
const std::string PRESET_NAME = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset)).name;
|
|
||||||
info_text += " - postfx " + toLower(PRESET_NAME);
|
|
||||||
if (Options::video.supersampling.enabled) { info_text += " (ss)"; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Centrado arriba
|
|
||||||
const int TEXT_WIDTH = debug_info_.text->length(info_text);
|
|
||||||
const int X_POS = (static_cast<int>(param.game.width) - TEXT_WIDTH) / 2;
|
|
||||||
debug_info_.text->writeDX(Text::COLOR | Text::STROKE, X_POS, 1, info_text, 1, GOLD, 1, GOLD_SHADOW);
|
|
||||||
|
|
||||||
#ifdef RECORDING
|
#ifdef RECORDING
|
||||||
const std::string REC_TEXT = "recording";
|
const std::string REC_TEXT = "recording";
|
||||||
const int REC_WIDTH = debug_info_.text->length(REC_TEXT);
|
const int REC_WIDTH = debug_info_.text->length(REC_TEXT);
|
||||||
const int REC_X = (static_cast<int>(param.game.width) - REC_WIDTH) / 2;
|
const int REC_X = (static_cast<int>(param.game.width) - REC_WIDTH) / 2;
|
||||||
debug_info_.text->writeDX(Text::COLOR | Text::STROKE, REC_X, 1 + debug_info_.text->getCharacterSize(), REC_TEXT, 1, GOLD, 1, GOLD_SHADOW);
|
debug_info_.text->writeDX(Text::COLOR | Text::STROKE, REC_X, 1 + debug_info_.text->getCharacterSize(), REC_TEXT, 1, GOLD, 1, GOLD_SHADOW);
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
// Inicializa shaders (SDL3GPU)
|
// Inicializa shaders (SDL3GPU)
|
||||||
@@ -302,8 +381,8 @@ void Screen::initShaders() {
|
|||||||
Options::video.gpu.acceleration ? Options::video.gpu.preferred_driver : FALLBACK_DRIVER);
|
Options::video.gpu.acceleration ? Options::video.gpu.preferred_driver : FALLBACK_DRIVER);
|
||||||
}
|
}
|
||||||
if (!self->shader_backend_->isHardwareAccelerated()) {
|
if (!self->shader_backend_->isHardwareAccelerated()) {
|
||||||
const bool ok = self->shader_backend_->init(self->window_, self->game_canvas_, "", "");
|
const bool OK = self->shader_backend_->init(self->window_, self->game_canvas_, "", "");
|
||||||
std::cout << "Screen::initShaders: SDL3GPUShader::init() = " << (ok ? "OK" : "FAILED") << '\n';
|
std::cout << "Screen::initShaders: SDL3GPUShader::init() = " << (OK ? "OK" : "FAILED") << '\n';
|
||||||
}
|
}
|
||||||
if (self->shader_backend_ && self->shader_backend_->isHardwareAccelerated()) {
|
if (self->shader_backend_ && self->shader_backend_->isHardwareAccelerated()) {
|
||||||
self->shader_backend_->setLinearUpscale(Options::video.supersampling.linear_upscale);
|
self->shader_backend_->setLinearUpscale(Options::video.supersampling.linear_upscale);
|
||||||
@@ -424,6 +503,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 +671,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 +711,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,10 +62,11 @@ 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 ---
|
||||||
[[nodiscard]] auto getDisplayMonitorName() const -> std::string { return display_monitor_.name; }
|
[[nodiscard]] auto getDisplayMonitorName() const -> const std::string& { return display_monitor_.name; }
|
||||||
[[nodiscard]] auto getDisplayMonitorWidth() const -> int { return display_monitor_.width; }
|
[[nodiscard]] auto getDisplayMonitorWidth() const -> int { return display_monitor_.width; }
|
||||||
[[nodiscard]] auto getDisplayMonitorHeight() const -> int { return display_monitor_.height; }
|
[[nodiscard]] auto getDisplayMonitorHeight() const -> int { return display_monitor_.height; }
|
||||||
[[nodiscard]] auto getDisplayMonitorRefreshRate() const -> int { return display_monitor_.refresh_rate; }
|
[[nodiscard]] auto getDisplayMonitorRefreshRate() const -> int { return display_monitor_.refresh_rate; }
|
||||||
@@ -232,18 +236,20 @@ 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
|
[[nodiscard]] auto buildDebugInfoText() const -> std::string; // Compone fps + driver + shader/preset para renderInfo
|
||||||
void applyCurrentCrtPiPreset(); // Aplica el preset CrtPi activo al backend
|
void renderPresent(); // Selecciona y ejecuta el método de renderizado adecuado
|
||||||
void adjustWindowSize(); // Calcula el tamaño de la ventana
|
void applyCurrentPostFXPreset(); // Aplica el preset PostFX activo al backend
|
||||||
void getDisplayInfo(); // Obtiene información sobre la pantalla
|
void applyCurrentCrtPiPreset(); // Aplica el preset CrtPi activo al backend
|
||||||
void renderOverlays(); // Renderiza todos los overlays y efectos
|
void adjustWindowSize(); // Calcula el tamaño de la ventana
|
||||||
void renderAttenuate(); // Atenúa la pantalla
|
void getDisplayInfo(); // Obtiene información sobre la pantalla
|
||||||
void createText(); // Crea el objeto de texto
|
void renderOverlays(); // Renderiza todos los overlays y efectos
|
||||||
|
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__
|
||||||
@@ -702,7 +702,7 @@ namespace Rendering {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::memcpy(mapped, pixels, static_cast<size_t>(width * height * 4));
|
std::memcpy(mapped, pixels, static_cast<size_t>(width) * height * 4);
|
||||||
SDL_UnmapGPUTransferBuffer(device_, upload_buffer_);
|
SDL_UnmapGPUTransferBuffer(device_, upload_buffer_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
DisableFormat: true
|
||||||
|
SortIncludes: Never
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# source/core/rendering/sdl3gpu/spv/.clang-tidy
|
||||||
|
Checks: '-*'
|
||||||
|
WarningsAsErrors: ''
|
||||||
|
HeaderFilterRegex: ''
|
||||||
+2
-1
@@ -10357,5 +10357,6 @@ static const uint8_t kcrtpi_frag_spv[] = {
|
|||||||
0x38,
|
0x38,
|
||||||
0x00,
|
0x00,
|
||||||
0x01,
|
0x01,
|
||||||
0x00};
|
0x00,
|
||||||
|
};
|
||||||
static const size_t kcrtpi_frag_spv_size = 10356;
|
static const size_t kcrtpi_frag_spv_size = 10356;
|
||||||
+2
-1
@@ -4249,5 +4249,6 @@ static const uint8_t kdownscale_frag_spv[] = {
|
|||||||
0x38,
|
0x38,
|
||||||
0x00,
|
0x00,
|
||||||
0x01,
|
0x01,
|
||||||
0x00};
|
0x00,
|
||||||
|
};
|
||||||
static const size_t kdownscale_frag_spv_size = 4248;
|
static const size_t kdownscale_frag_spv_size = 4248;
|
||||||
+2
-1
@@ -11241,5 +11241,6 @@ static const uint8_t kpostfx_frag_spv[] = {
|
|||||||
0x38,
|
0x38,
|
||||||
0x00,
|
0x00,
|
||||||
0x01,
|
0x01,
|
||||||
0x00};
|
0x00,
|
||||||
|
};
|
||||||
static const size_t kpostfx_frag_spv_size = 11240;
|
static const size_t kpostfx_frag_spv_size = 11240;
|
||||||
+2
-1
@@ -1445,5 +1445,6 @@ static const uint8_t kpostfx_vert_spv[] = {
|
|||||||
0x38,
|
0x38,
|
||||||
0x00,
|
0x00,
|
||||||
0x01,
|
0x01,
|
||||||
0x00};
|
0x00,
|
||||||
|
};
|
||||||
static const size_t kpostfx_vert_spv_size = 1444;
|
static const size_t kpostfx_vert_spv_size = 1444;
|
||||||
+2
-1
@@ -629,5 +629,6 @@ static const uint8_t kupscale_frag_spv[] = {
|
|||||||
0x38,
|
0x38,
|
||||||
0x00,
|
0x00,
|
||||||
0x01,
|
0x01,
|
||||||
0x00};
|
0x00,
|
||||||
|
};
|
||||||
static const size_t kupscale_frag_spv_size = 628;
|
static const size_t kupscale_frag_spv_size = 628;
|
||||||
@@ -2,13 +2,14 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace Rendering {
|
namespace Rendering {
|
||||||
|
|
||||||
/** @brief Identificador del shader de post-procesado activo */
|
/** @brief Identificador del shader de post-procesado activo */
|
||||||
enum class ShaderType { POSTFX,
|
enum class ShaderType : std::uint8_t { POSTFX,
|
||||||
CRTPI };
|
CRTPI };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ auto CardSprite::enable() -> bool {
|
|||||||
|
|
||||||
// Ángulo inicial
|
// Ángulo inicial
|
||||||
rotate_.angle = start_angle_;
|
rotate_.angle = start_angle_;
|
||||||
rotate_.center = {pos_.w / 2.0F, pos_.h / 2.0F};
|
rotate_.center = {.x = pos_.w / 2.0F, .y = pos_.h / 2.0F};
|
||||||
|
|
||||||
shadow_visible_ = true;
|
shadow_visible_ = true;
|
||||||
return true;
|
return true;
|
||||||
@@ -55,7 +55,7 @@ void CardSprite::startExit() {
|
|||||||
// Rotación continua
|
// Rotación continua
|
||||||
rotate_.enabled = true;
|
rotate_.enabled = true;
|
||||||
rotate_.amount = exit_rotate_amount_;
|
rotate_.amount = exit_rotate_amount_;
|
||||||
rotate_.center = {pos_.w / 2.0F, pos_.h / 2.0F};
|
rotate_.center = {.x = pos_.w / 2.0F, .y = pos_.h / 2.0F};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualiza según el estado
|
// Actualiza según el estado
|
||||||
@@ -80,7 +80,7 @@ void CardSprite::updateEntering(float delta_time) {
|
|||||||
double eased = entry_easing_(static_cast<double>(progress));
|
double eased = entry_easing_(static_cast<double>(progress));
|
||||||
|
|
||||||
// Zoom: de start_zoom_ a 1.0 con rebote
|
// Zoom: de start_zoom_ a 1.0 con rebote
|
||||||
auto current_zoom = static_cast<float>(start_zoom_ + (1.0 - start_zoom_) * eased);
|
auto current_zoom = static_cast<float>(start_zoom_ + ((1.0 - start_zoom_) * eased));
|
||||||
horizontal_zoom_ = current_zoom;
|
horizontal_zoom_ = current_zoom;
|
||||||
vertical_zoom_ = current_zoom;
|
vertical_zoom_ = current_zoom;
|
||||||
|
|
||||||
@@ -90,8 +90,8 @@ void CardSprite::updateEntering(float delta_time) {
|
|||||||
// Posición: de entry_start a landing con easing suave (sin rebote)
|
// Posición: de entry_start a landing con easing suave (sin rebote)
|
||||||
// Usamos easeOutCubic para que el desplazamiento sea fluido
|
// Usamos easeOutCubic para que el desplazamiento sea fluido
|
||||||
double pos_eased = easeOutCubic(static_cast<double>(progress));
|
double pos_eased = easeOutCubic(static_cast<double>(progress));
|
||||||
auto current_x = static_cast<float>(entry_start_x_ + (landing_x_ - entry_start_x_) * pos_eased);
|
auto current_x = static_cast<float>(entry_start_x_ + ((landing_x_ - entry_start_x_) * pos_eased));
|
||||||
auto current_y = static_cast<float>(entry_start_y_ + (landing_y_ - entry_start_y_) * pos_eased);
|
auto current_y = static_cast<float>(entry_start_y_ + ((landing_y_ - entry_start_y_) * pos_eased));
|
||||||
setPos(current_x, current_y);
|
setPos(current_x, current_y);
|
||||||
|
|
||||||
// Detecta el primer toque (cuando el easing alcanza ~1.0 por primera vez)
|
// Detecta el primer toque (cuando el easing alcanza ~1.0 por primera vez)
|
||||||
@@ -117,12 +117,9 @@ void CardSprite::updateExiting(float delta_time) {
|
|||||||
|
|
||||||
// Ganar altura gradualmente (zoom hacia el objetivo)
|
// Ganar altura gradualmente (zoom hacia el objetivo)
|
||||||
if (exit_zoom_speed_ > 0.0F && horizontal_zoom_ < exit_target_zoom_) {
|
if (exit_zoom_speed_ > 0.0F && horizontal_zoom_ < exit_target_zoom_) {
|
||||||
float new_zoom = horizontal_zoom_ + exit_zoom_speed_ * delta_time;
|
const float NEW_ZOOM = std::min(horizontal_zoom_ + (exit_zoom_speed_ * delta_time), exit_target_zoom_);
|
||||||
if (new_zoom > exit_target_zoom_) {
|
horizontal_zoom_ = NEW_ZOOM;
|
||||||
new_zoom = exit_target_zoom_;
|
vertical_zoom_ = NEW_ZOOM;
|
||||||
}
|
|
||||||
horizontal_zoom_ = new_zoom;
|
|
||||||
vertical_zoom_ = new_zoom;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOffScreen()) {
|
if (isOffScreen()) {
|
||||||
@@ -164,8 +161,8 @@ void CardSprite::renderShadow() {
|
|||||||
|
|
||||||
// Offset respecto a la tarjeta: base + extra proporcional a la altura
|
// Offset respecto a la tarjeta: base + extra proporcional a la altura
|
||||||
// La sombra se aleja en diagonal abajo-derecha (opuesta a la luz en 0,0)
|
// La sombra se aleja en diagonal abajo-derecha (opuesta a la luz en 0,0)
|
||||||
float offset_x = shadow_offset_x_ + height * SHADOW_HEIGHT_MULTIPLIER;
|
float offset_x = shadow_offset_x_ + (height * SHADOW_HEIGHT_MULTIPLIER);
|
||||||
float offset_y = shadow_offset_y_ + height * SHADOW_HEIGHT_MULTIPLIER;
|
float offset_y = shadow_offset_y_ + (height * SHADOW_HEIGHT_MULTIPLIER);
|
||||||
|
|
||||||
shadow_texture_->render(
|
shadow_texture_->render(
|
||||||
pos_.x + offset_x,
|
pos_.x + offset_x,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h> // Para SDL_FPoint
|
#include <SDL3/SDL.h> // Para SDL_FPoint
|
||||||
|
|
||||||
|
#include <cstdint> // Para std::uint8_t
|
||||||
#include <functional> // Para function
|
#include <functional> // Para function
|
||||||
#include <memory> // Para shared_ptr
|
#include <memory> // Para shared_ptr
|
||||||
|
|
||||||
@@ -10,7 +11,7 @@
|
|||||||
class Texture;
|
class Texture;
|
||||||
|
|
||||||
// --- Estados de la tarjeta ---
|
// --- Estados de la tarjeta ---
|
||||||
enum class CardState {
|
enum class CardState : std::uint8_t {
|
||||||
IDLE, // No activada todavía
|
IDLE, // No activada todavía
|
||||||
ENTERING, // Animación de entrada (zoom + rotación + desplazamiento con rebote)
|
ENTERING, // Animación de entrada (zoom + rotación + desplazamiento con rebote)
|
||||||
LANDED, // En reposo sobre la mesa
|
LANDED, // En reposo sobre la mesa
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h> // Para SDL_FPoint
|
#include <SDL3/SDL.h> // Para SDL_FPoint
|
||||||
|
|
||||||
|
#include <cstdint> // Para std::uint8_t
|
||||||
#include <functional> // Para std::function
|
#include <functional> // Para std::function
|
||||||
#include <memory> // Para shared_ptr
|
#include <memory> // Para shared_ptr
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -12,12 +13,12 @@
|
|||||||
class Texture;
|
class Texture;
|
||||||
|
|
||||||
// --- Enums ---
|
// --- Enums ---
|
||||||
enum class PathType { // Tipos de recorrido
|
enum class PathType : std::uint8_t { // Tipos de recorrido
|
||||||
VERTICAL,
|
VERTICAL,
|
||||||
HORIZONTAL,
|
HORIZONTAL,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class PathCentered { // Centrado del recorrido
|
enum class PathCentered : std::uint8_t { // Centrado del recorrido
|
||||||
ON_X,
|
ON_X,
|
||||||
ON_Y,
|
ON_Y,
|
||||||
NONE,
|
NONE,
|
||||||
@@ -25,12 +26,12 @@ enum class PathCentered { // Centrado del recorrido
|
|||||||
|
|
||||||
// --- Estructuras ---
|
// --- Estructuras ---
|
||||||
struct Path { // Define un recorrido para el sprite
|
struct Path { // Define un recorrido para el sprite
|
||||||
float start_pos; // Posición inicial
|
float start_pos = 0.0F; // Posición inicial
|
||||||
float end_pos; // Posición final
|
float end_pos = 0.0F; // Posición final
|
||||||
PathType type; // Tipo de movimiento (horizontal/vertical)
|
PathType type = PathType::HORIZONTAL; // Tipo de movimiento (horizontal/vertical)
|
||||||
float fixed_pos; // Posición fija en el eje contrario
|
float fixed_pos = 0.0F; // Posición fija en el eje contrario
|
||||||
float duration_s; // Duración de la animación en segundos
|
float duration_s = 0.0F; // Duración de la animación en segundos
|
||||||
float waiting_time_s; // Tiempo de espera una vez en el destino
|
float waiting_time_s = 0.0F; // Tiempo de espera una vez en el destino
|
||||||
std::function<double(double)> easing_function; // Función de easing
|
std::function<double(double)> easing_function; // Función de easing
|
||||||
float elapsed_time = 0.0F; // Tiempo transcurrido
|
float elapsed_time = 0.0F; // Tiempo transcurrido
|
||||||
float waiting_elapsed = 0.0F; // Tiempo de espera transcurrido
|
float waiting_elapsed = 0.0F; // Tiempo de espera transcurrido
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#include "core/rendering/sprite/smart_sprite.hpp"
|
#include "core/rendering/sprite/smart_sprite.hpp"
|
||||||
|
|
||||||
#include "core/rendering/sprite/moving_sprite.hpp" // Para MovingSprite
|
|
||||||
|
|
||||||
// Actualiza la posición y comprueba si ha llegado a su destino (time-based)
|
// Actualiza la posición y comprueba si ha llegado a su destino (time-based)
|
||||||
void SmartSprite::update(float delta_time) {
|
void SmartSprite::update(float delta_time) {
|
||||||
if (enabled_) {
|
if (enabled_) {
|
||||||
|
|||||||
@@ -3,16 +3,16 @@
|
|||||||
#include <memory> // Para shared_ptr
|
#include <memory> // Para shared_ptr
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite
|
#include "core/rendering/sprite/moving_sprite.hpp" // Para MovingSprite
|
||||||
|
|
||||||
class Texture;
|
class Texture;
|
||||||
|
|
||||||
// --- Clase SmartSprite: sprite animado que se mueve hacia un destino y puede deshabilitarse automáticamente ---
|
// --- Clase SmartSprite: sprite que se mueve hacia un destino y puede deshabilitarse automáticamente ---
|
||||||
class SmartSprite : public AnimatedSprite {
|
class SmartSprite : public MovingSprite {
|
||||||
public:
|
public:
|
||||||
// --- Constructor y destructor ---
|
// --- Constructor y destructor ---
|
||||||
explicit SmartSprite(std::shared_ptr<Texture> texture)
|
explicit SmartSprite(std::shared_ptr<Texture> texture)
|
||||||
: AnimatedSprite(std::move(texture)) {}
|
: MovingSprite(std::move(texture)) {}
|
||||||
~SmartSprite() override = default;
|
~SmartSprite() override = default;
|
||||||
|
|
||||||
// --- Métodos principales ---
|
// --- Métodos principales ---
|
||||||
|
|||||||
@@ -403,9 +403,8 @@ auto Text::loadFile(const std::string& file_path) -> std::shared_ptr<Text::File>
|
|||||||
file.open(file_path);
|
file.open(file_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::istream& input_stream = using_resource_data ? stream : static_cast<std::istream&>(file);
|
|
||||||
|
|
||||||
if ((using_resource_data && stream.good()) || (!using_resource_data && file.is_open() && file.good())) {
|
if ((using_resource_data && stream.good()) || (!using_resource_data && file.is_open() && file.good())) {
|
||||||
|
std::istream& input_stream = using_resource_data ? stream : static_cast<std::istream&>(file);
|
||||||
std::string buffer;
|
std::string buffer;
|
||||||
|
|
||||||
// Lee los dos primeros valores del fichero
|
// Lee los dos primeros valores del fichero
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class Text {
|
|||||||
int kerning;
|
int kerning;
|
||||||
|
|
||||||
// Constructor con argumentos por defecto
|
// Constructor con argumentos por defecto
|
||||||
Style(Uint8 flags = 0,
|
explicit Style(Uint8 flags = 0,
|
||||||
Color text = Color(),
|
Color text = Color(),
|
||||||
Color shadow = Color(),
|
Color shadow = Color(),
|
||||||
Uint8 distance = 1,
|
Uint8 distance = 1,
|
||||||
|
|||||||
@@ -251,11 +251,9 @@ auto Texture::loadSurface(const std::string& file_path) -> std::shared_ptr<Surfa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crear un objeto Gif y llamar a la función loadGif
|
|
||||||
GIF::Gif gif;
|
|
||||||
Uint16 w = 0;
|
Uint16 w = 0;
|
||||||
Uint16 h = 0;
|
Uint16 h = 0;
|
||||||
std::vector<Uint8> raw_pixels = gif.loadGif(buffer.data(), w, h);
|
std::vector<Uint8> raw_pixels = GIF::loadGif(buffer.data(), w, h);
|
||||||
if (raw_pixels.empty()) {
|
if (raw_pixels.empty()) {
|
||||||
std::cout << "Error: No se pudo cargar el GIF " << file_path << '\n';
|
std::cout << "Error: No se pudo cargar el GIF " << file_path << '\n';
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -329,8 +327,7 @@ auto Texture::loadPaletteFromFile(const std::string& file_path) -> Palette {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Usar la nueva función loadPalette, que devuelve un vector<uint32_t>
|
// Usar la nueva función loadPalette, que devuelve un vector<uint32_t>
|
||||||
GIF::Gif gif;
|
std::vector<uint32_t> pal = GIF::loadPalette(buffer.data());
|
||||||
std::vector<uint32_t> pal = gif.loadPalette(buffer.data());
|
|
||||||
if (pal.empty()) {
|
if (pal.empty()) {
|
||||||
std::cout << "Advertencia: No se encontró paleta en el archivo " << file_path << '\n';
|
std::cout << "Advertencia: No se encontró paleta en el archivo " << file_path << '\n';
|
||||||
return palette; // Devuelve un vector vacío si no hay paleta
|
return palette; // Devuelve un vector vacío si no hay paleta
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h> // Para SDL_FRect, SDL_SetTextureColorMod, SDL_Renderer, SDL_Texture
|
#include <SDL3/SDL.h> // Para SDL_FRect, SDL_SetTextureColorMod, SDL_Renderer, SDL_Texture
|
||||||
|
|
||||||
|
#include <cstdint> // Para std::uint8_t
|
||||||
|
|
||||||
#include "utils/color.hpp" // Para Color
|
#include "utils/color.hpp" // Para Color
|
||||||
|
|
||||||
// --- Enums ---
|
// --- Enums ---
|
||||||
enum class TiledBGMode : int { // Modos de funcionamiento para el tileado de fondo
|
enum class TiledBGMode : std::uint8_t { // Modos de funcionamiento para el tileado de fondo
|
||||||
CIRCLE = 0,
|
CIRCLE = 0,
|
||||||
DIAGONAL = 1,
|
DIAGONAL = 1,
|
||||||
RANDOM = 2,
|
RANDOM = 2,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "core/rendering/writer.hpp"
|
#include "core/rendering/writer.hpp"
|
||||||
|
|
||||||
#include "core/rendering/text.hpp" // Para Text
|
// Text es completat ací per `text_->write/length` via shared_ptr; include-cleaner no detecta l'ús indirecte.
|
||||||
|
#include "core/rendering/text.hpp" // IWYU pragma: keep
|
||||||
|
|
||||||
// Actualiza el objeto (delta_time en ms)
|
// Actualiza el objeto (delta_time en ms)
|
||||||
void Writer::update(float delta_time) {
|
void Writer::update(float delta_time) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
class Asset {
|
class Asset {
|
||||||
public:
|
public:
|
||||||
// --- Enums ---
|
// --- Enums ---
|
||||||
enum class Type : int {
|
enum class Type : std::uint8_t {
|
||||||
BITMAP, // Imágenes
|
BITMAP, // Imágenes
|
||||||
MUSIC, // Música
|
MUSIC, // Música
|
||||||
SOUND, // Sonidos
|
SOUND, // Sonidos
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "core/resources/resource_loader.hpp"
|
||||||
|
|
||||||
bool AssetIntegrated::resource_pack_enabled = false;
|
bool AssetIntegrated::resource_pack_enabled = false;
|
||||||
|
|
||||||
void AssetIntegrated::initWithResourcePack(const std::string& executable_path,
|
void AssetIntegrated::initWithResourcePack(const std::string& executable_path,
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "core/resources/asset.hpp"
|
#include "core/resources/asset.hpp"
|
||||||
#include "core/resources/resource_loader.hpp"
|
|
||||||
|
|
||||||
// Extensión de Asset que integra ResourceLoader
|
// Extensión de Asset que integra ResourceLoader
|
||||||
class AssetIntegrated : public Asset {
|
class AssetIntegrated : public Asset {
|
||||||
|
|||||||
+107
-159
@@ -2,17 +2,15 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h> // Para SDL_LogInfo, SDL_LogCategory, SDL_LogError, SDL_SetRenderDrawColor, SDL_EventType, SDL_PollEvent, SDL_RenderFillRect, SDL_RenderRect, SDLK_ESCAPE, SDL_Event
|
#include <SDL3/SDL.h> // Para SDL_LogInfo, SDL_LogCategory, SDL_LogError, SDL_SetRenderDrawColor, SDL_EventType, SDL_PollEvent, SDL_RenderFillRect, SDL_RenderRect, SDLK_ESCAPE, SDL_Event
|
||||||
|
|
||||||
|
#include <algorithm> // Para ranges::transform, ranges::find_if
|
||||||
#include <array> // Para array
|
#include <array> // Para array
|
||||||
#include <cstdlib> // Para exit
|
#include <cstdlib> // Para exit
|
||||||
#include <exception> // Para exception
|
|
||||||
#include <filesystem> // Para exists, path, remove
|
|
||||||
#include <fstream> // Para basic_ofstream, basic_ios, basic_ostream::write, ios, ofstream
|
|
||||||
#include <iostream> // Para std::cout
|
#include <iostream> // Para std::cout
|
||||||
#include <ranges> // Para __find_if_fn, find_if, __find_fn, find
|
#include <iterator> // Para back_inserter
|
||||||
#include <stdexcept> // Para runtime_error
|
#include <stdexcept> // Para runtime_error
|
||||||
#include <utility> // Para move
|
#include <utility> // Para move
|
||||||
|
|
||||||
#include "core/audio/jail_audio.hpp" // Para JA_LoadMusic, JA_LoadSound, JA_DeleteMusic, JA_DeleteSound
|
#include "core/audio/jail_audio.hpp" // Para Ja::loadMusic, Ja::loadSound, Ja::deleteMusic, Ja::deleteSound
|
||||||
#include "core/locale/lang.hpp" // Para getText
|
#include "core/locale/lang.hpp" // Para getText
|
||||||
#include "core/rendering/screen.hpp" // Para Screen
|
#include "core/rendering/screen.hpp" // Para Screen
|
||||||
#include "core/rendering/text.hpp" // Para Text
|
#include "core/rendering/text.hpp" // Para Text
|
||||||
@@ -25,9 +23,6 @@
|
|||||||
#include "utils/utils.hpp" // Para getFileName
|
#include "utils/utils.hpp" // Para getFileName
|
||||||
#include "version.h" // Para APP_NAME, GIT_HASH
|
#include "version.h" // Para APP_NAME, GIT_HASH
|
||||||
|
|
||||||
struct JA_Music_t; // lines 11-11
|
|
||||||
struct JA_Sound_t; // lines 12-12
|
|
||||||
|
|
||||||
// Helper para cargar archivos de audio desde pack o filesystem en memoria
|
// Helper para cargar archivos de audio desde pack o filesystem en memoria
|
||||||
namespace {
|
namespace {
|
||||||
struct AudioData {
|
struct AudioData {
|
||||||
@@ -138,40 +133,37 @@ void Resource::loadEssentialTextures() {
|
|||||||
|
|
||||||
// Inicializa las listas de recursos sin cargar el contenido (modo lazy)
|
// Inicializa las listas de recursos sin cargar el contenido (modo lazy)
|
||||||
void Resource::initResourceLists() {
|
void Resource::initResourceLists() {
|
||||||
|
const auto FILE_TO_NAME = [](const auto& file) { return getFileName(file); };
|
||||||
|
|
||||||
// Inicializa lista de sonidos
|
// Inicializa lista de sonidos
|
||||||
auto sound_list = Asset::get()->getListByType(Asset::Type::SOUND);
|
const auto SOUND_LIST = Asset::get()->getListByType(Asset::Type::SOUND);
|
||||||
sounds_.clear();
|
sounds_.clear();
|
||||||
for (const auto& file : sound_list) {
|
sounds_.reserve(SOUND_LIST.size());
|
||||||
sounds_.emplace_back(getFileName(file));
|
std::ranges::transform(SOUND_LIST, std::back_inserter(sounds_), [&](const auto& file) { return ResourceSound(FILE_TO_NAME(file)); });
|
||||||
}
|
|
||||||
|
|
||||||
// Inicializa lista de músicas
|
// Inicializa lista de músicas
|
||||||
auto music_list = Asset::get()->getListByType(Asset::Type::MUSIC);
|
const auto MUSIC_LIST = Asset::get()->getListByType(Asset::Type::MUSIC);
|
||||||
musics_.clear();
|
musics_.clear();
|
||||||
for (const auto& file : music_list) {
|
musics_.reserve(MUSIC_LIST.size());
|
||||||
musics_.emplace_back(getFileName(file));
|
std::ranges::transform(MUSIC_LIST, std::back_inserter(musics_), [&](const auto& file) { return ResourceMusic(FILE_TO_NAME(file)); });
|
||||||
}
|
|
||||||
|
|
||||||
// Inicializa lista de texturas
|
// Inicializa lista de texturas
|
||||||
auto texture_list = Asset::get()->getListByType(Asset::Type::BITMAP);
|
const auto TEXTURE_LIST = Asset::get()->getListByType(Asset::Type::BITMAP);
|
||||||
textures_.clear();
|
textures_.clear();
|
||||||
for (const auto& file : texture_list) {
|
textures_.reserve(TEXTURE_LIST.size());
|
||||||
textures_.emplace_back(getFileName(file));
|
std::ranges::transform(TEXTURE_LIST, std::back_inserter(textures_), [&](const auto& file) { return ResourceTexture(FILE_TO_NAME(file)); });
|
||||||
}
|
|
||||||
|
|
||||||
// Inicializa lista de ficheros de texto
|
// Inicializa lista de ficheros de texto
|
||||||
auto text_file_list = Asset::get()->getListByType(Asset::Type::FONT);
|
const auto TEXT_FILE_LIST = Asset::get()->getListByType(Asset::Type::FONT);
|
||||||
text_files_.clear();
|
text_files_.clear();
|
||||||
for (const auto& file : text_file_list) {
|
text_files_.reserve(TEXT_FILE_LIST.size());
|
||||||
text_files_.emplace_back(getFileName(file));
|
std::ranges::transform(TEXT_FILE_LIST, std::back_inserter(text_files_), [&](const auto& file) { return ResourceTextFile(FILE_TO_NAME(file)); });
|
||||||
}
|
|
||||||
|
|
||||||
// Inicializa lista de animaciones
|
// Inicializa lista de animaciones
|
||||||
auto animation_list = Asset::get()->getListByType(Asset::Type::ANIMATION);
|
const auto ANIMATION_LIST = Asset::get()->getListByType(Asset::Type::ANIMATION);
|
||||||
animations_.clear();
|
animations_.clear();
|
||||||
for (const auto& file : animation_list) {
|
animations_.reserve(ANIMATION_LIST.size());
|
||||||
animations_.emplace_back(getFileName(file));
|
std::ranges::transform(ANIMATION_LIST, std::back_inserter(animations_), [&](const auto& file) { return ResourceAnimation(FILE_TO_NAME(file)); });
|
||||||
}
|
|
||||||
|
|
||||||
// Los demos se cargan directamente sin mostrar progreso (son pocos y pequeños)
|
// Los demos se cargan directamente sin mostrar progreso (son pocos y pequeños)
|
||||||
loadDemoDataQuiet();
|
loadDemoDataQuiet();
|
||||||
@@ -192,13 +184,12 @@ void Resource::initResourceLists() {
|
|||||||
"smb2_grad"};
|
"smb2_grad"};
|
||||||
|
|
||||||
texts_.clear();
|
texts_.clear();
|
||||||
for (const auto& text_name : TEXT_OBJECTS) {
|
texts_.reserve(TEXT_OBJECTS.size());
|
||||||
texts_.emplace_back(text_name); // Constructor con nullptr por defecto
|
std::ranges::transform(TEXT_OBJECTS, std::back_inserter(texts_), [](const auto& text_name) { return ResourceText(text_name); });
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtiene el sonido a partir de un nombre (con carga perezosa)
|
// Obtiene el sonido a partir de un nombre (con carga perezosa)
|
||||||
auto Resource::getSound(const std::string& name) -> JA_Sound_t* {
|
auto Resource::getSound(const std::string& name) -> Ja::Sound* {
|
||||||
auto it = std::ranges::find_if(sounds_, [&name](const auto& s) -> auto { return s.name == name; });
|
auto it = std::ranges::find_if(sounds_, [&name](const auto& s) -> auto { return s.name == name; });
|
||||||
|
|
||||||
if (it != sounds_.end()) {
|
if (it != sounds_.end()) {
|
||||||
@@ -214,7 +205,7 @@ auto Resource::getSound(const std::string& name) -> JA_Sound_t* {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Obtiene la música a partir de un nombre (con carga perezosa)
|
// Obtiene la música a partir de un nombre (con carga perezosa)
|
||||||
auto Resource::getMusic(const std::string& name) -> JA_Music_t* {
|
auto Resource::getMusic(const std::string& name) -> Ja::Music* {
|
||||||
auto it = std::ranges::find_if(musics_, [&name](const auto& m) -> auto { return m.name == name; });
|
auto it = std::ranges::find_if(musics_, [&name](const auto& m) -> auto { return m.name == name; });
|
||||||
|
|
||||||
if (it != musics_.end()) {
|
if (it != musics_.end()) {
|
||||||
@@ -305,54 +296,48 @@ auto Resource::getDemoData(int index) -> DemoData& {
|
|||||||
|
|
||||||
// --- Métodos de carga perezosa ---
|
// --- Métodos de carga perezosa ---
|
||||||
|
|
||||||
auto Resource::loadSoundLazy(const std::string& name) -> JA_Sound_t* {
|
auto Resource::loadSoundLazy(const std::string& name) -> Ja::Sound* {
|
||||||
auto sound_list = Asset::get()->getListByType(Asset::Type::SOUND);
|
auto sound_list = Asset::get()->getListByType(Asset::Type::SOUND);
|
||||||
for (const auto& file : sound_list) {
|
for (const auto& file : sound_list) {
|
||||||
if (getFileName(file) == name) {
|
if (getFileName(file) == name) {
|
||||||
auto audio_data = loadAudioData(file);
|
auto audio_data = loadAudioData(file);
|
||||||
if (!audio_data.data.empty()) {
|
if (!audio_data.data.empty()) {
|
||||||
return JA_LoadSound(audio_data.data.data(), audio_data.data.size());
|
return Ja::loadSound(audio_data.data.data(), audio_data.data.size());
|
||||||
}
|
}
|
||||||
// Fallback a cargar desde disco si no está en pack
|
// Fallback a cargar desde disco si no está en pack
|
||||||
return JA_LoadSound(file.c_str());
|
return Ja::loadSound(file.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Resource::loadMusicLazy(const std::string& name) -> JA_Music_t* {
|
auto Resource::loadMusicLazy(const std::string& name) -> Ja::Music* {
|
||||||
auto music_list = Asset::get()->getListByType(Asset::Type::MUSIC);
|
auto music_list = Asset::get()->getListByType(Asset::Type::MUSIC);
|
||||||
for (const auto& file : music_list) {
|
for (const auto& file : music_list) {
|
||||||
if (getFileName(file) == name) {
|
if (getFileName(file) == name) {
|
||||||
auto audio_data = loadAudioData(file);
|
auto audio_data = loadAudioData(file);
|
||||||
if (!audio_data.data.empty()) {
|
if (!audio_data.data.empty()) {
|
||||||
return JA_LoadMusic(audio_data.data.data(), audio_data.data.size());
|
return Ja::loadMusic(audio_data.data.data(), audio_data.data.size());
|
||||||
}
|
}
|
||||||
// Fallback a cargar desde disco si no está en pack
|
// Fallback a cargar desde disco si no está en pack
|
||||||
return JA_LoadMusic(file.c_str());
|
return Ja::loadMusic(file.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Resource::loadTextureLazy(const std::string& name) -> std::shared_ptr<Texture> {
|
auto Resource::loadTextureLazy(const std::string& name) -> std::shared_ptr<Texture> {
|
||||||
auto texture_list = Asset::get()->getListByType(Asset::Type::BITMAP);
|
const auto TEXTURE_LIST = Asset::get()->getListByType(Asset::Type::BITMAP);
|
||||||
for (const auto& file : texture_list) {
|
const auto IT = std::ranges::find_if(TEXTURE_LIST,
|
||||||
if (getFileName(file) == name) {
|
[&name](const auto& file) { return getFileName(file) == name; });
|
||||||
return std::make_shared<Texture>(Screen::get()->getRenderer(), file);
|
return IT != TEXTURE_LIST.end() ? std::make_shared<Texture>(Screen::get()->getRenderer(), *IT) : nullptr;
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Resource::loadTextFileLazy(const std::string& name) -> std::shared_ptr<Text::File> {
|
auto Resource::loadTextFileLazy(const std::string& name) -> std::shared_ptr<Text::File> {
|
||||||
auto text_file_list = Asset::get()->getListByType(Asset::Type::FONT);
|
const auto TEXT_FILE_LIST = Asset::get()->getListByType(Asset::Type::FONT);
|
||||||
for (const auto& file : text_file_list) {
|
const auto IT = std::ranges::find_if(TEXT_FILE_LIST,
|
||||||
if (getFileName(file) == name) {
|
[&name](const auto& file) { return getFileName(file) == name; });
|
||||||
return Text::loadFile(file);
|
return IT != TEXT_FILE_LIST.end() ? Text::loadFile(*IT) : nullptr;
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Resource::loadTextLazy(const std::string& name) -> std::shared_ptr<Text> {
|
auto Resource::loadTextLazy(const std::string& name) -> std::shared_ptr<Text> {
|
||||||
@@ -377,27 +362,22 @@ auto Resource::loadTextLazy(const std::string& name) -> std::shared_ptr<Text> {
|
|||||||
{.key = "smb2", .texture_file = "smb2.png", .text_file = "smb2.txt"},
|
{.key = "smb2", .texture_file = "smb2.png", .text_file = "smb2.txt"},
|
||||||
{.key = "smb2_grad", .texture_file = "smb2_grad.png", .text_file = "smb2.txt"}};
|
{.key = "smb2_grad", .texture_file = "smb2_grad.png", .text_file = "smb2.txt"}};
|
||||||
|
|
||||||
for (const auto& mapping : TEXT_MAPPINGS) {
|
const auto IT = std::ranges::find_if(TEXT_MAPPINGS,
|
||||||
if (mapping.key == name) {
|
[&name](const auto& mapping) { return mapping.key == name; });
|
||||||
// Cargar las dependencias automáticamente
|
if (IT == TEXT_MAPPINGS.end()) {
|
||||||
auto texture = getTexture(mapping.texture_file); // Esto cargará la textura si no está cargada
|
return nullptr;
|
||||||
auto text_file = getTextFile(mapping.text_file); // Esto cargará el archivo de texto si no está cargado
|
|
||||||
|
|
||||||
if (texture && text_file) {
|
|
||||||
return std::make_shared<Text>(texture, text_file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
auto texture = getTexture(IT->texture_file); // Esto cargará la textura si no está cargada
|
||||||
return nullptr;
|
auto text_file = getTextFile(IT->text_file); // Esto cargará el archivo de texto si no está cargado
|
||||||
|
return (texture && text_file) ? std::make_shared<Text>(texture, text_file) : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Resource::loadAnimationLazy(const std::string& name) -> AnimationsFileBuffer {
|
auto Resource::loadAnimationLazy(const std::string& name) -> AnimationsFileBuffer {
|
||||||
auto animation_list = Asset::get()->getListByType(Asset::Type::ANIMATION);
|
const auto ANIMATION_LIST = Asset::get()->getListByType(Asset::Type::ANIMATION);
|
||||||
for (const auto& file : animation_list) {
|
const auto IT = std::ranges::find_if(ANIMATION_LIST,
|
||||||
if (getFileName(file) == name) {
|
[&name](const auto& file) { return getFileName(file) == name; });
|
||||||
return loadAnimationsFromFile(file);
|
if (IT != ANIMATION_LIST.end()) {
|
||||||
}
|
return loadAnimationsFromFile(*IT);
|
||||||
}
|
}
|
||||||
// Si no se encuentra, retorna vector vacío
|
// Si no se encuentra, retorna vector vacío
|
||||||
return AnimationsFileBuffer{};
|
return AnimationsFileBuffer{};
|
||||||
@@ -440,80 +420,54 @@ auto Resource::isLoadDone() const -> bool {
|
|||||||
return stage_ == LoadStage::DONE;
|
return stage_ == LoadStage::DONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Avança una etapa que descarrega una llista d'assets.
|
||||||
|
void Resource::advanceListLoadStage(const std::vector<std::string>& list, void (Resource::*load_one)(size_t), LoadStage next_stage) {
|
||||||
|
if (stage_index_ >= list.size()) {
|
||||||
|
stage_ = next_stage;
|
||||||
|
stage_index_ = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(this->*load_one)(stage_index_++);
|
||||||
|
}
|
||||||
|
|
||||||
// Bombea la máquina de etapas hasta agotar el presupuesto de tiempo o completar la carga.
|
// Bombea la máquina de etapas hasta agotar el presupuesto de tiempo o completar la carga.
|
||||||
// Devuelve true cuando ya no queda nada por cargar.
|
// Devuelve true cuando ya no queda nada por cargar.
|
||||||
auto Resource::loadStep(int budget_ms) -> bool {
|
auto Resource::loadStep(int budget_ms) -> bool {
|
||||||
if (stage_ == LoadStage::DONE) { return true; }
|
if (stage_ == LoadStage::DONE) { return true; }
|
||||||
|
|
||||||
const Uint64 start_ns = SDL_GetTicksNS();
|
const Uint64 START_NS = SDL_GetTicksNS();
|
||||||
const Uint64 budget_ns = static_cast<Uint64>(budget_ms) * 1'000'000ULL;
|
const Uint64 BUDGET_NS = static_cast<Uint64>(budget_ms) * 1'000'000ULL;
|
||||||
|
|
||||||
while (stage_ != LoadStage::DONE) {
|
while (stage_ != LoadStage::DONE) {
|
||||||
switch (stage_) {
|
switch (stage_) {
|
||||||
case LoadStage::SOUNDS: {
|
case LoadStage::SOUNDS: {
|
||||||
auto list = Asset::get()->getListByType(Asset::Type::SOUND);
|
|
||||||
if (stage_index_ == 0) { sounds_.clear(); }
|
if (stage_index_ == 0) { sounds_.clear(); }
|
||||||
if (stage_index_ >= list.size()) {
|
advanceListLoadStage(Asset::get()->getListByType(Asset::Type::SOUND), &Resource::loadOneSound, LoadStage::MUSICS);
|
||||||
stage_ = LoadStage::MUSICS;
|
|
||||||
stage_index_ = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
loadOneSound(stage_index_++);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LoadStage::MUSICS: {
|
case LoadStage::MUSICS: {
|
||||||
auto list = Asset::get()->getListByType(Asset::Type::MUSIC);
|
|
||||||
if (stage_index_ == 0) { musics_.clear(); }
|
if (stage_index_ == 0) { musics_.clear(); }
|
||||||
if (stage_index_ >= list.size()) {
|
advanceListLoadStage(Asset::get()->getListByType(Asset::Type::MUSIC), &Resource::loadOneMusic, LoadStage::TEXTURES);
|
||||||
stage_ = LoadStage::TEXTURES;
|
|
||||||
stage_index_ = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
loadOneMusic(stage_index_++);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LoadStage::TEXTURES: {
|
case LoadStage::TEXTURES: {
|
||||||
auto list = Asset::get()->getListByType(Asset::Type::BITMAP);
|
|
||||||
if (stage_index_ == 0) { textures_.clear(); }
|
if (stage_index_ == 0) { textures_.clear(); }
|
||||||
if (stage_index_ >= list.size()) {
|
advanceListLoadStage(Asset::get()->getListByType(Asset::Type::BITMAP), &Resource::loadOneTexture, LoadStage::TEXT_FILES);
|
||||||
stage_ = LoadStage::TEXT_FILES;
|
|
||||||
stage_index_ = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
loadOneTexture(stage_index_++);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LoadStage::TEXT_FILES: {
|
case LoadStage::TEXT_FILES: {
|
||||||
auto list = Asset::get()->getListByType(Asset::Type::FONT);
|
|
||||||
if (stage_index_ == 0) { text_files_.clear(); }
|
if (stage_index_ == 0) { text_files_.clear(); }
|
||||||
if (stage_index_ >= list.size()) {
|
advanceListLoadStage(Asset::get()->getListByType(Asset::Type::FONT), &Resource::loadOneTextFile, LoadStage::ANIMATIONS);
|
||||||
stage_ = LoadStage::ANIMATIONS;
|
|
||||||
stage_index_ = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
loadOneTextFile(stage_index_++);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LoadStage::ANIMATIONS: {
|
case LoadStage::ANIMATIONS: {
|
||||||
auto list = Asset::get()->getListByType(Asset::Type::ANIMATION);
|
|
||||||
if (stage_index_ == 0) { animations_.clear(); }
|
if (stage_index_ == 0) { animations_.clear(); }
|
||||||
if (stage_index_ >= list.size()) {
|
advanceListLoadStage(Asset::get()->getListByType(Asset::Type::ANIMATION), &Resource::loadOneAnimation, LoadStage::DEMO_DATA);
|
||||||
stage_ = LoadStage::DEMO_DATA;
|
|
||||||
stage_index_ = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
loadOneAnimation(stage_index_++);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LoadStage::DEMO_DATA: {
|
case LoadStage::DEMO_DATA: {
|
||||||
auto list = Asset::get()->getListByType(Asset::Type::DEMODATA);
|
|
||||||
if (stage_index_ == 0) { demos_.clear(); }
|
if (stage_index_ == 0) { demos_.clear(); }
|
||||||
if (stage_index_ >= list.size()) {
|
advanceListLoadStage(Asset::get()->getListByType(Asset::Type::DEMODATA), &Resource::loadOneDemoData, LoadStage::CREATE_TEXT);
|
||||||
stage_ = LoadStage::CREATE_TEXT;
|
|
||||||
stage_index_ = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
loadOneDemoData(stage_index_++);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LoadStage::CREATE_TEXT:
|
case LoadStage::CREATE_TEXT:
|
||||||
@@ -532,7 +486,7 @@ auto Resource::loadStep(int budget_ms) -> bool {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((SDL_GetTicksNS() - start_ns) >= budget_ns) { break; }
|
if ((SDL_GetTicksNS() - START_NS) >= BUDGET_NS) { break; }
|
||||||
}
|
}
|
||||||
|
|
||||||
return stage_ == LoadStage::DONE;
|
return stage_ == LoadStage::DONE;
|
||||||
@@ -555,11 +509,11 @@ void Resource::loadOneSound(size_t idx) {
|
|||||||
auto name = getFileName(path);
|
auto name = getFileName(path);
|
||||||
updateLoadingProgress(name);
|
updateLoadingProgress(name);
|
||||||
auto audio_data = loadAudioData(path);
|
auto audio_data = loadAudioData(path);
|
||||||
JA_Sound_t* sound = nullptr;
|
Ja::Sound* sound = nullptr;
|
||||||
if (!audio_data.data.empty()) {
|
if (!audio_data.data.empty()) {
|
||||||
sound = JA_LoadSound(audio_data.data.data(), audio_data.data.size());
|
sound = Ja::loadSound(audio_data.data.data(), audio_data.data.size());
|
||||||
} else {
|
} else {
|
||||||
sound = JA_LoadSound(path.c_str());
|
sound = Ja::loadSound(path.c_str());
|
||||||
}
|
}
|
||||||
if (sound == nullptr) {
|
if (sound == nullptr) {
|
||||||
std::cout << "Sound load failed: " << name << '\n';
|
std::cout << "Sound load failed: " << name << '\n';
|
||||||
@@ -574,11 +528,11 @@ void Resource::loadOneMusic(size_t idx) {
|
|||||||
auto name = getFileName(path);
|
auto name = getFileName(path);
|
||||||
updateLoadingProgress(name);
|
updateLoadingProgress(name);
|
||||||
auto audio_data = loadAudioData(path);
|
auto audio_data = loadAudioData(path);
|
||||||
JA_Music_t* music = nullptr;
|
Ja::Music* music = nullptr;
|
||||||
if (!audio_data.data.empty()) {
|
if (!audio_data.data.empty()) {
|
||||||
music = JA_LoadMusic(audio_data.data.data(), audio_data.data.size());
|
music = Ja::loadMusic(audio_data.data.data(), audio_data.data.size());
|
||||||
} else {
|
} else {
|
||||||
music = JA_LoadMusic(path.c_str());
|
music = Ja::loadMusic(path.c_str());
|
||||||
}
|
}
|
||||||
if (music == nullptr) {
|
if (music == nullptr) {
|
||||||
std::cout << "Music load failed: " << name << '\n';
|
std::cout << "Music load failed: " << name << '\n';
|
||||||
@@ -640,14 +594,10 @@ void Resource::createPlayerTextures() {
|
|||||||
const auto& player = players[player_idx]; // Obtenemos el jugador actual
|
const auto& player = players[player_idx]; // Obtenemos el jugador actual
|
||||||
|
|
||||||
// Encontrar el archivo original de la textura
|
// Encontrar el archivo original de la textura
|
||||||
std::string texture_file_path;
|
const auto TEXTURE_LIST = Asset::get()->getListByType(Asset::Type::BITMAP);
|
||||||
auto texture_list = Asset::get()->getListByType(Asset::Type::BITMAP);
|
const auto IT = std::ranges::find_if(TEXTURE_LIST,
|
||||||
for (const auto& file : texture_list) {
|
[&player](const auto& file) { return getFileName(file) == player.base_texture; });
|
||||||
if (getFileName(file) == player.base_texture) {
|
const std::string TEXTURE_FILE_PATH = (IT != TEXTURE_LIST.end()) ? *IT : std::string{};
|
||||||
texture_file_path = file;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Crear las 4 texturas con sus respectivas paletas
|
// Crear las 4 texturas con sus respectivas paletas
|
||||||
for (int palette_idx = 0; palette_idx < 4; ++palette_idx) {
|
for (int palette_idx = 0; palette_idx < 4; ++palette_idx) {
|
||||||
@@ -656,14 +606,14 @@ void Resource::createPlayerTextures() {
|
|||||||
if (palette_idx == 0) {
|
if (palette_idx == 0) {
|
||||||
// Textura 0 - usar la ya cargada y modificar solo paleta 0 (default_shirt)
|
// Textura 0 - usar la ya cargada y modificar solo paleta 0 (default_shirt)
|
||||||
texture = getTexture(player.base_texture);
|
texture = getTexture(player.base_texture);
|
||||||
texture->setPaletteColor(0, 16, param.player.default_shirt[player_idx].darkest.TO_UINT32());
|
texture->setPaletteColor(0, 16, param.player.default_shirt[player_idx].darkest.toUint32());
|
||||||
texture->setPaletteColor(0, 17, param.player.default_shirt[player_idx].dark.TO_UINT32());
|
texture->setPaletteColor(0, 17, param.player.default_shirt[player_idx].dark.toUint32());
|
||||||
texture->setPaletteColor(0, 18, param.player.default_shirt[player_idx].base.TO_UINT32());
|
texture->setPaletteColor(0, 18, param.player.default_shirt[player_idx].base.toUint32());
|
||||||
texture->setPaletteColor(0, 19, param.player.default_shirt[player_idx].light.TO_UINT32());
|
texture->setPaletteColor(0, 19, param.player.default_shirt[player_idx].light.toUint32());
|
||||||
texture->setPaletteColor(0, 56, param.player.outline_color[player_idx].TO_UINT32());
|
texture->setPaletteColor(0, 56, param.player.outline_color[player_idx].toUint32());
|
||||||
} else {
|
} else {
|
||||||
// Crear textura nueva desde archivo usando ResourceHelper
|
// Crear textura nueva desde archivo usando ResourceHelper
|
||||||
texture = std::make_shared<Texture>(Screen::get()->getRenderer(), texture_file_path);
|
texture = std::make_shared<Texture>(Screen::get()->getRenderer(), TEXTURE_FILE_PATH);
|
||||||
|
|
||||||
// Añadir todas las paletas
|
// Añadir todas las paletas
|
||||||
texture->addPaletteFromPalFile(Asset::get()->getPath(player.palette_files[0]));
|
texture->addPaletteFromPalFile(Asset::get()->getPath(player.palette_files[0]));
|
||||||
@@ -672,18 +622,18 @@ void Resource::createPlayerTextures() {
|
|||||||
|
|
||||||
if (palette_idx == 1) {
|
if (palette_idx == 1) {
|
||||||
// Textura 1 - modificar solo paleta 1 (one_coffee_shirt)
|
// Textura 1 - modificar solo paleta 1 (one_coffee_shirt)
|
||||||
texture->setPaletteColor(1, 16, param.player.one_coffee_shirt[player_idx].darkest.TO_UINT32());
|
texture->setPaletteColor(1, 16, param.player.one_coffee_shirt[player_idx].darkest.toUint32());
|
||||||
texture->setPaletteColor(1, 17, param.player.one_coffee_shirt[player_idx].dark.TO_UINT32());
|
texture->setPaletteColor(1, 17, param.player.one_coffee_shirt[player_idx].dark.toUint32());
|
||||||
texture->setPaletteColor(1, 18, param.player.one_coffee_shirt[player_idx].base.TO_UINT32());
|
texture->setPaletteColor(1, 18, param.player.one_coffee_shirt[player_idx].base.toUint32());
|
||||||
texture->setPaletteColor(1, 19, param.player.one_coffee_shirt[player_idx].light.TO_UINT32());
|
texture->setPaletteColor(1, 19, param.player.one_coffee_shirt[player_idx].light.toUint32());
|
||||||
texture->setPaletteColor(1, 56, param.player.outline_color[player_idx].TO_UINT32());
|
texture->setPaletteColor(1, 56, param.player.outline_color[player_idx].toUint32());
|
||||||
} else if (palette_idx == 2) {
|
} else if (palette_idx == 2) {
|
||||||
// Textura 2 - modificar solo paleta 2 (two_coffee_shirt)
|
// Textura 2 - modificar solo paleta 2 (two_coffee_shirt)
|
||||||
texture->setPaletteColor(2, 16, param.player.two_coffee_shirt[player_idx].darkest.TO_UINT32());
|
texture->setPaletteColor(2, 16, param.player.two_coffee_shirt[player_idx].darkest.toUint32());
|
||||||
texture->setPaletteColor(2, 17, param.player.two_coffee_shirt[player_idx].dark.TO_UINT32());
|
texture->setPaletteColor(2, 17, param.player.two_coffee_shirt[player_idx].dark.toUint32());
|
||||||
texture->setPaletteColor(2, 18, param.player.two_coffee_shirt[player_idx].base.TO_UINT32());
|
texture->setPaletteColor(2, 18, param.player.two_coffee_shirt[player_idx].base.toUint32());
|
||||||
texture->setPaletteColor(2, 19, param.player.two_coffee_shirt[player_idx].light.TO_UINT32());
|
texture->setPaletteColor(2, 19, param.player.two_coffee_shirt[player_idx].light.toUint32());
|
||||||
texture->setPaletteColor(2, 56, param.player.outline_color[player_idx].TO_UINT32());
|
texture->setPaletteColor(2, 56, param.player.outline_color[player_idx].toUint32());
|
||||||
}
|
}
|
||||||
// Textura 3 (palette_idx == 3) - no modificar nada, usar colores originales
|
// Textura 3 (palette_idx == 3) - no modificar nada, usar colores originales
|
||||||
}
|
}
|
||||||
@@ -720,9 +670,9 @@ void Resource::createTextTextures() {
|
|||||||
{"game_text_1000000_points", Lang::getText("[GAME_TEXT] 8")}};
|
{"game_text_1000000_points", Lang::getText("[GAME_TEXT] 8")}};
|
||||||
|
|
||||||
auto text1 = getText("04b_25_enhanced");
|
auto text1 = getText("04b_25_enhanced");
|
||||||
for (const auto& s : strings1) {
|
std::ranges::transform(strings1, std::back_inserter(textures_), [&](const auto& s) {
|
||||||
textures_.emplace_back(s.name, text1->writeDXToTexture(Text::STROKE, s.text, -2, Colors::NO_COLOR_MOD, 1, param.game.item_text_outline_color));
|
return ResourceTexture(s.name, text1->writeDXToTexture(Text::STROKE, s.text, -2, Colors::NO_COLOR_MOD, 1, param.game.item_text_outline_color));
|
||||||
}
|
});
|
||||||
|
|
||||||
// Texturas de tamaño doble
|
// Texturas de tamaño doble
|
||||||
std::vector<NameAndText> strings2 = {
|
std::vector<NameAndText> strings2 = {
|
||||||
@@ -734,9 +684,9 @@ void Resource::createTextTextures() {
|
|||||||
{"game_text_game_over", "Game Over"}};
|
{"game_text_game_over", "Game Over"}};
|
||||||
|
|
||||||
auto text2 = getText("04b_25_2x_enhanced");
|
auto text2 = getText("04b_25_2x_enhanced");
|
||||||
for (const auto& s : strings2) {
|
std::ranges::transform(strings2, std::back_inserter(textures_), [&](const auto& s) {
|
||||||
textures_.emplace_back(s.name, text2->writeDXToTexture(Text::STROKE, s.text, -4, Colors::NO_COLOR_MOD, 1, param.game.item_text_outline_color));
|
return ResourceTexture(s.name, text2->writeDXToTexture(Text::STROKE, s.text, -4, Colors::NO_COLOR_MOD, 1, param.game.item_text_outline_color));
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crea los objetos de texto a partir de los archivos de textura y texto
|
// Crea los objetos de texto a partir de los archivos de textura y texto
|
||||||
@@ -786,7 +736,7 @@ void Resource::createText() {
|
|||||||
void Resource::clearSounds() {
|
void Resource::clearSounds() {
|
||||||
for (auto& sound : sounds_) {
|
for (auto& sound : sounds_) {
|
||||||
if (sound.sound != nullptr) {
|
if (sound.sound != nullptr) {
|
||||||
JA_DeleteSound(sound.sound);
|
Ja::deleteSound(sound.sound);
|
||||||
sound.sound = nullptr;
|
sound.sound = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -797,7 +747,7 @@ void Resource::clearSounds() {
|
|||||||
void Resource::clearMusics() {
|
void Resource::clearMusics() {
|
||||||
for (auto& music : musics_) {
|
for (auto& music : musics_) {
|
||||||
if (music.music != nullptr) {
|
if (music.music != nullptr) {
|
||||||
JA_DeleteMusic(music.music);
|
Ja::deleteMusic(music.music);
|
||||||
music.music = nullptr;
|
music.music = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -847,7 +797,7 @@ void Resource::renderProgress() {
|
|||||||
const bool WAITING_FOR_INPUT = isLoadDone() && Options::loading.wait_for_input;
|
const bool WAITING_FOR_INPUT = isLoadDone() && Options::loading.wait_for_input;
|
||||||
|
|
||||||
auto text_color = param.resource.color;
|
auto text_color = param.resource.color;
|
||||||
auto bar_color = param.resource.color.DARKEN(100);
|
auto bar_color = param.resource.color.darken(100);
|
||||||
const auto TEXT_HEIGHT = loading_text_->getCharacterSize();
|
const auto TEXT_HEIGHT = loading_text_->getCharacterSize();
|
||||||
|
|
||||||
// Dibuja el interior de la barra de progreso
|
// Dibuja el interior de la barra de progreso
|
||||||
@@ -897,12 +847,10 @@ void Resource::renderProgress() {
|
|||||||
|
|
||||||
// Carga los datos para el modo demostración (sin mostrar progreso)
|
// Carga los datos para el modo demostración (sin mostrar progreso)
|
||||||
void Resource::loadDemoDataQuiet() {
|
void Resource::loadDemoDataQuiet() {
|
||||||
auto list = Asset::get()->getListByType(Asset::Type::DEMODATA);
|
const auto LIST = Asset::get()->getListByType(Asset::Type::DEMODATA);
|
||||||
demos_.clear();
|
demos_.clear();
|
||||||
|
demos_.reserve(LIST.size());
|
||||||
for (const auto& l : list) {
|
std::ranges::transform(LIST, std::back_inserter(demos_), [](const auto& l) { return loadDemoDataFromFile(l); });
|
||||||
demos_.emplace_back(loadDemoDataFromFile(l));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inicializa los rectangulos que definen la barra de progreso
|
// Inicializa los rectangulos que definen la barra de progreso
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <SDL3/SDL.h> // Para SDL_FRect
|
#include <SDL3/SDL.h> // Para SDL_FRect
|
||||||
|
|
||||||
#include <cstddef> // Para size_t
|
#include <cstddef> // Para size_t
|
||||||
|
#include <cstdint> // Para std::uint8_t
|
||||||
#include <memory> // Para shared_ptr
|
#include <memory> // Para shared_ptr
|
||||||
#include <string> // Para string
|
#include <string> // Para string
|
||||||
#include <utility> // Para move
|
#include <utility> // Para move
|
||||||
@@ -13,14 +14,16 @@
|
|||||||
#include "core/rendering/texture.hpp" // Para Texture
|
#include "core/rendering/texture.hpp" // Para Texture
|
||||||
#include "core/system/demo.hpp" // Para DemoData
|
#include "core/system/demo.hpp" // Para DemoData
|
||||||
|
|
||||||
struct JA_Music_t;
|
namespace Ja {
|
||||||
struct JA_Sound_t;
|
struct Music;
|
||||||
|
struct Sound;
|
||||||
|
} // namespace Ja
|
||||||
|
|
||||||
// --- Clase Resource: gestiona todos los recursos del juego (singleton) ---
|
// --- Clase Resource: gestiona todos los recursos del juego (singleton) ---
|
||||||
class Resource {
|
class Resource {
|
||||||
public:
|
public:
|
||||||
// --- Enum para el modo de carga ---
|
// --- Enum para el modo de carga ---
|
||||||
enum class LoadingMode {
|
enum class LoadingMode : std::uint8_t {
|
||||||
PRELOAD, // Carga todos los recursos al inicio
|
PRELOAD, // Carga todos los recursos al inicio
|
||||||
LAZY_LOAD // Carga los recursos bajo demanda
|
LAZY_LOAD // Carga los recursos bajo demanda
|
||||||
};
|
};
|
||||||
@@ -31,8 +34,8 @@ class Resource {
|
|||||||
static auto get() -> Resource*; // Obtiene el puntero al objeto Resource
|
static auto get() -> Resource*; // Obtiene el puntero al objeto Resource
|
||||||
|
|
||||||
// --- Métodos de acceso a recursos ---
|
// --- Métodos de acceso a recursos ---
|
||||||
auto getSound(const std::string& name) -> JA_Sound_t*; // Obtiene el sonido por nombre
|
auto getSound(const std::string& name) -> Ja::Sound*; // Obtiene el sonido por nombre
|
||||||
auto getMusic(const std::string& name) -> JA_Music_t*; // Obtiene la música por nombre
|
auto getMusic(const std::string& name) -> Ja::Music*; // Obtiene la música por nombre
|
||||||
auto getTexture(const std::string& name) -> std::shared_ptr<Texture>; // Obtiene la textura por nombre
|
auto getTexture(const std::string& name) -> std::shared_ptr<Texture>; // Obtiene la textura por nombre
|
||||||
auto getTextFile(const std::string& name) -> std::shared_ptr<Text::File>; // Obtiene el fichero de texto por nombre
|
auto getTextFile(const std::string& name) -> std::shared_ptr<Text::File>; // Obtiene el fichero de texto por nombre
|
||||||
auto getText(const std::string& name) -> std::shared_ptr<Text>; // Obtiene el objeto de texto por nombre
|
auto getText(const std::string& name) -> std::shared_ptr<Text>; // Obtiene el objeto de texto por nombre
|
||||||
@@ -58,18 +61,18 @@ class Resource {
|
|||||||
// --- Estructuras para recursos individuales ---
|
// --- Estructuras para recursos individuales ---
|
||||||
struct ResourceSound {
|
struct ResourceSound {
|
||||||
std::string name; // Nombre del sonido
|
std::string name; // Nombre del sonido
|
||||||
JA_Sound_t* sound; // Objeto con el sonido
|
Ja::Sound* sound; // Objeto con el sonido
|
||||||
|
|
||||||
ResourceSound(std::string name, JA_Sound_t* sound = nullptr)
|
explicit ResourceSound(std::string name, Ja::Sound* sound = nullptr)
|
||||||
: name(std::move(name)),
|
: name(std::move(name)),
|
||||||
sound(sound) {}
|
sound(sound) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ResourceMusic {
|
struct ResourceMusic {
|
||||||
std::string name; // Nombre de la música
|
std::string name; // Nombre de la música
|
||||||
JA_Music_t* music; // Objeto con la música
|
Ja::Music* music; // Objeto con la música
|
||||||
|
|
||||||
ResourceMusic(std::string name, JA_Music_t* music = nullptr)
|
explicit ResourceMusic(std::string name, Ja::Music* music = nullptr)
|
||||||
: name(std::move(name)),
|
: name(std::move(name)),
|
||||||
music(music) {}
|
music(music) {}
|
||||||
};
|
};
|
||||||
@@ -78,7 +81,7 @@ class Resource {
|
|||||||
std::string name; // Nombre de la textura
|
std::string name; // Nombre de la textura
|
||||||
std::shared_ptr<Texture> texture; // Objeto con la textura
|
std::shared_ptr<Texture> texture; // Objeto con la textura
|
||||||
|
|
||||||
ResourceTexture(std::string name, std::shared_ptr<Texture> texture = nullptr)
|
explicit ResourceTexture(std::string name, std::shared_ptr<Texture> texture = nullptr)
|
||||||
: name(std::move(name)),
|
: name(std::move(name)),
|
||||||
texture(std::move(texture)) {}
|
texture(std::move(texture)) {}
|
||||||
};
|
};
|
||||||
@@ -87,7 +90,7 @@ class Resource {
|
|||||||
std::string name; // Nombre del fichero
|
std::string name; // Nombre del fichero
|
||||||
std::shared_ptr<Text::File> text_file; // Objeto con los descriptores de la fuente de texto
|
std::shared_ptr<Text::File> text_file; // Objeto con los descriptores de la fuente de texto
|
||||||
|
|
||||||
ResourceTextFile(std::string name, std::shared_ptr<Text::File> text_file = nullptr)
|
explicit ResourceTextFile(std::string name, std::shared_ptr<Text::File> text_file = nullptr)
|
||||||
: name(std::move(name)),
|
: name(std::move(name)),
|
||||||
text_file(std::move(text_file)) {}
|
text_file(std::move(text_file)) {}
|
||||||
};
|
};
|
||||||
@@ -96,7 +99,7 @@ class Resource {
|
|||||||
std::string name; // Nombre del objeto
|
std::string name; // Nombre del objeto
|
||||||
std::shared_ptr<Text> text; // Objeto de texto
|
std::shared_ptr<Text> text; // Objeto de texto
|
||||||
|
|
||||||
ResourceText(std::string name, std::shared_ptr<Text> text = nullptr)
|
explicit ResourceText(std::string name, std::shared_ptr<Text> text = nullptr)
|
||||||
: name(std::move(name)),
|
: name(std::move(name)),
|
||||||
text(std::move(text)) {}
|
text(std::move(text)) {}
|
||||||
};
|
};
|
||||||
@@ -105,7 +108,7 @@ class Resource {
|
|||||||
std::string name; // Nombre de la animación
|
std::string name; // Nombre de la animación
|
||||||
AnimationsFileBuffer animation; // Objeto con las animaciones
|
AnimationsFileBuffer animation; // Objeto con las animaciones
|
||||||
|
|
||||||
ResourceAnimation(std::string name, AnimationsFileBuffer animation = {})
|
explicit ResourceAnimation(std::string name, AnimationsFileBuffer animation = {})
|
||||||
: name(std::move(name)),
|
: name(std::move(name)),
|
||||||
animation(std::move(animation)) {}
|
animation(std::move(animation)) {}
|
||||||
};
|
};
|
||||||
@@ -116,7 +119,7 @@ class Resource {
|
|||||||
size_t loaded{0}; // Número de recursos cargados
|
size_t loaded{0}; // Número de recursos cargados
|
||||||
|
|
||||||
ResourceCount() = default;
|
ResourceCount() = default;
|
||||||
ResourceCount(size_t total)
|
explicit ResourceCount(size_t total)
|
||||||
: total(total) {}
|
: total(total) {}
|
||||||
|
|
||||||
void add(size_t amount) { loaded += amount; }
|
void add(size_t amount) { loaded += amount; }
|
||||||
@@ -156,7 +159,7 @@ class Resource {
|
|||||||
SDL_FRect loading_full_rect_;
|
SDL_FRect loading_full_rect_;
|
||||||
|
|
||||||
// --- Estado del cargador incremental ---
|
// --- Estado del cargador incremental ---
|
||||||
enum class LoadStage {
|
enum class LoadStage : std::uint8_t {
|
||||||
SOUNDS,
|
SOUNDS,
|
||||||
MUSICS,
|
MUSICS,
|
||||||
TEXTURES,
|
TEXTURES,
|
||||||
@@ -187,8 +190,8 @@ class Resource {
|
|||||||
|
|
||||||
// --- Métodos para carga perezosa ---
|
// --- Métodos para carga perezosa ---
|
||||||
void initResourceLists(); // Inicializa las listas de recursos sin cargar el contenido
|
void initResourceLists(); // Inicializa las listas de recursos sin cargar el contenido
|
||||||
static auto loadSoundLazy(const std::string& name) -> JA_Sound_t*; // Carga un sonido específico bajo demanda
|
static auto loadSoundLazy(const std::string& name) -> Ja::Sound*; // Carga un sonido específico bajo demanda
|
||||||
static auto loadMusicLazy(const std::string& name) -> JA_Music_t*; // Carga una música específica bajo demanda
|
static auto loadMusicLazy(const std::string& name) -> Ja::Music*; // Carga una música específica bajo demanda
|
||||||
static auto loadTextureLazy(const std::string& name) -> std::shared_ptr<Texture>; // Carga una textura específica bajo demanda
|
static auto loadTextureLazy(const std::string& name) -> std::shared_ptr<Texture>; // Carga una textura específica bajo demanda
|
||||||
static auto loadTextFileLazy(const std::string& name) -> std::shared_ptr<Text::File>; // Carga un fichero de texto específico bajo demanda
|
static auto loadTextFileLazy(const std::string& name) -> std::shared_ptr<Text::File>; // Carga un fichero de texto específico bajo demanda
|
||||||
auto loadTextLazy(const std::string& name) -> std::shared_ptr<Text>; // Carga un objeto de texto específico bajo demanda
|
auto loadTextLazy(const std::string& name) -> std::shared_ptr<Text>; // Carga un objeto de texto específico bajo demanda
|
||||||
@@ -200,6 +203,10 @@ class Resource {
|
|||||||
void initProgressBar(); // Inicializa los rectangulos que definen la barra de progreso
|
void initProgressBar(); // Inicializa los rectangulos que definen la barra de progreso
|
||||||
void updateProgressBar(); // Actualiza la barra de estado
|
void updateProgressBar(); // Actualiza la barra de estado
|
||||||
|
|
||||||
|
// Avança una etapa que descarrega una llista d'assets: si `stage_index_` desborda la mida,
|
||||||
|
// salta a `next_stage`; si no, crida `load_one` per a l'element actual i incrementa.
|
||||||
|
void advanceListLoadStage(const std::vector<std::string>& list, void (Resource::*load_one)(size_t), LoadStage next_stage);
|
||||||
|
|
||||||
// --- Helpers del cargador incremental (cargan un único recurso) ---
|
// --- Helpers del cargador incremental (cargan un único recurso) ---
|
||||||
void loadOneSound(size_t idx);
|
void loadOneSound(size_t idx);
|
||||||
void loadOneMusic(size_t idx);
|
void loadOneMusic(size_t idx);
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint> // Para uint8_t
|
#include <cstdint> // Para uint8_t
|
||||||
#include <filesystem> // Para remove, path
|
#include <string> // Para string
|
||||||
#include <fstream> // Para basic_ofstream, basic_ios, basic_ostream::write, ios, ofstream
|
#include <vector> // Para vector
|
||||||
#include <string> // Para string, basic_string, hash, operator+, to_string, __str_hash_base
|
|
||||||
#include <vector> // Para vector
|
|
||||||
|
|
||||||
// Helper functions para integrar ResourceLoader con el sistema existente
|
// Helper functions para integrar ResourceLoader con el sistema existente
|
||||||
namespace ResourceHelper {
|
namespace ResourceHelper {
|
||||||
@@ -22,24 +20,4 @@ namespace ResourceHelper {
|
|||||||
|
|
||||||
// Convierte ruta Asset a ruta relativa para ResourceLoader
|
// Convierte ruta Asset a ruta relativa para ResourceLoader
|
||||||
auto getPackPath(const std::string& asset_path) -> std::string;
|
auto getPackPath(const std::string& asset_path) -> std::string;
|
||||||
|
|
||||||
// Wrappea la carga de archivos para mantener compatibilidad
|
|
||||||
template <typename T>
|
|
||||||
auto loadResourceFile(const std::string& asset_path, T* (*loader_func)(const char*)) -> T* {
|
|
||||||
auto data = loadFile(asset_path);
|
|
||||||
if (data.empty()) {
|
|
||||||
return loader_func(asset_path.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Crear archivo temporal para funciones que esperan path
|
|
||||||
std::string temp_path = "/tmp/ccae_" + std::to_string(std::hash<std::string>{}(asset_path));
|
|
||||||
std::ofstream temp_file(temp_path, std::ios::binary);
|
|
||||||
temp_file.write(reinterpret_cast<const char*>(data.data()), data.size());
|
|
||||||
temp_file.close();
|
|
||||||
|
|
||||||
T* result = loader_func(temp_path.c_str());
|
|
||||||
std::filesystem::remove(temp_path);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
} // namespace ResourceHelper
|
} // namespace ResourceHelper
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
#include "core/resources/resource_pack.hpp"
|
#include "core/resources/resource_pack.hpp"
|
||||||
|
|
||||||
#include <algorithm> // Para replace
|
#include <algorithm> // Para replace, ranges::all_of
|
||||||
#include <array> // Para array
|
#include <array> // Para array
|
||||||
#include <filesystem> // Para path, recursive_directory_iterator, directory_entry, exists, relative
|
#include <filesystem> // Para path, recursive_directory_iterator, directory_entry, exists, relative
|
||||||
#include <fstream> // Para basic_ifstream, basic_ostream, basic_ofstream, operator<<, basic_ios, basic_istream::read, basic_ostream::write, endl, ios, basic_istream, ifstream, operator|, basic_istream::seekg, basic_istream::tellg, ofstream, streamsize
|
#include <fstream> // Para basic_ifstream, basic_ostream, basic_ofstream, operator<<, basic_ios, basic_istream::read, basic_ostream::write, endl, ios, basic_istream, ifstream, operator|, basic_istream::seekg, basic_istream::tellg, ofstream, streamsize
|
||||||
#include <iostream> // Para cerr
|
#include <iostream> // Para cerr
|
||||||
|
#include <numeric> // Para accumulate
|
||||||
#include <utility> // Para pair
|
#include <utility> // Para pair
|
||||||
|
|
||||||
const std::string ResourcePack::DEFAULT_ENCRYPT_KEY = "CCAE_RESOURCES__2024";
|
const std::string ResourcePack::DEFAULT_ENCRYPT_KEY = "CCAE_RESOURCES__2024";
|
||||||
@@ -17,11 +18,7 @@ ResourcePack::~ResourcePack() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto ResourcePack::calculateChecksum(const std::vector<uint8_t>& data) -> uint32_t {
|
auto ResourcePack::calculateChecksum(const std::vector<uint8_t>& data) -> uint32_t {
|
||||||
uint32_t checksum = 0x12345678;
|
return std::accumulate(data.begin(), data.end(), static_cast<uint32_t>(0x12345678), [](uint32_t checksum, unsigned char i) { return ((checksum << 5) + checksum) + i; });
|
||||||
for (unsigned char i : data) {
|
|
||||||
checksum = ((checksum << 5) + checksum) + i;
|
|
||||||
}
|
|
||||||
return checksum;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResourcePack::encryptData(std::vector<uint8_t>& data, const std::string& key) {
|
void ResourcePack::encryptData(std::vector<uint8_t>& data, const std::string& key) {
|
||||||
@@ -162,20 +159,16 @@ auto ResourcePack::addDirectory(const std::string& directory) -> bool {
|
|||||||
return false; // NOLINT(readability-simplify-boolean-expr)
|
return false; // NOLINT(readability-simplify-boolean-expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(directory)) {
|
return std::ranges::all_of(std::filesystem::recursive_directory_iterator(directory),
|
||||||
if (entry.is_regular_file()) {
|
[this, &directory](const auto& entry) {
|
||||||
|
if (!entry.is_regular_file()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
std::string filepath = entry.path().string();
|
std::string filepath = entry.path().string();
|
||||||
std::string filename = std::filesystem::relative(entry.path(), directory).string();
|
std::string filename = std::filesystem::relative(entry.path(), directory).string();
|
||||||
|
|
||||||
std::ranges::replace(filename, '\\', '/');
|
std::ranges::replace(filename, '\\', '/');
|
||||||
|
return addFile(filename, filepath);
|
||||||
if (!addFile(filename, filepath)) {
|
});
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ResourcePack::getResource(const std::string& filename) -> std::vector<uint8_t> {
|
auto ResourcePack::getResource(const std::string& filename) -> std::vector<uint8_t> {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
#include <stdexcept> // Para runtime_error
|
#include <stdexcept> // Para runtime_error
|
||||||
|
|
||||||
#include "core/resources/resource_helper.hpp" // Para ResourceHelper
|
#include "core/resources/resource_helper.hpp" // Para ResourceHelper
|
||||||
#include "utils/utils.hpp" // Para getFileName
|
|
||||||
|
|
||||||
// Carga el fichero de datos para la demo
|
// Carga el fichero de datos para la demo
|
||||||
auto loadDemoDataFromFile(const std::string& file_path) -> DemoData {
|
auto loadDemoDataFromFile(const std::string& file_path) -> DemoData {
|
||||||
|
|||||||
@@ -38,6 +38,38 @@
|
|||||||
#include "game/ui/service_menu.hpp" // Para ServiceMenu
|
#include "game/ui/service_menu.hpp" // Para ServiceMenu
|
||||||
#include "utils/param.hpp" // Para loadParamsFromFile
|
#include "utils/param.hpp" // Para loadParamsFromFile
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// Llig un camp opcional d'un YAML cap a `dest`. Si no existeix, no toca `dest`.
|
||||||
|
// Si existeix però el tipus no encaixa, deixa el valor per defecte i avisa per stderr
|
||||||
|
// (un debug.yaml mal escrit no ha de tombar l'arrencada, però l'usuari ha de saber-ho).
|
||||||
|
template <typename T>
|
||||||
|
void loadYamlField(const fkyaml::node& yaml, const std::string& key, T& dest) {
|
||||||
|
if (!yaml.contains(key)) { return; }
|
||||||
|
try {
|
||||||
|
dest = yaml[key].get_value<T>();
|
||||||
|
} catch (...) {
|
||||||
|
std::cerr << "debug.yaml: valor invàlid per a '" << key << "', es manté el valor per defecte\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parseInitialSection(const std::string& value) -> Section::Name {
|
||||||
|
if (value == "logo") { return Section::Name::LOGO; }
|
||||||
|
if (value == "intro") { return Section::Name::INTRO; }
|
||||||
|
if (value == "title") { return Section::Name::TITLE; }
|
||||||
|
if (value == "credits") { return Section::Name::CREDITS; }
|
||||||
|
if (value == "instructions") { return Section::Name::INSTRUCTIONS; }
|
||||||
|
if (value == "hiscore") { return Section::Name::HI_SCORE_TABLE; }
|
||||||
|
return Section::Name::GAME; // "game" i qualsevol valor desconegut
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parseInitialOptions(const std::string& value) -> Section::Options {
|
||||||
|
if (value == "none") { return Section::Options::NONE; }
|
||||||
|
if (value == "2p") { return Section::Options::GAME_PLAY_2P; }
|
||||||
|
if (value == "both") { return Section::Options::GAME_PLAY_BOTH; }
|
||||||
|
return Section::Options::GAME_PLAY_1P; // "1p" i qualsevol valor desconegut
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Director::Director() {
|
Director::Director() {
|
||||||
Section::attract_mode = Section::AttractMode::TITLE_TO_DEMO;
|
Section::attract_mode = Section::AttractMode::TITLE_TO_DEMO;
|
||||||
@@ -275,78 +307,20 @@ void Director::loadDebugConfig() {
|
|||||||
file.close();
|
file.close();
|
||||||
try {
|
try {
|
||||||
auto yaml = fkyaml::node::deserialize(content);
|
auto yaml = fkyaml::node::deserialize(content);
|
||||||
if (yaml.contains("initial_section")) {
|
loadYamlField(yaml, "initial_section", debug_config.initial_section);
|
||||||
try {
|
loadYamlField(yaml, "initial_options", debug_config.initial_options);
|
||||||
debug_config.initial_section = yaml["initial_section"].get_value<std::string>();
|
loadYamlField(yaml, "initial_stage", debug_config.initial_stage);
|
||||||
} catch (...) {}
|
loadYamlField(yaml, "show_render_info", debug_config.show_render_info);
|
||||||
}
|
loadYamlField(yaml, "resource_loading", debug_config.resource_loading);
|
||||||
if (yaml.contains("initial_options")) {
|
loadYamlField(yaml, "autoplay", debug_config.autoplay);
|
||||||
try {
|
loadYamlField(yaml, "invincibility", debug_config.invincibility);
|
||||||
debug_config.initial_options = yaml["initial_options"].get_value<std::string>();
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
if (yaml.contains("initial_stage")) {
|
|
||||||
try {
|
|
||||||
debug_config.initial_stage = yaml["initial_stage"].get_value<int>();
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
if (yaml.contains("show_render_info")) {
|
|
||||||
try {
|
|
||||||
debug_config.show_render_info = yaml["show_render_info"].get_value<bool>();
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
if (yaml.contains("resource_loading")) {
|
|
||||||
try {
|
|
||||||
debug_config.resource_loading = yaml["resource_loading"].get_value<std::string>();
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
if (yaml.contains("autoplay")) {
|
|
||||||
try {
|
|
||||||
debug_config.autoplay = yaml["autoplay"].get_value<bool>();
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
if (yaml.contains("invincibility")) {
|
|
||||||
try {
|
|
||||||
debug_config.invincibility = yaml["invincibility"].get_value<bool>();
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
std::cout << "Error parsing debug.yaml, using defaults" << '\n';
|
std::cout << "Error parsing debug.yaml, using defaults" << '\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mapear strings a enums
|
Section::name = parseInitialSection(debug_config.initial_section);
|
||||||
const auto& sec = debug_config.initial_section;
|
Section::options = parseInitialOptions(debug_config.initial_options);
|
||||||
if (sec == "logo") {
|
|
||||||
Section::name = Section::Name::LOGO;
|
|
||||||
} else if (sec == "intro") {
|
|
||||||
Section::name = Section::Name::INTRO;
|
|
||||||
} else if (sec == "title") {
|
|
||||||
Section::name = Section::Name::TITLE;
|
|
||||||
} else if (sec == "game") {
|
|
||||||
Section::name = Section::Name::GAME;
|
|
||||||
} else if (sec == "credits") {
|
|
||||||
Section::name = Section::Name::CREDITS;
|
|
||||||
} else if (sec == "instructions") {
|
|
||||||
Section::name = Section::Name::INSTRUCTIONS;
|
|
||||||
} else if (sec == "hiscore") {
|
|
||||||
Section::name = Section::Name::HI_SCORE_TABLE;
|
|
||||||
} else {
|
|
||||||
Section::name = Section::Name::GAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& opt = debug_config.initial_options;
|
|
||||||
if (opt == "none") {
|
|
||||||
Section::options = Section::Options::NONE;
|
|
||||||
} else if (opt == "1p") {
|
|
||||||
Section::options = Section::Options::GAME_PLAY_1P;
|
|
||||||
} else if (opt == "2p") {
|
|
||||||
Section::options = Section::Options::GAME_PLAY_2P;
|
|
||||||
} else if (opt == "both") {
|
|
||||||
Section::options = Section::Options::GAME_PLAY_BOTH;
|
|
||||||
} else {
|
|
||||||
Section::options = Section::Options::GAME_PLAY_1P;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crea la carpeta del sistema donde guardar datos
|
// Crea la carpeta del sistema donde guardar datos
|
||||||
@@ -499,7 +473,7 @@ auto Director::iterate() -> SDL_AppResult {
|
|||||||
|
|
||||||
// Ejecuta un frame de la sección activa
|
// Ejecuta un frame de la sección activa
|
||||||
if (preload_) {
|
if (preload_) {
|
||||||
preload_->iterate();
|
Preload::iterate();
|
||||||
} else if (logo_) {
|
} else if (logo_) {
|
||||||
logo_->iterate();
|
logo_->iterate();
|
||||||
} else if (intro_) {
|
} else if (intro_) {
|
||||||
@@ -520,13 +494,13 @@ auto Director::iterate() -> SDL_AppResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Procesa un evento SDL (llamado desde SDL_AppEvent)
|
// Procesa un evento SDL (llamado desde SDL_AppEvent)
|
||||||
auto Director::handleEvent(SDL_Event& event) -> SDL_AppResult {
|
auto Director::handleEvent(const SDL_Event& event) -> SDL_AppResult {
|
||||||
// Eventos globales (SDL_EVENT_QUIT, resize, render target reset, hotplug, service menu, ratón)
|
// Eventos globales (SDL_EVENT_QUIT, resize, render target reset, hotplug, service menu, ratón)
|
||||||
GlobalEvents::handle(event);
|
GlobalEvents::handle(event);
|
||||||
|
|
||||||
// Reenvía a la sección activa
|
// Reenvía a la sección activa
|
||||||
if (preload_) {
|
if (preload_) {
|
||||||
preload_->handleEvent(event);
|
Preload::handleEvent(event);
|
||||||
} else if (logo_) {
|
} else if (logo_) {
|
||||||
logo_->handleEvent(event);
|
logo_->handleEvent(event);
|
||||||
} else if (intro_) {
|
} else if (intro_) {
|
||||||
|
|||||||
@@ -7,8 +7,10 @@
|
|||||||
|
|
||||||
#include "core/system/section.hpp" // Para Section::Name
|
#include "core/system/section.hpp" // Para Section::Name
|
||||||
|
|
||||||
|
#include <cstdint> // Para std::uint8_t
|
||||||
|
|
||||||
namespace Lang {
|
namespace Lang {
|
||||||
enum class Code : int;
|
enum class Code : std::uint8_t;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Declaraciones adelantadas de las secciones
|
// Declaraciones adelantadas de las secciones
|
||||||
@@ -29,8 +31,8 @@ class Director {
|
|||||||
~Director();
|
~Director();
|
||||||
|
|
||||||
// --- Callbacks para SDL_MAIN_USE_CALLBACKS ---
|
// --- Callbacks para SDL_MAIN_USE_CALLBACKS ---
|
||||||
auto iterate() -> SDL_AppResult; // Avanza un frame de la sección activa
|
auto iterate() -> SDL_AppResult; // Avanza un frame de la sección activa
|
||||||
auto handleEvent(SDL_Event& event) -> SDL_AppResult; // Procesa un evento SDL
|
auto handleEvent(const SDL_Event& event) -> SDL_AppResult; // Procesa un evento SDL
|
||||||
|
|
||||||
// --- Debug config (accesible desde otras clases) ---
|
// --- Debug config (accesible desde otras clases) ---
|
||||||
struct DebugConfig {
|
struct DebugConfig {
|
||||||
@@ -70,7 +72,7 @@ class Director {
|
|||||||
|
|
||||||
// --- Inicialización y cierre del sistema ---
|
// --- Inicialización y cierre del sistema ---
|
||||||
void init(); // Inicializa la aplicación (pre-boot)
|
void init(); // Inicializa la aplicación (pre-boot)
|
||||||
void finishBoot(); // Post-boot: inicializa lo que depende de recursos cargados
|
static void finishBoot(); // Post-boot: inicializa lo que depende de recursos cargados
|
||||||
static void close(); // Cierra y libera recursos
|
static void close(); // Cierra y libera recursos
|
||||||
|
|
||||||
// --- Configuración inicial ---
|
// --- Configuración inicial ---
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Namespace section: define los estados/secciones principales del programa,
|
Namespace section: define los estados/secciones principales del programa,
|
||||||
así como las opciones y modos especiales (como el Attract Mode).
|
así como las opciones y modos especiales (como el Attract Mode).
|
||||||
@@ -8,7 +10,7 @@
|
|||||||
|
|
||||||
namespace Section {
|
namespace Section {
|
||||||
// --- Enumeraciones de secciones del programa ---
|
// --- Enumeraciones de secciones del programa ---
|
||||||
enum class Name {
|
enum class Name : std::uint8_t {
|
||||||
RESET, // Inicialización
|
RESET, // Inicialización
|
||||||
PRELOAD, // Carga incremental de recursos
|
PRELOAD, // Carga incremental de recursos
|
||||||
LOGO, // Pantalla de logo
|
LOGO, // Pantalla de logo
|
||||||
@@ -23,7 +25,7 @@ namespace Section {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// --- Opciones para la sección actual ---
|
// --- Opciones para la sección actual ---
|
||||||
enum class Options {
|
enum class Options : std::uint8_t {
|
||||||
GAME_PLAY_1P, // Iniciar el juego con el jugador 1
|
GAME_PLAY_1P, // Iniciar el juego con el jugador 1
|
||||||
GAME_PLAY_2P, // Iniciar el juego con el jugador 2
|
GAME_PLAY_2P, // Iniciar el juego con el jugador 2
|
||||||
GAME_PLAY_BOTH, // Iniciar el juego con los dos jugadores
|
GAME_PLAY_BOTH, // Iniciar el juego con los dos jugadores
|
||||||
@@ -37,7 +39,7 @@ namespace Section {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// --- Modos para el Attract Mode ---
|
// --- Modos para el Attract Mode ---
|
||||||
enum class AttractMode {
|
enum class AttractMode : std::uint8_t {
|
||||||
TITLE_TO_DEMO, // Pasar de título a demo
|
TITLE_TO_DEMO, // Pasar de título a demo
|
||||||
TITLE_TO_LOGO, // Pasar de título a logo
|
TITLE_TO_LOGO, // Pasar de título a logo
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
// --- Namespace SystemShutdown: utilidad multiplataforma para apagar el sistema de forma segura ---
|
// --- Namespace SystemShutdown: utilidad multiplataforma para apagar el sistema de forma segura ---
|
||||||
namespace SystemShutdown {
|
namespace SystemShutdown {
|
||||||
|
|
||||||
// --- Enums ---
|
// --- Enums ---
|
||||||
enum class ShutdownResult {
|
enum class ShutdownResult : std::uint8_t {
|
||||||
SUCCESS = 0, // Éxito
|
SUCCESS = 0, // Éxito
|
||||||
ERROR_PERMISSION, // Error de permisos insuficientes
|
ERROR_PERMISSION, // Error de permisos insuficientes
|
||||||
ERROR_SYSTEM_CALL, // Error en la llamada al sistema
|
ERROR_SYSTEM_CALL, // Error en la llamada al sistema
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
// --- Namespace SystemUtils: utilidades multiplataforma para operaciones del sistema ---
|
// --- Namespace SystemUtils: utilidades multiplataforma para operaciones del sistema ---
|
||||||
namespace SystemUtils {
|
namespace SystemUtils {
|
||||||
// --- Enums ---
|
// --- Enums ---
|
||||||
enum class Result { // Códigos de resultado para operaciones del sistema
|
enum class Result : std::uint8_t { // Códigos de resultado para operaciones del sistema
|
||||||
SUCCESS = 0,
|
SUCCESS = 0,
|
||||||
PERMISSION_DENIED, // Sin permisos para crear la carpeta
|
PERMISSION_DENIED, // Sin permisos para crear la carpeta
|
||||||
PATH_TOO_LONG, // Ruta demasiado larga
|
PATH_TOO_LONG, // Ruta demasiado larga
|
||||||
|
|||||||
Vendored
+5584
File diff suppressed because it is too large
Load Diff
Vendored
-5631
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@
|
|||||||
#include "core/audio/audio.hpp" // Para Audio
|
#include "core/audio/audio.hpp" // Para Audio
|
||||||
#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite
|
#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite
|
||||||
#include "core/rendering/sprite/sprite.hpp" // Para Sprite
|
#include "core/rendering/sprite/sprite.hpp" // Para Sprite
|
||||||
#include "core/rendering/texture.hpp" // Para Texture
|
#include "core/rendering/texture.hpp" // IWYU pragma: keep
|
||||||
#include "utils/param.hpp" // Para Param, ParamBalloon, param
|
#include "utils/param.hpp" // Para Param, ParamBalloon, param
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class Balloon {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// --- Constructores y destructor ---
|
// --- Constructores y destructor ---
|
||||||
Balloon(const Config& config);
|
explicit Balloon(const Config& config);
|
||||||
~Balloon() = default;
|
~Balloon() = default;
|
||||||
|
|
||||||
// --- Métodos principales ---
|
// --- Métodos principales ---
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h> // Para SDL_FRect, Uint16
|
#include <SDL3/SDL.h> // Para SDL_FRect, Uint16
|
||||||
|
|
||||||
#include <memory> // Para shared_ptr, unique_ptr
|
#include <cstdint> // Para std::uint8_t
|
||||||
#include <string> // Para string
|
#include <memory> // Para shared_ptr, unique_ptr
|
||||||
#include <vector> // Para vector
|
#include <string> // Para string
|
||||||
|
#include <vector> // Para vector
|
||||||
|
|
||||||
#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite
|
#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite
|
||||||
#include "utils/utils.hpp" // Para Circle
|
#include "utils/utils.hpp" // Para Circle
|
||||||
@@ -12,7 +13,7 @@
|
|||||||
class Texture;
|
class Texture;
|
||||||
|
|
||||||
// --- Enums ---
|
// --- Enums ---
|
||||||
enum class ItemType : int {
|
enum class ItemType : std::uint8_t {
|
||||||
DISK = 1, // Disco
|
DISK = 1, // Disco
|
||||||
GAVINA = 2, // Gavina
|
GAVINA = 2, // Gavina
|
||||||
PACMAR = 3, // Pacman
|
PACMAR = 3, // Pacman
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
#include "core/audio/audio.hpp" // Para Audio
|
#include "core/audio/audio.hpp" // Para Audio
|
||||||
#include "core/input/input.hpp" // Para Input
|
#include "core/input/input.hpp" // Para Input
|
||||||
#include "core/input/input_types.hpp" // Para InputAction
|
|
||||||
#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite
|
#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite
|
||||||
#include "core/rendering/texture.hpp" // Para Texture
|
#include "core/rendering/texture.hpp" // Para Texture
|
||||||
#include "core/resources/asset.hpp" // Para Asset
|
#include "core/resources/asset.hpp" // Para Asset
|
||||||
@@ -977,7 +976,7 @@ void Player::playSound(const std::string& name) const {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static auto* audio_ = Audio::get();
|
static const auto* audio_ = Audio::get();
|
||||||
audio_->playSound(name);
|
audio_->playSound(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1135,8 +1134,6 @@ void Player::updateFiringStateFromVisual() {
|
|||||||
firing_state_ = State::RECOILING_RIGHT;
|
firing_state_ = State::RECOILING_RIGHT;
|
||||||
break;
|
break;
|
||||||
case State::FIRING_UP:
|
case State::FIRING_UP:
|
||||||
firing_state_ = State::RECOILING_UP;
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
firing_state_ = State::RECOILING_UP;
|
firing_state_ = State::RECOILING_UP;
|
||||||
break;
|
break;
|
||||||
@@ -1152,8 +1149,6 @@ void Player::updateFiringStateFromVisual() {
|
|||||||
firing_state_ = State::COOLING_RIGHT;
|
firing_state_ = State::COOLING_RIGHT;
|
||||||
break;
|
break;
|
||||||
case State::FIRING_UP:
|
case State::FIRING_UP:
|
||||||
firing_state_ = State::COOLING_UP;
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
firing_state_ = State::COOLING_UP;
|
firing_state_ = State::COOLING_UP;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h> // Para SDL_FRect, SDL_FlipMode
|
#include <SDL3/SDL.h> // Para SDL_FRect, SDL_FlipMode
|
||||||
|
|
||||||
#include <cstddef> // Para size_t
|
#include <cstddef> // Para size_t
|
||||||
#include <iterator> // Para pair
|
#include <cstdint> // Para std::uint8_t, std::int8_t
|
||||||
#include <memory> // Para shared_ptr, unique_ptr
|
#include <memory> // Para shared_ptr, unique_ptr
|
||||||
#include <string> // Para basic_string, string
|
#include <string> // Para basic_string, string
|
||||||
#include <utility> // Para move, pair
|
#include <utility> // Para move, pair
|
||||||
#include <vector> // Para vector
|
#include <vector> // Para vector
|
||||||
|
|
||||||
#include "core/input/input.hpp" // for Input
|
#include "core/input/input.hpp" // for Input
|
||||||
#include "core/rendering/sprite/animated_sprite.hpp" // for AnimatedSprite
|
#include "core/rendering/sprite/animated_sprite.hpp" // for AnimatedSprite
|
||||||
@@ -49,14 +49,14 @@ class Player {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// --- Enums ---
|
// --- Enums ---
|
||||||
enum class Id : int {
|
enum class Id : std::int8_t {
|
||||||
NO_PLAYER = -1, // Sin jugador
|
NO_PLAYER = -1, // Sin jugador
|
||||||
BOTH_PLAYERS = 0, // Ambos jugadores
|
BOTH_PLAYERS = 0, // Ambos jugadores
|
||||||
PLAYER1 = 1, // Jugador 1
|
PLAYER1 = 1, // Jugador 1
|
||||||
PLAYER2 = 2 // Jugador 2
|
PLAYER2 = 2 // Jugador 2
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class State {
|
enum class State : std::uint8_t {
|
||||||
// Estados de movimiento
|
// Estados de movimiento
|
||||||
WALKING_LEFT, // Caminando hacia la izquierda
|
WALKING_LEFT, // Caminando hacia la izquierda
|
||||||
WALKING_RIGHT, // Caminando hacia la derecha
|
WALKING_RIGHT, // Caminando hacia la derecha
|
||||||
@@ -114,7 +114,7 @@ class Player {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// --- Constructor y destructor ---
|
// --- Constructor y destructor ---
|
||||||
Player(const Config& config);
|
explicit Player(const Config& config);
|
||||||
~Player() = default;
|
~Player() = default;
|
||||||
|
|
||||||
// --- Inicialización y ciclo de vida ---
|
// --- Inicialización y ciclo de vida ---
|
||||||
@@ -201,16 +201,16 @@ class Player {
|
|||||||
[[nodiscard]] auto getCoffees() const -> int { return coffees_; }
|
[[nodiscard]] auto getCoffees() const -> int { return coffees_; }
|
||||||
[[nodiscard]] auto getPowerUpCounter() const -> int { return power_up_counter_; }
|
[[nodiscard]] auto getPowerUpCounter() const -> int { return power_up_counter_; }
|
||||||
[[nodiscard]] auto getInvulnerableCounter() const -> int { return invulnerable_counter_; }
|
[[nodiscard]] auto getInvulnerableCounter() const -> int { return invulnerable_counter_; }
|
||||||
[[nodiscard]] auto getBulletColor() const -> Bullet::Color; // Devuelve el color actual de bala según el estado
|
[[nodiscard]] auto getBulletColor() const -> Bullet::Color; // Devuelve el color actual de bala según el estado
|
||||||
auto getNextBulletColor() -> Bullet::Color; // Devuelve el color para la próxima bala (alterna si está en modo toggle)
|
auto getNextBulletColor() -> Bullet::Color; // Devuelve el color para la próxima bala (alterna si está en modo toggle)
|
||||||
void setBulletColors(Bullet::Color normal, Bullet::Color powered); // Establece los colores de bala para este jugador
|
void setBulletColors(Bullet::Color normal, Bullet::Color powered); // Establece los colores de bala para este jugador
|
||||||
[[nodiscard]] auto getBulletSoundFile() const -> std::string { return bullet_sound_file_; } // Devuelve el archivo de sonido de bala
|
[[nodiscard]] auto getBulletSoundFile() const -> const std::string& { return bullet_sound_file_; } // Devuelve el archivo de sonido de bala
|
||||||
void setBulletSoundFile(const std::string& filename); // Establece el archivo de sonido de bala para este jugador
|
void setBulletSoundFile(const std::string& filename); // Establece el archivo de sonido de bala para este jugador
|
||||||
|
|
||||||
// Contadores y timers
|
// Contadores y timers
|
||||||
[[nodiscard]] auto getContinueCounter() const -> int { return continue_counter_; }
|
[[nodiscard]] auto getContinueCounter() const -> int { return continue_counter_; }
|
||||||
[[nodiscard]] auto getRecordName() const -> std::string { return enter_name_ ? enter_name_->getFinalName() : "xxx"; }
|
[[nodiscard]] auto getRecordName() const -> std::string { return enter_name_ ? enter_name_->getFinalName() : "xxx"; }
|
||||||
[[nodiscard]] auto getLastEnterName() const -> std::string { return last_enter_name_; }
|
[[nodiscard]] auto getLastEnterName() const -> const std::string& { return last_enter_name_; }
|
||||||
|
|
||||||
// --- Configuración e interfaz externa ---
|
// --- Configuración e interfaz externa ---
|
||||||
void setName(const std::string& name) { name_ = name; }
|
void setName(const std::string& name) { name_ = name; }
|
||||||
@@ -293,7 +293,7 @@ class Player {
|
|||||||
bool can_fire_new_system_ = true; // true si puede disparar ahora mismo
|
bool can_fire_new_system_ = true; // true si puede disparar ahora mismo
|
||||||
|
|
||||||
// LÍNEA 2: SISTEMA VISUAL (Animaciones)
|
// LÍNEA 2: SISTEMA VISUAL (Animaciones)
|
||||||
enum class VisualFireState {
|
enum class VisualFireState : std::uint8_t {
|
||||||
NORMAL, // Brazo en posición neutral
|
NORMAL, // Brazo en posición neutral
|
||||||
AIMING, // Brazo alzado (disparando)
|
AIMING, // Brazo alzado (disparando)
|
||||||
RECOILING, // Brazo en retroceso
|
RECOILING, // Brazo en retroceso
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h> // Para Uint32, SDL_GetTicks, SDL_FRect
|
#include <SDL3/SDL.h> // Para Uint32, SDL_GetTicks, SDL_FRect
|
||||||
|
|
||||||
|
#include <cstdint> // Para std::uint8_t
|
||||||
#include <cstdlib> // Para rand
|
#include <cstdlib> // Para rand
|
||||||
#include <memory> // Para unique_ptr
|
#include <memory> // Para unique_ptr
|
||||||
|
|
||||||
@@ -11,12 +12,12 @@
|
|||||||
class Tabe {
|
class Tabe {
|
||||||
public:
|
public:
|
||||||
// --- Enumeraciones para dirección y estado ---
|
// --- Enumeraciones para dirección y estado ---
|
||||||
enum class Direction : int {
|
enum class Direction : std::uint8_t {
|
||||||
TO_THE_LEFT = 0,
|
TO_THE_LEFT = 0,
|
||||||
TO_THE_RIGHT = 1,
|
TO_THE_RIGHT = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class State : int {
|
enum class State : std::uint8_t {
|
||||||
FLY = 0,
|
FLY = 0,
|
||||||
HIT = 1,
|
HIT = 1,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -201,6 +201,8 @@ auto BalloonFormations::evaluateSimpleExpression(const std::string& expr, const
|
|||||||
return left_val * right_val;
|
return left_val * right_val;
|
||||||
case '/':
|
case '/':
|
||||||
return right_val != 0 ? left_val / right_val : 0;
|
return right_val != 0 ? left_val / right_val : 0;
|
||||||
|
default:
|
||||||
|
break; // Inalcanzable: el if exterior solo deja pasar '+', '-', '*', '/'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -222,13 +224,13 @@ void BalloonFormations::createFloaterVariants() {
|
|||||||
formations_.resize(100);
|
formations_.resize(100);
|
||||||
|
|
||||||
// Crear variantes flotantes de las primeras 50 formaciones
|
// Crear variantes flotantes de las primeras 50 formaciones
|
||||||
for (size_t k = 0; k < 50 && k < formations_.size(); k++) {
|
for (size_t k = 0; k < 50; k++) {
|
||||||
|
const auto& source = formations_.at(k).balloons;
|
||||||
std::vector<SpawnParams> floater_params;
|
std::vector<SpawnParams> floater_params;
|
||||||
floater_params.reserve(formations_.at(k).balloons.size());
|
floater_params.reserve(source.size());
|
||||||
|
std::ranges::transform(source, std::back_inserter(floater_params), [](const auto& original) {
|
||||||
for (const auto& original : formations_.at(k).balloons) {
|
return SpawnParams{original.x, original.y, original.vel_x, Balloon::Type::FLOATER, original.size, original.creation_counter};
|
||||||
floater_params.emplace_back(original.x, original.y, original.vel_x, Balloon::Type::FLOATER, original.size, original.creation_counter);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
formations_.at(k + 50) = Formation(floater_params);
|
formations_.at(k + 50) = Formation(floater_params);
|
||||||
}
|
}
|
||||||
@@ -395,24 +397,21 @@ void BalloonFormations::loadDefaultPools() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pool 2: Mix de formaciones normales y floaters (50+)
|
// Pools 2 i 3: requereixen formacions de floaters (>50)
|
||||||
if (total_formations > 50) {
|
if (total_formations > 50) {
|
||||||
|
// Pool 2: Mix de formacions normals i floaters
|
||||||
Pool pool2;
|
Pool pool2;
|
||||||
// Agregar algunas formaciones básicas
|
|
||||||
for (size_t i = 0; i < std::min(static_cast<size_t>(5), total_formations); ++i) {
|
for (size_t i = 0; i < std::min(static_cast<size_t>(5), total_formations); ++i) {
|
||||||
pool2.push_back(static_cast<int>(i));
|
pool2.push_back(static_cast<int>(i));
|
||||||
}
|
}
|
||||||
// Agregar algunas floaters si existen
|
|
||||||
for (size_t i = 50; i < std::min(static_cast<size_t>(55), total_formations); ++i) {
|
for (size_t i = 50; i < std::min(static_cast<size_t>(55), total_formations); ++i) {
|
||||||
pool2.push_back(static_cast<int>(i));
|
pool2.push_back(static_cast<int>(i));
|
||||||
}
|
}
|
||||||
if (!pool2.empty()) {
|
if (!pool2.empty()) {
|
||||||
pools_.push_back(pool2);
|
pools_.push_back(pool2);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Pool 3: Solo floaters (si existen formaciones 50+)
|
// Pool 3: Només floaters
|
||||||
if (total_formations > 50) {
|
|
||||||
Pool pool3;
|
Pool pool3;
|
||||||
for (size_t i = 50; i < std::min(static_cast<size_t>(70), total_formations); ++i) {
|
for (size_t i = 50; i < std::min(static_cast<size_t>(70), total_formations); ++i) {
|
||||||
pool3.push_back(static_cast<int>(i));
|
pool3.push_back(static_cast<int>(i));
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef> // Para size_t
|
#include <cstddef> // Para size_t
|
||||||
#include <iterator> // Para pair
|
|
||||||
#include <map> // Para map
|
#include <map> // Para map
|
||||||
#include <optional> // Para optional
|
#include <optional> // Para optional
|
||||||
#include <string> // Para string
|
#include <string> // Para string
|
||||||
@@ -39,7 +38,7 @@ class BalloonFormations {
|
|||||||
std::vector<SpawnParams> balloons; // Vector con todas las inicializaciones de los globos de la formación
|
std::vector<SpawnParams> balloons; // Vector con todas las inicializaciones de los globos de la formación
|
||||||
|
|
||||||
// Constructor con parámetros
|
// Constructor con parámetros
|
||||||
Formation(const std::vector<SpawnParams>& spawn_params)
|
explicit Formation(const std::vector<SpawnParams>& spawn_params)
|
||||||
: balloons(spawn_params) {}
|
: balloons(spawn_params) {}
|
||||||
|
|
||||||
// Constructor por defecto
|
// Constructor por defecto
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ void BalloonManager::deployRandomFormation(int stage) {
|
|||||||
|
|
||||||
// Crea los globos de la formación
|
// Crea los globos de la formación
|
||||||
const auto BALLOONS = balloon_formations_->getFormationFromPool(stage, formation_id).balloons;
|
const auto BALLOONS = balloon_formations_->getFormationFromPool(stage, formation_id).balloons;
|
||||||
for (auto balloon : BALLOONS) {
|
for (const auto& balloon : BALLOONS) {
|
||||||
Balloon::Config config = {
|
Balloon::Config config = {
|
||||||
.x = balloon.x,
|
.x = balloon.x,
|
||||||
.y = balloon.y,
|
.y = balloon.y,
|
||||||
@@ -127,7 +127,7 @@ void BalloonManager::deployRandomFormation(int stage) {
|
|||||||
// Crea una formación de globos específica
|
// Crea una formación de globos específica
|
||||||
void BalloonManager::deployFormation(int formation_id) {
|
void BalloonManager::deployFormation(int formation_id) {
|
||||||
const auto BALLOONS = balloon_formations_->getFormation(formation_id).balloons;
|
const auto BALLOONS = balloon_formations_->getFormation(formation_id).balloons;
|
||||||
for (auto balloon : BALLOONS) {
|
for (const auto& balloon : BALLOONS) {
|
||||||
Balloon::Config config = {
|
Balloon::Config config = {
|
||||||
.x = balloon.x,
|
.x = balloon.x,
|
||||||
.y = balloon.y,
|
.y = balloon.y,
|
||||||
@@ -143,7 +143,7 @@ void BalloonManager::deployFormation(int formation_id) {
|
|||||||
// Crea una formación de globos específica a una altura determinada
|
// Crea una formación de globos específica a una altura determinada
|
||||||
void BalloonManager::deployFormation(int formation_id, float y) {
|
void BalloonManager::deployFormation(int formation_id, float y) {
|
||||||
const auto BALLOONS = balloon_formations_->getFormation(formation_id).balloons;
|
const auto BALLOONS = balloon_formations_->getFormation(formation_id).balloons;
|
||||||
for (auto balloon : BALLOONS) {
|
for (const auto& balloon : BALLOONS) {
|
||||||
Balloon::Config config = {
|
Balloon::Config config = {
|
||||||
.x = balloon.x,
|
.x = balloon.x,
|
||||||
.y = y,
|
.y = y,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ using Balloons = std::list<std::shared_ptr<Balloon>>;
|
|||||||
class BalloonManager {
|
class BalloonManager {
|
||||||
public:
|
public:
|
||||||
// --- Constructor y destructor ---
|
// --- Constructor y destructor ---
|
||||||
BalloonManager(IStageInfo* stage_info);
|
explicit BalloonManager(IStageInfo* stage_info);
|
||||||
~BalloonManager() = default;
|
~BalloonManager() = default;
|
||||||
|
|
||||||
// --- Métodos principales ---
|
// --- Métodos principales ---
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#include "game/gameplay/bullet_manager.hpp"
|
#include "game/gameplay/bullet_manager.hpp"
|
||||||
|
|
||||||
#include <algorithm> // Para remove_if
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "game/entities/bullet.hpp" // Para Bullet
|
#include "game/entities/bullet.hpp" // Para Bullet
|
||||||
@@ -14,7 +13,7 @@ BulletManager::BulletManager()
|
|||||||
|
|
||||||
// Actualiza el estado de todas las balas
|
// Actualiza el estado de todas las balas
|
||||||
void BulletManager::update(float delta_time) {
|
void BulletManager::update(float delta_time) {
|
||||||
for (auto& bullet : bullets_) {
|
for (const auto& bullet : bullets_) {
|
||||||
if (bullet->isEnabled()) {
|
if (bullet->isEnabled()) {
|
||||||
processBulletUpdate(bullet, delta_time);
|
processBulletUpdate(bullet, delta_time);
|
||||||
}
|
}
|
||||||
@@ -23,7 +22,7 @@ void BulletManager::update(float delta_time) {
|
|||||||
|
|
||||||
// Renderiza todas las balas activas
|
// Renderiza todas las balas activas
|
||||||
void BulletManager::render() {
|
void BulletManager::render() {
|
||||||
for (auto& bullet : bullets_) {
|
for (const auto& bullet : bullets_) {
|
||||||
if (bullet->isEnabled()) {
|
if (bullet->isEnabled()) {
|
||||||
bullet->render();
|
bullet->render();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
#include <functional> // Para function
|
#include <functional> // Para function
|
||||||
#include <list> // Para list
|
#include <list> // Para list
|
||||||
#include <memory> // Para shared_ptr
|
#include <memory> // Para shared_ptr
|
||||||
#include <vector> // Para vector
|
|
||||||
|
|
||||||
#include "game/entities/bullet.hpp" // for Bullet
|
#include "game/entities/bullet.hpp" // for Bullet
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
class Cooldown {
|
class Cooldown {
|
||||||
public:
|
public:
|
||||||
Cooldown(float first_delay_s = 0.0F, float repeat_delay_s = 0.0F)
|
explicit Cooldown(float first_delay_s = 0.0F, float repeat_delay_s = 0.0F)
|
||||||
: first_delay_s_(first_delay_s),
|
: first_delay_s_(first_delay_s),
|
||||||
repeat_delay_s_(repeat_delay_s) {}
|
repeat_delay_s_(repeat_delay_s) {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "game/gameplay/difficulty.hpp"
|
#include "game/gameplay/difficulty.hpp"
|
||||||
|
|
||||||
#include <vector> // Para vector
|
#include <algorithm> // Para ranges::find_if
|
||||||
|
#include <vector> // Para vector
|
||||||
|
|
||||||
namespace Difficulty {
|
namespace Difficulty {
|
||||||
|
|
||||||
@@ -18,19 +19,19 @@ namespace Difficulty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto getNameFromCode(Code code) -> std::string {
|
auto getNameFromCode(Code code) -> std::string {
|
||||||
for (const auto& difficulty : difficulties_list) {
|
const auto IT = std::ranges::find_if(difficulties_list,
|
||||||
if (difficulty.code == code) {
|
[code](const auto& difficulty) { return difficulty.code == code; });
|
||||||
return difficulty.name;
|
if (IT != difficulties_list.end()) {
|
||||||
}
|
return IT->name;
|
||||||
}
|
}
|
||||||
return !difficulties_list.empty() ? difficulties_list.front().name : "Unknown";
|
return !difficulties_list.empty() ? difficulties_list.front().name : "Unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
auto getCodeFromName(const std::string& name) -> Code {
|
auto getCodeFromName(const std::string& name) -> Code {
|
||||||
for (const auto& difficulty : difficulties_list) {
|
const auto IT = std::ranges::find_if(difficulties_list,
|
||||||
if (difficulty.name == name) {
|
[&name](const auto& difficulty) { return difficulty.name == name; });
|
||||||
return difficulty.code;
|
if (IT != difficulties_list.end()) {
|
||||||
}
|
return IT->code;
|
||||||
}
|
}
|
||||||
return !difficulties_list.empty() ? difficulties_list.front().code : Code::NORMAL;
|
return !difficulties_list.empty() ? difficulties_list.front().code : Code::NORMAL;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string> // Para string
|
#include <cstdint> // Para std::uint8_t
|
||||||
#include <vector> // Para vector
|
#include <string> // Para string
|
||||||
|
#include <vector> // Para vector
|
||||||
|
|
||||||
namespace Difficulty {
|
namespace Difficulty {
|
||||||
|
|
||||||
// --- Enums ---
|
// --- Enums ---
|
||||||
enum class Code {
|
enum class Code : std::uint8_t {
|
||||||
EASY = 0, // Dificultad fácil
|
EASY = 0, // Dificultad fácil
|
||||||
NORMAL = 1, // Dificultad normal
|
NORMAL = 1, // Dificultad normal
|
||||||
HARD = 2, // Dificultad difícil
|
HARD = 2, // Dificultad difícil
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class EnterName {
|
|||||||
void removeLastCharacter(); // Elimina el último carácter del nombre
|
void removeLastCharacter(); // Elimina el último carácter del nombre
|
||||||
|
|
||||||
auto getFinalName() -> std::string; // Obtiene el nombre final (o aleatorio si vacío)
|
auto getFinalName() -> std::string; // Obtiene el nombre final (o aleatorio si vacío)
|
||||||
[[nodiscard]] auto getCurrentName() const -> std::string { return name_; } // Obtiene el nombre actual en proceso
|
[[nodiscard]] auto getCurrentName() const -> const std::string& { return name_; } // Obtiene el nombre actual en proceso
|
||||||
[[nodiscard]] auto getSelectedCharacter(int offset = 0) const -> std::string; // Devuelve el carácter seleccionado con offset relativo
|
[[nodiscard]] auto getSelectedCharacter(int offset = 0) const -> std::string; // Devuelve el carácter seleccionado con offset relativo
|
||||||
[[nodiscard]] auto getCarousel(int size) const -> std::string; // Devuelve el carrusel de caracteres (size debe ser impar)
|
[[nodiscard]] auto getCarousel(int size) const -> std::string; // Devuelve el carrusel de caracteres (size debe ser impar)
|
||||||
[[nodiscard]] auto getSelectedIndex() const -> int { return selected_index_; } // Obtiene el índice del carácter seleccionado
|
[[nodiscard]] auto getSelectedIndex() const -> int { return selected_index_; } // Obtiene el índice del carácter seleccionado
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory> // Para unique_ptr, shared_ptr
|
#include <cstdint> // Para std::uint8_t
|
||||||
|
#include <memory> // Para unique_ptr, shared_ptr
|
||||||
|
|
||||||
#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite
|
#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite
|
||||||
#include "core/rendering/sprite/smart_sprite.hpp" // Para SmartSprite
|
#include "core/rendering/sprite/smart_sprite.hpp" // Para SmartSprite
|
||||||
@@ -36,7 +37,7 @@ class GameLogo {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
// --- Enums ---
|
// --- Enums ---
|
||||||
enum class Status {
|
enum class Status : std::uint8_t {
|
||||||
DISABLED, // Deshabilitado
|
DISABLED, // Deshabilitado
|
||||||
MOVING, // En movimiento
|
MOVING, // En movimiento
|
||||||
SHAKING, // Temblando
|
SHAKING, // Temblando
|
||||||
|
|||||||
@@ -2,14 +2,13 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h> // Para SDL_ReadIO, SDL_WriteIO, SDL_CloseIO, SDL_GetError, SDL_IOFromFile, SDL_LogError, SDL_LogCategory, SDL_LogInfo
|
#include <SDL3/SDL.h> // Para SDL_ReadIO, SDL_WriteIO, SDL_CloseIO, SDL_GetError, SDL_IOFromFile, SDL_LogError, SDL_LogCategory, SDL_LogInfo
|
||||||
|
|
||||||
#include <algorithm> // Para __sort_fn, sort
|
#include <algorithm> // Para sort, ranges::find_if, ranges::sort
|
||||||
#include <array> // Para array
|
#include <array> // Para array
|
||||||
#include <functional> // Para identity
|
#include <iomanip> // Para std::setw, std::setfill
|
||||||
#include <iomanip> // Para std::setw, std::setfill
|
#include <iostream> // Para std::cout
|
||||||
#include <iostream> // Para std::cout
|
#include <iterator> // Para distance
|
||||||
#include <iterator> // Para distance
|
#include <numeric> // Para accumulate
|
||||||
#include <ranges> // Para __find_if_fn, find_if
|
#include <utility> // Para move
|
||||||
#include <utility> // Para move
|
|
||||||
|
|
||||||
#include "utils/utils.hpp" // Para getFileName
|
#include "utils/utils.hpp" // Para getFileName
|
||||||
|
|
||||||
@@ -260,22 +259,11 @@ auto ManageHiScoreTable::verifyChecksum(SDL_IOStream* file, const std::string& f
|
|||||||
|
|
||||||
// Calcula checksum de la tabla
|
// Calcula checksum de la tabla
|
||||||
auto ManageHiScoreTable::calculateChecksum(const Table& table) -> unsigned int {
|
auto ManageHiScoreTable::calculateChecksum(const Table& table) -> unsigned int {
|
||||||
unsigned int checksum = 0x12345678; // Magic seed
|
return std::accumulate(table.begin(), table.end(), 0x12345678U, [](unsigned int checksum, const auto& entry) {
|
||||||
|
|
||||||
for (const auto& entry : table) {
|
|
||||||
// Checksum del score
|
|
||||||
checksum = ((checksum << 5) + checksum) + static_cast<unsigned int>(entry.score);
|
checksum = ((checksum << 5) + checksum) + static_cast<unsigned int>(entry.score);
|
||||||
|
checksum = std::accumulate(entry.name.begin(), entry.name.end(), checksum, [](unsigned int acc, char c) { return ((acc << 5) + acc) + static_cast<unsigned int>(c); });
|
||||||
// Checksum del nombre
|
return ((checksum << 5) + checksum) + (entry.one_credit_complete ? 1U : 0U);
|
||||||
for (char c : entry.name) {
|
});
|
||||||
checksum = ((checksum << 5) + checksum) + static_cast<unsigned int>(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checksum de one_credit_complete
|
|
||||||
checksum = ((checksum << 5) + checksum) + (entry.one_credit_complete ? 1U : 0U);
|
|
||||||
}
|
|
||||||
|
|
||||||
return checksum;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guarda la tabla en un fichero
|
// Guarda la tabla en un fichero
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ Scoreboard::Scoreboard()
|
|||||||
fillBackgroundTexture();
|
fillBackgroundTexture();
|
||||||
|
|
||||||
// Inicializa el ciclo de colores para el nombre
|
// Inicializa el ciclo de colores para el nombre
|
||||||
name_color_cycle_ = Colors::generateMirroredCycle(color_.INVERSE(), ColorCycleStyle::VIBRANT);
|
name_color_cycle_ = Colors::generateMirroredCycle(color_.inverse(), ColorCycleStyle::VIBRANT);
|
||||||
animated_color_ = name_color_cycle_.at(0);
|
animated_color_ = name_color_cycle_.at(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,13 +337,13 @@ void Scoreboard::render() {
|
|||||||
void Scoreboard::setColor(Color color) {
|
void Scoreboard::setColor(Color color) {
|
||||||
// Actualiza las variables de colores
|
// Actualiza las variables de colores
|
||||||
color_ = color;
|
color_ = color;
|
||||||
text_color1_ = param.scoreboard.text_autocolor ? color_.LIGHTEN(100) : param.scoreboard.text_color1;
|
text_color1_ = param.scoreboard.text_autocolor ? color_.lighten(100) : param.scoreboard.text_color1;
|
||||||
text_color2_ = param.scoreboard.text_autocolor ? color_.LIGHTEN(150) : param.scoreboard.text_color2;
|
text_color2_ = param.scoreboard.text_autocolor ? color_.lighten(150) : param.scoreboard.text_color2;
|
||||||
|
|
||||||
// Aplica los colores
|
// Aplica los colores
|
||||||
power_meter_sprite_->getTexture()->setColor(text_color2_);
|
power_meter_sprite_->getTexture()->setColor(text_color2_);
|
||||||
fillBackgroundTexture();
|
fillBackgroundTexture();
|
||||||
name_color_cycle_ = Colors::generateMirroredCycle(color_.INVERSE(), ColorCycleStyle::VIBRANT);
|
name_color_cycle_ = Colors::generateMirroredCycle(color_.inverse(), ColorCycleStyle::VIBRANT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Establece el valor de la variable
|
// Establece el valor de la variable
|
||||||
@@ -383,9 +383,9 @@ void Scoreboard::fillPanelTextures() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Interpolar entre color base y color aclarado
|
// Interpolar entre color base y color aclarado
|
||||||
Color target_color = color_.LIGHTEN(PANEL_PULSE_LIGHTEN_AMOUNT);
|
Color target_color = color_.lighten(PANEL_PULSE_LIGHTEN_AMOUNT);
|
||||||
// Color target_color = color_.INVERSE();
|
// Color target_color = color_.INVERSE();
|
||||||
background_color = color_.LERP(target_color, pulse_intensity);
|
background_color = color_.lerp(target_color, pulse_intensity);
|
||||||
background_color.a = 255; // Opaco durante el pulso
|
background_color.a = 255; // Opaco durante el pulso
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -716,7 +716,7 @@ void Scoreboard::createPanelTextures() {
|
|||||||
// Dibuja la linea que separa la zona de juego del marcador
|
// Dibuja la linea que separa la zona de juego del marcador
|
||||||
void Scoreboard::renderSeparator() {
|
void Scoreboard::renderSeparator() {
|
||||||
// Dibuja la linea que separa el marcador de la zona de juego
|
// Dibuja la linea que separa el marcador de la zona de juego
|
||||||
auto color = param.scoreboard.separator_autocolor ? color_.DARKEN() : param.scoreboard.separator_color;
|
auto color = param.scoreboard.separator_autocolor ? color_.darken() : param.scoreboard.separator_color;
|
||||||
SDL_SetRenderDrawColor(renderer_, color.r, color.g, color.b, 255);
|
SDL_SetRenderDrawColor(renderer_, color.r, color.g, color.b, 255);
|
||||||
SDL_RenderLine(renderer_, 0, 0, rect_.w, 0);
|
SDL_RenderLine(renderer_, 0, 0, rect_.w, 0);
|
||||||
}
|
}
|
||||||
@@ -724,7 +724,7 @@ void Scoreboard::renderSeparator() {
|
|||||||
// Pinta el carrusel de caracteres con efecto de color LERP y animación suave
|
// Pinta el carrusel de caracteres con efecto de color LERP y animación suave
|
||||||
void Scoreboard::renderCarousel(size_t panel_index, int center_x, int y) {
|
void Scoreboard::renderCarousel(size_t panel_index, int center_x, int y) {
|
||||||
// Obtener referencia a EnterName
|
// Obtener referencia a EnterName
|
||||||
EnterName* enter_name = enter_name_ref_.at(panel_index);
|
const EnterName* enter_name = enter_name_ref_.at(panel_index);
|
||||||
if (enter_name == nullptr) {
|
if (enter_name == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -755,7 +755,7 @@ void Scoreboard::renderCarousel(size_t panel_index, int center_x, int y) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const float FRACTIONAL_OFFSET = frac;
|
const float FRACTIONAL_OFFSET = frac;
|
||||||
const int PIXEL_OFFSET = static_cast<int>((FRACTIONAL_OFFSET * CHAR_STEP) + 0.5F);
|
const int PIXEL_OFFSET = static_cast<int>(std::lround(FRACTIONAL_OFFSET * CHAR_STEP));
|
||||||
|
|
||||||
// Índice base en la lista de caracteres (posición central)
|
// Índice base en la lista de caracteres (posición central)
|
||||||
const int BASE_INDEX = static_cast<int>(std::floor(carousel_pos));
|
const int BASE_INDEX = static_cast<int>(std::floor(carousel_pos));
|
||||||
@@ -790,13 +790,13 @@ void Scoreboard::renderCarousel(size_t panel_index, int center_x, int y) {
|
|||||||
if (DISTANCE_FROM_CENTER < 0.5F) {
|
if (DISTANCE_FROM_CENTER < 0.5F) {
|
||||||
// Letra central → transiciona hacia animated_color_
|
// Letra central → transiciona hacia animated_color_
|
||||||
float lerp_to_animated = DISTANCE_FROM_CENTER / 0.5F; // 0.0 a 1.0
|
float lerp_to_animated = DISTANCE_FROM_CENTER / 0.5F; // 0.0 a 1.0
|
||||||
letter_color = animated_color_.LERP(text_color1_, lerp_to_animated);
|
letter_color = animated_color_.lerp(text_color1_, lerp_to_animated);
|
||||||
} else {
|
} else {
|
||||||
// Letras alejadas → degradan hacia color_ base
|
// Letras alejadas → degradan hacia color_ base
|
||||||
float base_lerp = (DISTANCE_FROM_CENTER - 0.5F) / (HALF_VISIBLE - 0.5F);
|
float base_lerp = (DISTANCE_FROM_CENTER - 0.5F) / (HALF_VISIBLE - 0.5F);
|
||||||
base_lerp = std::min(base_lerp, 1.0F);
|
base_lerp = std::min(base_lerp, 1.0F);
|
||||||
const float LERP_FACTOR = base_lerp * 0.85F;
|
const float LERP_FACTOR = base_lerp * 0.85F;
|
||||||
letter_color = text_color1_.LERP(color_, LERP_FACTOR);
|
letter_color = text_color1_.lerp(color_, LERP_FACTOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calcular posición X de la letra
|
// Calcular posición X de la letra
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <array> // Para array
|
#include <array> // Para array
|
||||||
#include <cstddef> // Para size_t
|
#include <cstddef> // Para size_t
|
||||||
|
#include <cstdint> // Para std::uint8_t
|
||||||
#include <memory> // Para shared_ptr, unique_ptr
|
#include <memory> // Para shared_ptr, unique_ptr
|
||||||
#include <string> // Para string, basic_string
|
#include <string> // Para string, basic_string
|
||||||
#include <vector> // Para vector
|
#include <vector> // Para vector
|
||||||
@@ -21,14 +22,14 @@ class Texture;
|
|||||||
class Scoreboard {
|
class Scoreboard {
|
||||||
public:
|
public:
|
||||||
// --- Enums ---
|
// --- Enums ---
|
||||||
enum class Id : size_t {
|
enum class Id : std::uint8_t {
|
||||||
LEFT = 0,
|
LEFT = 0,
|
||||||
CENTER = 1,
|
CENTER = 1,
|
||||||
RIGHT = 2,
|
RIGHT = 2,
|
||||||
SIZE = 3
|
SIZE = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class Mode : int {
|
enum class Mode : std::uint8_t {
|
||||||
SCORE,
|
SCORE,
|
||||||
STAGE_INFO,
|
STAGE_INFO,
|
||||||
CONTINUE,
|
CONTINUE,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <algorithm> // Para max, min
|
#include <algorithm> // Para max, min
|
||||||
#include <exception> // Para exception
|
#include <exception> // Para exception
|
||||||
#include <fstream> // Para basic_istream, basic_ifstream, ifstream, stringstream
|
#include <fstream> // Para basic_istream, basic_ifstream, ifstream, stringstream
|
||||||
|
#include <numeric> // Para accumulate
|
||||||
#include <sstream> // Para basic_stringstream
|
#include <sstream> // Para basic_stringstream
|
||||||
#include <utility> // Para move
|
#include <utility> // Para move
|
||||||
|
|
||||||
@@ -282,11 +283,7 @@ auto StageManager::getPowerNeededForCurrentStage() const -> int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto StageManager::getTotalPowerNeededToCompleteGame() const -> int {
|
auto StageManager::getTotalPowerNeededToCompleteGame() const -> int {
|
||||||
int total_power_needed = 0;
|
return std::accumulate(stages_.begin(), stages_.end(), 0, [](int total, const auto& stage) { return total + stage.getPowerToComplete(); });
|
||||||
for (const auto& stage : stages_) {
|
|
||||||
total_power_needed += stage.getPowerToComplete();
|
|
||||||
}
|
|
||||||
return total_power_needed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto StageManager::getPowerNeededToReachStage(size_t target_stage_index) const -> int {
|
auto StageManager::getPowerNeededToReachStage(size_t target_stage_index) const -> int {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef> // Para size_t
|
#include <cstddef> // Para size_t
|
||||||
|
#include <cstdint> // Para std::uint8_t
|
||||||
#include <functional> // Para function
|
#include <functional> // Para function
|
||||||
#include <optional> // Para optional
|
#include <optional> // Para optional
|
||||||
#include <string> // Para basic_string, string
|
#include <string> // Para basic_string, string
|
||||||
@@ -9,12 +10,12 @@
|
|||||||
#include "core/system/stage_interface.hpp" // for IStageInfo
|
#include "core/system/stage_interface.hpp" // for IStageInfo
|
||||||
|
|
||||||
// --- Enums ---
|
// --- Enums ---
|
||||||
enum class PowerCollectionState {
|
enum class PowerCollectionState : std::uint8_t {
|
||||||
ENABLED, // Recolección habilitada
|
ENABLED, // Recolección habilitada
|
||||||
DISABLED // Recolección deshabilitada
|
DISABLED // Recolección deshabilitada
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class StageStatus {
|
enum class StageStatus : std::uint8_t {
|
||||||
LOCKED, // Fase bloqueada
|
LOCKED, // Fase bloqueada
|
||||||
IN_PROGRESS, // Fase en progreso
|
IN_PROGRESS, // Fase en progreso
|
||||||
COMPLETED // Fase completada
|
COMPLETED // Fase completada
|
||||||
|
|||||||
+127
-242
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h> // Para SDL_ScaleMode, SDL_LogCategory, SDL_LogError, SDL_LogInfo, SDL_LogWarn
|
#include <SDL3/SDL.h> // Para SDL_ScaleMode, SDL_LogCategory, SDL_LogError, SDL_LogInfo, SDL_LogWarn
|
||||||
|
|
||||||
#include <algorithm> // Para clamp
|
#include <algorithm> // Para clamp, ranges::find_if
|
||||||
#include <cstddef> // Para size_t
|
#include <cstddef> // Para size_t
|
||||||
#include <fstream> // Para ifstream, ofstream
|
#include <fstream> // Para ifstream, ofstream
|
||||||
#include <iostream> // Para std::cout
|
#include <iostream> // Para std::cout
|
||||||
@@ -26,12 +26,12 @@ namespace Options {
|
|||||||
Keyboard keyboard; // Opciones para el teclado
|
Keyboard keyboard; // Opciones para el teclado
|
||||||
PendingChanges pending_changes; // Opciones que se aplican al cerrar
|
PendingChanges pending_changes; // Opciones que se aplican al cerrar
|
||||||
std::vector<PostFXPreset> postfx_presets = {
|
std::vector<PostFXPreset> postfx_presets = {
|
||||||
{"CRT", 0.15F, 0.7F, 0.2F, 0.5F, 0.1F, 0.0F, 0.0F, 0.0F},
|
{.name = "CRT", .vignette = 0.15F, .scanlines = 0.7F, .chroma = 0.2F, .mask = 0.5F, .gamma = 0.1F, .curvature = 0.0F, .bleeding = 0.0F, .flicker = 0.0F},
|
||||||
{"NTSC", 0.4F, 0.5F, 0.2F, 0.3F, 0.3F, 0.0F, 0.6F, 0.0F},
|
{.name = "NTSC", .vignette = 0.4F, .scanlines = 0.5F, .chroma = 0.2F, .mask = 0.3F, .gamma = 0.3F, .curvature = 0.0F, .bleeding = 0.6F, .flicker = 0.0F},
|
||||||
{"Curved", 0.5F, 0.6F, 0.1F, 0.4F, 0.4F, 0.8F, 0.0F, 0.0F},
|
{.name = "Curved", .vignette = 0.5F, .scanlines = 0.6F, .chroma = 0.1F, .mask = 0.4F, .gamma = 0.4F, .curvature = 0.8F, .bleeding = 0.0F, .flicker = 0.0F},
|
||||||
{"Scanlines", 0.0F, 0.8F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F},
|
{.name = "Scanlines", .vignette = 0.0F, .scanlines = 0.8F, .chroma = 0.0F, .mask = 0.0F, .gamma = 0.0F, .curvature = 0.0F, .bleeding = 0.0F, .flicker = 0.0F},
|
||||||
{"Subtle", 0.3F, 0.4F, 0.05F, 0.0F, 0.2F, 0.0F, 0.0F, 0.0F},
|
{.name = "Subtle", .vignette = 0.3F, .scanlines = 0.4F, .chroma = 0.05F, .mask = 0.0F, .gamma = 0.2F, .curvature = 0.0F, .bleeding = 0.0F, .flicker = 0.0F},
|
||||||
{"CRT Live", 0.15F, 0.6F, 0.3F, 0.3F, 0.1F, 0.0F, 0.4F, 0.8F},
|
{.name = "CRT Live", .vignette = 0.15F, .scanlines = 0.6F, .chroma = 0.3F, .mask = 0.3F, .gamma = 0.1F, .curvature = 0.0F, .bleeding = 0.4F, .flicker = 0.8F},
|
||||||
};
|
};
|
||||||
std::string postfx_file_path;
|
std::string postfx_file_path;
|
||||||
std::vector<CrtPiPreset> crtpi_presets;
|
std::vector<CrtPiPreset> crtpi_presets;
|
||||||
@@ -50,11 +50,17 @@ namespace Options {
|
|||||||
void setCrtPiFile(const std::string& path) { crtpi_file_path = path; }
|
void setCrtPiFile(const std::string& path) { crtpi_file_path = path; }
|
||||||
|
|
||||||
// Helper: extrae un campo float de un nodo YAML si existe, ignorando errores de conversión
|
// Helper: extrae un campo float de un nodo YAML si existe, ignorando errores de conversión
|
||||||
static void parseFloatField(const fkyaml::node& node, const std::string& key, float& target) {
|
// Llig un camp opcional d'un node YAML. Si no existeix, no toca `target`.
|
||||||
if (node.contains(key)) {
|
// Si existeix però el tipus no encaixa, manté el valor previ i avisa per stderr
|
||||||
try {
|
// (un fitxer de configuració parcialment malformat no ha de tombar l'arrencada,
|
||||||
target = node[key].get_value<float>();
|
// però l'usuari ha de saber quin camp ha quedat ignorat).
|
||||||
} catch (...) {}
|
template <typename T>
|
||||||
|
void parseField(const fkyaml::node& node, const std::string& key, T& target) {
|
||||||
|
if (!node.contains(key)) { return; }
|
||||||
|
try {
|
||||||
|
target = node[key].get_value<T>();
|
||||||
|
} catch (...) {
|
||||||
|
std::cerr << "config YAML: valor invàlid per a '" << key << "', es manté el valor per defecte\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,14 +86,14 @@ namespace Options {
|
|||||||
if (p.contains("name")) {
|
if (p.contains("name")) {
|
||||||
preset.name = p["name"].get_value<std::string>();
|
preset.name = p["name"].get_value<std::string>();
|
||||||
}
|
}
|
||||||
parseFloatField(p, "vignette", preset.vignette);
|
parseField(p, "vignette", preset.vignette);
|
||||||
parseFloatField(p, "scanlines", preset.scanlines);
|
parseField(p, "scanlines", preset.scanlines);
|
||||||
parseFloatField(p, "chroma", preset.chroma);
|
parseField(p, "chroma", preset.chroma);
|
||||||
parseFloatField(p, "mask", preset.mask);
|
parseField(p, "mask", preset.mask);
|
||||||
parseFloatField(p, "gamma", preset.gamma);
|
parseField(p, "gamma", preset.gamma);
|
||||||
parseFloatField(p, "curvature", preset.curvature);
|
parseField(p, "curvature", preset.curvature);
|
||||||
parseFloatField(p, "bleeding", preset.bleeding);
|
parseField(p, "bleeding", preset.bleeding);
|
||||||
parseFloatField(p, "flicker", preset.flicker);
|
parseField(p, "flicker", preset.flicker);
|
||||||
postfx_presets.push_back(preset);
|
postfx_presets.push_back(preset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -212,24 +218,6 @@ namespace Options {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: extrae un campo bool de un nodo YAML si existe, ignorando errores
|
|
||||||
static void parseBoolField(const fkyaml::node& node, const std::string& key, bool& target) {
|
|
||||||
if (node.contains(key)) {
|
|
||||||
try {
|
|
||||||
target = node[key].get_value<bool>();
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: extrae un campo int de un nodo YAML si existe, ignorando errores
|
|
||||||
static void parseIntField(const fkyaml::node& node, const std::string& key, int& target) {
|
|
||||||
if (node.contains(key)) {
|
|
||||||
try {
|
|
||||||
target = node[key].get_value<int>();
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rellena los presets CrtPi por defecto
|
// Rellena los presets CrtPi por defecto
|
||||||
static void populateDefaultCrtPiPresets() {
|
static void populateDefaultCrtPiPresets() {
|
||||||
crtpi_presets.clear();
|
crtpi_presets.clear();
|
||||||
@@ -289,20 +277,20 @@ namespace Options {
|
|||||||
if (p.contains("name")) {
|
if (p.contains("name")) {
|
||||||
preset.name = p["name"].get_value<std::string>();
|
preset.name = p["name"].get_value<std::string>();
|
||||||
}
|
}
|
||||||
parseFloatField(p, "scanline_weight", preset.scanline_weight);
|
parseField(p, "scanline_weight", preset.scanline_weight);
|
||||||
parseFloatField(p, "scanline_gap_brightness", preset.scanline_gap_brightness);
|
parseField(p, "scanline_gap_brightness", preset.scanline_gap_brightness);
|
||||||
parseFloatField(p, "bloom_factor", preset.bloom_factor);
|
parseField(p, "bloom_factor", preset.bloom_factor);
|
||||||
parseFloatField(p, "input_gamma", preset.input_gamma);
|
parseField(p, "input_gamma", preset.input_gamma);
|
||||||
parseFloatField(p, "output_gamma", preset.output_gamma);
|
parseField(p, "output_gamma", preset.output_gamma);
|
||||||
parseFloatField(p, "mask_brightness", preset.mask_brightness);
|
parseField(p, "mask_brightness", preset.mask_brightness);
|
||||||
parseFloatField(p, "curvature_x", preset.curvature_x);
|
parseField(p, "curvature_x", preset.curvature_x);
|
||||||
parseFloatField(p, "curvature_y", preset.curvature_y);
|
parseField(p, "curvature_y", preset.curvature_y);
|
||||||
parseIntField(p, "mask_type", preset.mask_type);
|
parseField(p, "mask_type", preset.mask_type);
|
||||||
parseBoolField(p, "enable_scanlines", preset.enable_scanlines);
|
parseField(p, "enable_scanlines", preset.enable_scanlines);
|
||||||
parseBoolField(p, "enable_multisample", preset.enable_multisample);
|
parseField(p, "enable_multisample", preset.enable_multisample);
|
||||||
parseBoolField(p, "enable_gamma", preset.enable_gamma);
|
parseField(p, "enable_gamma", preset.enable_gamma);
|
||||||
parseBoolField(p, "enable_curvature", preset.enable_curvature);
|
parseField(p, "enable_curvature", preset.enable_curvature);
|
||||||
parseBoolField(p, "enable_sharper", preset.enable_sharper);
|
parseField(p, "enable_sharper", preset.enable_sharper);
|
||||||
crtpi_presets.push_back(preset);
|
crtpi_presets.push_back(preset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,98 +341,46 @@ namespace Options {
|
|||||||
void loadWindowFromYaml(const fkyaml::node& yaml) {
|
void loadWindowFromYaml(const fkyaml::node& yaml) {
|
||||||
if (!yaml.contains("window")) { return; }
|
if (!yaml.contains("window")) { return; }
|
||||||
const auto& win = yaml["window"];
|
const auto& win = yaml["window"];
|
||||||
if (win.contains("zoom")) {
|
int zoom = window.zoom;
|
||||||
try {
|
parseField(win, "zoom", zoom);
|
||||||
int val = win["zoom"].get_value<int>();
|
window.zoom = (zoom > 0) ? zoom : window.zoom;
|
||||||
window.zoom = (val > 0) ? val : window.zoom;
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadVideoFromYaml(const fkyaml::node& yaml) {
|
void loadVideoFromYaml(const fkyaml::node& yaml) {
|
||||||
if (!yaml.contains("video")) { return; }
|
if (!yaml.contains("video")) { return; }
|
||||||
const auto& vid = yaml["video"];
|
const auto& vid = yaml["video"];
|
||||||
|
|
||||||
if (vid.contains("fullscreen")) {
|
parseField(vid, "fullscreen", video.fullscreen);
|
||||||
try {
|
parseField(vid, "vsync", video.vsync);
|
||||||
video.fullscreen = vid["fullscreen"].get_value<bool>();
|
parseField(vid, "integer_scale", video.integer_scale);
|
||||||
} catch (...) {}
|
|
||||||
}
|
int scale_mode_int = static_cast<int>(video.scale_mode);
|
||||||
if (vid.contains("scale_mode")) {
|
parseField(vid, "scale_mode", scale_mode_int);
|
||||||
try {
|
video.scale_mode = static_cast<SDL_ScaleMode>(scale_mode_int);
|
||||||
video.scale_mode = static_cast<SDL_ScaleMode>(vid["scale_mode"].get_value<int>());
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
if (vid.contains("vsync")) {
|
|
||||||
try {
|
|
||||||
video.vsync = vid["vsync"].get_value<bool>();
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
if (vid.contains("integer_scale")) {
|
|
||||||
try {
|
|
||||||
video.integer_scale = vid["integer_scale"].get_value<bool>();
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- GPU ---
|
|
||||||
if (vid.contains("gpu")) {
|
if (vid.contains("gpu")) {
|
||||||
const auto& gpu_node = vid["gpu"];
|
const auto& gpu_node = vid["gpu"];
|
||||||
if (gpu_node.contains("acceleration")) {
|
parseField(gpu_node, "acceleration", video.gpu.acceleration);
|
||||||
try {
|
parseField(gpu_node, "preferred_driver", video.gpu.preferred_driver);
|
||||||
video.gpu.acceleration = gpu_node["acceleration"].get_value<bool>();
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
if (gpu_node.contains("preferred_driver")) {
|
|
||||||
try {
|
|
||||||
video.gpu.preferred_driver = gpu_node["preferred_driver"].get_value<std::string>();
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Shader config ---
|
|
||||||
if (vid.contains("shader")) {
|
if (vid.contains("shader")) {
|
||||||
const auto& sh = vid["shader"];
|
const auto& sh = vid["shader"];
|
||||||
if (sh.contains("enabled")) {
|
parseField(sh, "enabled", video.shader.enabled);
|
||||||
try {
|
parseField(sh, "postfx_preset", video.shader.current_postfx_preset_name);
|
||||||
video.shader.enabled = sh["enabled"].get_value<bool>();
|
parseField(sh, "crtpi_preset", video.shader.current_crtpi_preset_name);
|
||||||
} catch (...) {}
|
std::string shader_name;
|
||||||
}
|
parseField(sh, "current_shader", shader_name);
|
||||||
if (sh.contains("current_shader")) {
|
if (!shader_name.empty()) {
|
||||||
try {
|
video.shader.current_shader = (shader_name == "crtpi") ? Rendering::ShaderType::CRTPI : Rendering::ShaderType::POSTFX;
|
||||||
auto s = sh["current_shader"].get_value<std::string>();
|
|
||||||
video.shader.current_shader = (s == "crtpi") ? Rendering::ShaderType::CRTPI : Rendering::ShaderType::POSTFX;
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
if (sh.contains("postfx_preset")) {
|
|
||||||
try {
|
|
||||||
video.shader.current_postfx_preset_name = sh["postfx_preset"].get_value<std::string>();
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
if (sh.contains("crtpi_preset")) {
|
|
||||||
try {
|
|
||||||
video.shader.current_crtpi_preset_name = sh["crtpi_preset"].get_value<std::string>();
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Supersampling ---
|
|
||||||
if (vid.contains("supersampling")) {
|
if (vid.contains("supersampling")) {
|
||||||
const auto& ss_node = vid["supersampling"];
|
const auto& ss_node = vid["supersampling"];
|
||||||
if (ss_node.contains("enabled")) {
|
parseField(ss_node, "enabled", video.supersampling.enabled);
|
||||||
try {
|
parseField(ss_node, "linear_upscale", video.supersampling.linear_upscale);
|
||||||
video.supersampling.enabled = ss_node["enabled"].get_value<bool>();
|
parseField(ss_node, "downscale_algo", video.supersampling.downscale_algo);
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
if (ss_node.contains("linear_upscale")) {
|
|
||||||
try {
|
|
||||||
video.supersampling.linear_upscale = ss_node["linear_upscale"].get_value<bool>();
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
if (ss_node.contains("downscale_algo")) {
|
|
||||||
try {
|
|
||||||
video.supersampling.downscale_algo = ss_node["downscale_algo"].get_value<int>();
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,50 +388,30 @@ namespace Options {
|
|||||||
if (!yaml.contains("audio")) { return; }
|
if (!yaml.contains("audio")) { return; }
|
||||||
const auto& aud = yaml["audio"];
|
const auto& aud = yaml["audio"];
|
||||||
|
|
||||||
if (aud.contains("enabled")) {
|
parseField(aud, "enabled", audio.enabled);
|
||||||
try {
|
parseField(aud, "volume", audio.volume);
|
||||||
audio.enabled = aud["enabled"].get_value<bool>();
|
audio.volume = std::clamp(audio.volume, 0.0F, 1.0F);
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
if (aud.contains("volume")) {
|
|
||||||
try {
|
|
||||||
audio.volume = std::clamp(aud["volume"].get_value<int>(), 0, 100);
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
if (aud.contains("music")) {
|
if (aud.contains("music")) {
|
||||||
const auto& mus = aud["music"];
|
const auto& mus = aud["music"];
|
||||||
if (mus.contains("enabled")) {
|
parseField(mus, "enabled", audio.music.enabled);
|
||||||
try {
|
parseField(mus, "volume", audio.music.volume);
|
||||||
audio.music.enabled = mus["enabled"].get_value<bool>();
|
audio.music.volume = std::clamp(audio.music.volume, 0.0F, 1.0F);
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
if (mus.contains("volume")) {
|
|
||||||
try {
|
|
||||||
audio.music.volume = std::clamp(mus["volume"].get_value<int>(), 0, 100);
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (aud.contains("sound")) {
|
if (aud.contains("sound")) {
|
||||||
const auto& snd = aud["sound"];
|
const auto& snd = aud["sound"];
|
||||||
if (snd.contains("enabled")) {
|
parseField(snd, "enabled", audio.sound.enabled);
|
||||||
try {
|
parseField(snd, "volume", audio.sound.volume);
|
||||||
audio.sound.enabled = snd["enabled"].get_value<bool>();
|
audio.sound.volume = std::clamp(audio.sound.volume, 0.0F, 1.0F);
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
if (snd.contains("volume")) {
|
|
||||||
try {
|
|
||||||
audio.sound.volume = std::clamp(snd["volume"].get_value<int>(), 0, 100);
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadLoadingFromYaml(const fkyaml::node& yaml) {
|
void loadLoadingFromYaml(const fkyaml::node& yaml) {
|
||||||
if (!yaml.contains("loading")) { return; }
|
if (!yaml.contains("loading")) { return; }
|
||||||
const auto& ld = yaml["loading"];
|
const auto& ld = yaml["loading"];
|
||||||
parseBoolField(ld, "show", loading.show);
|
parseField(ld, "show", loading.show);
|
||||||
parseBoolField(ld, "show_resource_name", loading.show_resource_name);
|
parseField(ld, "show_resource_name", loading.show_resource_name);
|
||||||
parseBoolField(ld, "wait_for_input", loading.wait_for_input);
|
parseField(ld, "wait_for_input", loading.wait_for_input);
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadGameFromYaml(const fkyaml::node& yaml) {
|
void loadGameFromYaml(const fkyaml::node& yaml) {
|
||||||
@@ -503,37 +419,23 @@ namespace Options {
|
|||||||
const auto& game = yaml["game"];
|
const auto& game = yaml["game"];
|
||||||
|
|
||||||
if (game.contains("language")) {
|
if (game.contains("language")) {
|
||||||
try {
|
int lang_int = static_cast<int>(settings.language);
|
||||||
auto lang = static_cast<Lang::Code>(game["language"].get_value<int>());
|
parseField(game, "language", lang_int);
|
||||||
if (lang == Lang::Code::ENGLISH || lang == Lang::Code::VALENCIAN || lang == Lang::Code::SPANISH) {
|
const auto LANG = static_cast<Lang::Code>(lang_int);
|
||||||
settings.language = lang;
|
settings.language = (LANG == Lang::Code::ENGLISH || LANG == Lang::Code::VALENCIAN || LANG == Lang::Code::SPANISH)
|
||||||
} else {
|
? LANG
|
||||||
settings.language = Lang::Code::ENGLISH;
|
: Lang::Code::ENGLISH;
|
||||||
}
|
pending_changes.new_language = settings.language;
|
||||||
pending_changes.new_language = settings.language;
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
}
|
||||||
if (game.contains("difficulty")) {
|
if (game.contains("difficulty")) {
|
||||||
try {
|
int diff_int = static_cast<int>(settings.difficulty);
|
||||||
settings.difficulty = static_cast<Difficulty::Code>(game["difficulty"].get_value<int>());
|
parseField(game, "difficulty", diff_int);
|
||||||
pending_changes.new_difficulty = settings.difficulty;
|
settings.difficulty = static_cast<Difficulty::Code>(diff_int);
|
||||||
} catch (...) {}
|
pending_changes.new_difficulty = settings.difficulty;
|
||||||
}
|
|
||||||
if (game.contains("autofire")) {
|
|
||||||
try {
|
|
||||||
settings.autofire = game["autofire"].get_value<bool>();
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
if (game.contains("shutdown_enabled")) {
|
|
||||||
try {
|
|
||||||
settings.shutdown_enabled = game["shutdown_enabled"].get_value<bool>();
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
if (game.contains("params_file")) {
|
|
||||||
try {
|
|
||||||
settings.params_file = game["params_file"].get_value<std::string>();
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
}
|
||||||
|
parseField(game, "autofire", settings.autofire);
|
||||||
|
parseField(game, "shutdown_enabled", settings.shutdown_enabled);
|
||||||
|
parseField(game, "params_file", settings.params_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadControllersFromYaml(const fkyaml::node& yaml) {
|
void loadControllersFromYaml(const fkyaml::node& yaml) {
|
||||||
@@ -543,25 +445,14 @@ namespace Options {
|
|||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
for (const auto& ctrl : controllers) {
|
for (const auto& ctrl : controllers) {
|
||||||
if (i >= GamepadManager::size()) { break; }
|
if (i >= GamepadManager::size()) { break; }
|
||||||
if (ctrl.contains("name")) {
|
parseField(ctrl, "name", gamepad_manager[i].name);
|
||||||
try {
|
parseField(ctrl, "path", gamepad_manager[i].path);
|
||||||
gamepad_manager[i].name = ctrl["name"].get_value<std::string>();
|
int player_int = 0;
|
||||||
} catch (...) {}
|
parseField(ctrl, "player", player_int);
|
||||||
}
|
if (player_int == 1) {
|
||||||
if (ctrl.contains("path")) {
|
gamepad_manager[i].player_id = Player::Id::PLAYER1;
|
||||||
try {
|
} else if (player_int == 2) {
|
||||||
gamepad_manager[i].path = ctrl["path"].get_value<std::string>();
|
gamepad_manager[i].player_id = Player::Id::PLAYER2;
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
if (ctrl.contains("player")) {
|
|
||||||
try {
|
|
||||||
int player_int = ctrl["player"].get_value<int>();
|
|
||||||
if (player_int == 1) {
|
|
||||||
gamepad_manager[i].player_id = Player::Id::PLAYER1;
|
|
||||||
} else if (player_int == 2) {
|
|
||||||
gamepad_manager[i].player_id = Player::Id::PLAYER2;
|
|
||||||
}
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
}
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
@@ -570,11 +461,9 @@ namespace Options {
|
|||||||
void loadKeyboardFromYaml(const fkyaml::node& yaml) {
|
void loadKeyboardFromYaml(const fkyaml::node& yaml) {
|
||||||
if (!yaml.contains("keyboard")) { return; }
|
if (!yaml.contains("keyboard")) { return; }
|
||||||
const auto& kb = yaml["keyboard"];
|
const auto& kb = yaml["keyboard"];
|
||||||
if (kb.contains("player")) {
|
int player_int = static_cast<int>(keyboard.player_id);
|
||||||
try {
|
parseField(kb, "player", player_int);
|
||||||
keyboard.player_id = static_cast<Player::Id>(kb["player"].get_value<int>());
|
keyboard.player_id = static_cast<Player::Id>(player_int);
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carga el fichero de configuración
|
// Carga el fichero de configuración
|
||||||
@@ -595,11 +484,7 @@ namespace Options {
|
|||||||
|
|
||||||
// Comprobar versión: si no coincide, regenerar config por defecto
|
// Comprobar versión: si no coincide, regenerar config por defecto
|
||||||
int file_version = 0;
|
int file_version = 0;
|
||||||
if (yaml.contains("version")) {
|
parseField(yaml, "version", file_version);
|
||||||
try {
|
|
||||||
file_version = yaml["version"].get_value<int>();
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
if (file_version != Settings::CURRENT_CONFIG_VERSION) {
|
if (file_version != Settings::CURRENT_CONFIG_VERSION) {
|
||||||
std::cout << "Config version " << file_version << " != expected " << Settings::CURRENT_CONFIG_VERSION << ". Recreating defaults." << '\n';
|
std::cout << "Config version " << file_version << " != expected " << Settings::CURRENT_CONFIG_VERSION << ". Recreating defaults." << '\n';
|
||||||
init();
|
init();
|
||||||
@@ -820,14 +705,14 @@ namespace Options {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& physical_gamepad : physical_gamepads) {
|
const auto IT = std::ranges::find_if(physical_gamepads,
|
||||||
if (physical_gamepad->path == desired_path && !isGamepadAssigned(physical_gamepad, assigned_instances)) {
|
[&desired_path, &assigned_instances](const auto& pg) {
|
||||||
gamepads_[i].instance = physical_gamepad;
|
return pg->path == desired_path && !isGamepadAssigned(pg, assigned_instances);
|
||||||
gamepads_[i].name = physical_gamepad->name;
|
});
|
||||||
|
if (IT != physical_gamepads.end()) {
|
||||||
assigned_instances.push_back(physical_gamepad);
|
gamepads_[i].instance = *IT;
|
||||||
break;
|
gamepads_[i].name = (*IT)->name;
|
||||||
}
|
assigned_instances.push_back(*IT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -849,15 +734,15 @@ namespace Options {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& physical_gamepad : physical_gamepads) {
|
const auto IT = std::ranges::find_if(physical_gamepads,
|
||||||
if (physical_gamepad->name == desired_name && !isGamepadAssigned(physical_gamepad, assigned_instances)) {
|
[&desired_name, &assigned_instances](const auto& pg) {
|
||||||
gamepads_[i].instance = physical_gamepad;
|
return pg->name == desired_name && !isGamepadAssigned(pg, assigned_instances);
|
||||||
gamepads_[i].name = physical_gamepad->name;
|
});
|
||||||
gamepads_[i].path = physical_gamepad->path;
|
if (IT != physical_gamepads.end()) {
|
||||||
|
gamepads_[i].instance = *IT;
|
||||||
assigned_instances.push_back(physical_gamepad);
|
gamepads_[i].name = (*IT)->name;
|
||||||
break;
|
gamepads_[i].path = (*IT)->path;
|
||||||
}
|
assigned_instances.push_back(*IT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -871,15 +756,15 @@ namespace Options {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& physical_gamepad : physical_gamepads) {
|
const auto IT = std::ranges::find_if(physical_gamepads,
|
||||||
if (!isGamepadAssigned(physical_gamepad, assigned_instances)) {
|
[&assigned_instances](const auto& pg) {
|
||||||
gamepads_[i].instance = physical_gamepad;
|
return !isGamepadAssigned(pg, assigned_instances);
|
||||||
gamepads_[i].name = physical_gamepad->name;
|
});
|
||||||
gamepads_[i].path = physical_gamepad->path;
|
if (IT != physical_gamepads.end()) {
|
||||||
|
gamepads_[i].instance = *IT;
|
||||||
assigned_instances.push_back(physical_gamepad);
|
gamepads_[i].name = (*IT)->name;
|
||||||
break;
|
gamepads_[i].path = (*IT)->path;
|
||||||
}
|
assigned_instances.push_back(*IT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -141,7 +141,7 @@ namespace Options {
|
|||||||
std::string path; // Ruta física del dispositivo
|
std::string path; // Ruta física del dispositivo
|
||||||
Player::Id player_id; // Jugador asociado al mando
|
Player::Id player_id; // Jugador asociado al mando
|
||||||
|
|
||||||
Gamepad(Player::Id custom_player_id = Player::Id::NO_PLAYER)
|
explicit Gamepad(Player::Id custom_player_id = Player::Id::NO_PLAYER)
|
||||||
: player_id(custom_player_id) {}
|
: player_id(custom_player_id) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -683,10 +683,11 @@ void Credits::startCredits() {
|
|||||||
init_right_x_ = static_cast<int>(right_black_rect_.x);
|
init_right_x_ = static_cast<int>(right_black_rect_.x);
|
||||||
|
|
||||||
// Objetivos
|
// Objetivos
|
||||||
|
const int CENTER_X = param.game.game_area.center_x;
|
||||||
int top_target_h = param.game.game_area.center_y - 1;
|
int top_target_h = param.game.game_area.center_y - 1;
|
||||||
int bottom_target_y = param.game.game_area.center_y + 1;
|
int bottom_target_y = param.game.game_area.center_y + 1;
|
||||||
int left_target_w = param.game.game_area.center_x;
|
int left_target_w = CENTER_X;
|
||||||
int right_target_x = param.game.game_area.center_x;
|
int right_target_x = CENTER_X;
|
||||||
|
|
||||||
// Pasos verticales
|
// Pasos verticales
|
||||||
int pasos_top = std::max(0, top_target_h - init_top_h_);
|
int pasos_top = std::max(0, top_target_h - init_top_h_);
|
||||||
@@ -725,7 +726,7 @@ void Credits::drawBorderRect() {
|
|||||||
return; // no dibujar
|
return; // no dibujar
|
||||||
}
|
}
|
||||||
|
|
||||||
const Color COLOR = color_.LIGHTEN();
|
const Color COLOR = color_.lighten();
|
||||||
SDL_Renderer* rdr = Screen::get()->getRenderer();
|
SDL_Renderer* rdr = Screen::get()->getRenderer();
|
||||||
SDL_SetRenderDrawColor(rdr, COLOR.r, COLOR.g, COLOR.b, 0xFF);
|
SDL_SetRenderDrawColor(rdr, COLOR.r, COLOR.g, COLOR.b, 0xFF);
|
||||||
|
|
||||||
|
|||||||
+16
-31
@@ -16,7 +16,6 @@
|
|||||||
#include "core/audio/audio.hpp" // Para Audio
|
#include "core/audio/audio.hpp" // Para Audio
|
||||||
#include "core/input/global_inputs.hpp" // Para check
|
#include "core/input/global_inputs.hpp" // Para check
|
||||||
#include "core/input/input.hpp" // Para Input
|
#include "core/input/input.hpp" // Para Input
|
||||||
#include "core/input/input_types.hpp" // Para InputAction
|
|
||||||
#include "core/input/pause_manager.hpp" // Para PauseManager
|
#include "core/input/pause_manager.hpp" // Para PauseManager
|
||||||
#include "core/locale/lang.hpp" // Para getText
|
#include "core/locale/lang.hpp" // Para getText
|
||||||
#include "core/rendering/background.hpp" // Para Background
|
#include "core/rendering/background.hpp" // Para Background
|
||||||
@@ -341,7 +340,7 @@ void Game::updateStage() {
|
|||||||
|
|
||||||
// Modificar color de fondo en la última fase
|
// Modificar color de fondo en la última fase
|
||||||
if (current_stage_index == total_stages - 1) { // Última fase
|
if (current_stage_index == total_stages - 1) { // Última fase
|
||||||
background_->setColor(Color(0xdd, 0x19, 0x1d).DARKEN());
|
background_->setColor(Color(0xdd, 0x19, 0x1d).darken());
|
||||||
background_->setAlpha(96);
|
background_->setAlpha(96);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -801,7 +800,7 @@ void Game::renderPathSprites() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Acciones a realizar cuando el jugador colisiona con un globo
|
// Acciones a realizar cuando el jugador colisiona con un globo
|
||||||
void Game::handlePlayerCollision(std::shared_ptr<Player>& player, std::shared_ptr<Balloon>& balloon) {
|
void Game::handlePlayerCollision(std::shared_ptr<Player>& player, const std::shared_ptr<Balloon>& balloon) {
|
||||||
if (!player->isPlaying() || player->isInvulnerable()) {
|
if (!player->isPlaying() || player->isInvulnerable()) {
|
||||||
return; // Si no está jugando o tiene inmunidad, no hace nada
|
return; // Si no está jugando o tiene inmunidad, no hace nada
|
||||||
}
|
}
|
||||||
@@ -1243,18 +1242,6 @@ auto Game::getPlayer(Player::Id id) -> std::shared_ptr<Player> {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtiene un controlador a partir del "id" del jugador
|
|
||||||
auto Game::getController(Player::Id player_id) -> int {
|
|
||||||
switch (player_id) {
|
|
||||||
case Player::Id::PLAYER1:
|
|
||||||
return 0;
|
|
||||||
case Player::Id::PLAYER2:
|
|
||||||
return 1;
|
|
||||||
default:
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gestiona la entrada durante el juego
|
// Gestiona la entrada durante el juego
|
||||||
void Game::checkInput() {
|
void Game::checkInput() {
|
||||||
Input::get()->update();
|
Input::get()->update();
|
||||||
@@ -1293,11 +1280,12 @@ void Game::checkInput() {
|
|||||||
// Verifica si alguno de los controladores ha solicitado una pausa y actualiza el estado de pausa del juego.
|
// Verifica si alguno de los controladores ha solicitado una pausa y actualiza el estado de pausa del juego.
|
||||||
void Game::checkPauseInput() {
|
void Game::checkPauseInput() {
|
||||||
// Comprueba los mandos
|
// Comprueba los mandos
|
||||||
for (const auto& gamepad : input_->getGamepads()) {
|
const auto& gamepads = input_->getGamepads();
|
||||||
if (input_->checkAction(Input::Action::PAUSE, Input::DO_NOT_ALLOW_REPEAT, Input::DO_NOT_CHECK_KEYBOARD, gamepad)) {
|
if (std::ranges::any_of(gamepads, [this](const auto& gamepad) {
|
||||||
pause_manager_->togglePlayerPause();
|
return input_->checkAction(Input::Action::PAUSE, Input::DO_NOT_ALLOW_REPEAT, Input::DO_NOT_CHECK_KEYBOARD, gamepad);
|
||||||
return;
|
})) {
|
||||||
}
|
pause_manager_->togglePlayerPause();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comprueba el teclado
|
// Comprueba el teclado
|
||||||
@@ -1973,7 +1961,7 @@ void Game::playSound(const std::string& name) const {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static auto* audio_ = Audio::get();
|
static const auto* audio_ = Audio::get();
|
||||||
audio_->playSound(name);
|
audio_->playSound(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2048,9 +2036,7 @@ void Game::handleGameOverEvents() {
|
|||||||
void Game::buildPlayerDrawList(const Players& elements, Players& draw_list) {
|
void Game::buildPlayerDrawList(const Players& elements, Players& draw_list) {
|
||||||
draw_list.clear();
|
draw_list.clear();
|
||||||
draw_list.reserve(elements.size());
|
draw_list.reserve(elements.size());
|
||||||
for (const auto& e : elements) {
|
std::ranges::copy(elements, std::back_inserter(draw_list));
|
||||||
draw_list.push_back(e); // copia el shared_ptr
|
|
||||||
}
|
|
||||||
std::ranges::stable_sort(draw_list, [](const std::shared_ptr<Player>& a, const std::shared_ptr<Player>& b) -> bool {
|
std::ranges::stable_sort(draw_list, [](const std::shared_ptr<Player>& a, const std::shared_ptr<Player>& b) -> bool {
|
||||||
return a->getZOrder() < b->getZOrder();
|
return a->getZOrder() < b->getZOrder();
|
||||||
});
|
});
|
||||||
@@ -2154,8 +2140,8 @@ void Game::autoplayHandleInput() {
|
|||||||
|
|
||||||
// Comprueba los eventos en el modo DEBUG
|
// Comprueba los eventos en el modo DEBUG
|
||||||
void Game::handleDebugEvents(const SDL_Event& event) {
|
void Game::handleDebugEvents(const SDL_Event& event) {
|
||||||
static int formation_id_ = 0;
|
|
||||||
if (event.type == SDL_EVENT_KEY_DOWN && static_cast<int>(event.key.repeat) == 0) {
|
if (event.type == SDL_EVENT_KEY_DOWN && static_cast<int>(event.key.repeat) == 0) {
|
||||||
|
static int formation_id_ = 0;
|
||||||
switch (event.key.key) {
|
switch (event.key.key) {
|
||||||
case SDLK_1: { // Crea una powerball
|
case SDLK_1: { // Crea una powerball
|
||||||
balloon_manager_->createPowerBall();
|
balloon_manager_->createPowerBall();
|
||||||
@@ -2196,12 +2182,11 @@ void Game::handleDebugEvents(const SDL_Event& event) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SDLK_8: {
|
case SDLK_8: {
|
||||||
for (const auto& player : players_) {
|
const auto IT = std::ranges::find_if(players_,
|
||||||
if (player->isPlaying()) {
|
[](const auto& player) { return player->isPlaying(); });
|
||||||
createItem(ItemType::COFFEE_MACHINE, player->getPosX(), param.game.game_area.rect.y - Item::COFFEE_MACHINE_HEIGHT);
|
if (IT != players_.end()) {
|
||||||
coffee_machine_enabled_ = true;
|
createItem(ItemType::COFFEE_MACHINE, (*IT)->getPosX(), param.game.game_area.rect.y - Item::COFFEE_MACHINE_HEIGHT);
|
||||||
break;
|
coffee_machine_enabled_ = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-10
@@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h> // Para SDL_Event, SDL_Renderer, SDL_Texture, Uint64
|
#include <SDL3/SDL.h> // Para SDL_Event, SDL_Renderer, SDL_Texture, Uint64
|
||||||
|
|
||||||
#include <list> // Para list
|
#include <cstdint> // Para std::uint8_t
|
||||||
#include <memory> // Para shared_ptr, unique_ptr
|
#include <list> // Para list
|
||||||
#include <string> // Para string
|
#include <memory> // Para shared_ptr, unique_ptr
|
||||||
#include <vector> // Para vector
|
#include <string> // Para string
|
||||||
|
#include <vector> // Para vector
|
||||||
|
|
||||||
#include "core/system/demo.hpp" // for Demo
|
#include "core/system/demo.hpp" // for Demo
|
||||||
#include "game/entities/bullet.hpp" // for Bullet
|
#include "game/entities/bullet.hpp" // for Bullet
|
||||||
@@ -31,7 +32,7 @@ class Texture;
|
|||||||
struct Path;
|
struct Path;
|
||||||
|
|
||||||
namespace Difficulty {
|
namespace Difficulty {
|
||||||
enum class Code;
|
enum class Code : std::uint8_t;
|
||||||
} // namespace Difficulty
|
} // namespace Difficulty
|
||||||
|
|
||||||
// --- Clase Game: núcleo principal del gameplay ---
|
// --- Clase Game: núcleo principal del gameplay ---
|
||||||
@@ -71,7 +72,7 @@ class Game {
|
|||||||
using Players = std::vector<std::shared_ptr<Player>>;
|
using Players = std::vector<std::shared_ptr<Player>>;
|
||||||
|
|
||||||
// --- Enums ---
|
// --- Enums ---
|
||||||
enum class State {
|
enum class State : std::uint8_t {
|
||||||
FADE_IN, // Transición de entrada
|
FADE_IN, // Transición de entrada
|
||||||
ENTERING_PLAYER, // Jugador entrando
|
ENTERING_PLAYER, // Jugador entrando
|
||||||
SHOWING_GET_READY_MESSAGE, // Mostrando mensaje de preparado
|
SHOWING_GET_READY_MESSAGE, // Mostrando mensaje de preparado
|
||||||
@@ -227,7 +228,6 @@ class Game {
|
|||||||
void updatePlayers(float delta_time); // Actualiza las variables y estados de los jugadores
|
void updatePlayers(float delta_time); // Actualiza las variables y estados de los jugadores
|
||||||
void renderPlayers(); // Renderiza todos los jugadores en pantalla
|
void renderPlayers(); // Renderiza todos los jugadores en pantalla
|
||||||
auto getPlayer(Player::Id id) -> std::shared_ptr<Player>; // Obtiene un jugador por su identificador
|
auto getPlayer(Player::Id id) -> std::shared_ptr<Player>; // Obtiene un jugador por su identificador
|
||||||
static auto getController(Player::Id player_id) -> int; // Obtiene el controlador asignado a un jugador
|
|
||||||
|
|
||||||
// --- Estado de jugadores ---
|
// --- Estado de jugadores ---
|
||||||
void checkAndUpdatePlayerStatus(int active_player_index, int inactive_player_index); // Actualiza estado entre jugadores
|
void checkAndUpdatePlayerStatus(int active_player_index, int inactive_player_index); // Actualiza estado entre jugadores
|
||||||
@@ -237,9 +237,9 @@ class Game {
|
|||||||
auto allPlayersAreNotPlaying() -> bool; // Verifica si ningún jugador está activo
|
auto allPlayersAreNotPlaying() -> bool; // Verifica si ningún jugador está activo
|
||||||
|
|
||||||
// --- Colisiones de jugadores ---
|
// --- Colisiones de jugadores ---
|
||||||
void handlePlayerCollision(std::shared_ptr<Player>& player, std::shared_ptr<Balloon>& balloon); // Procesa colisión de jugador con globo
|
void handlePlayerCollision(std::shared_ptr<Player>& player, const std::shared_ptr<Balloon>& balloon); // Procesa colisión de jugador con globo
|
||||||
auto checkPlayerBalloonCollision(std::shared_ptr<Player>& player) -> std::shared_ptr<Balloon>; // Detecta colisión jugador-globo
|
auto checkPlayerBalloonCollision(std::shared_ptr<Player>& player) -> std::shared_ptr<Balloon>; // Detecta colisión jugador-globo
|
||||||
void checkPlayerItemCollision(std::shared_ptr<Player>& player); // Detecta colisión jugador-ítem
|
void checkPlayerItemCollision(std::shared_ptr<Player>& player); // Detecta colisión jugador-ítem
|
||||||
|
|
||||||
// --- Sistema de entrada (input) ---
|
// --- Sistema de entrada (input) ---
|
||||||
void checkInput(); // Gestiona toda la entrada durante el juego
|
void checkInput(); // Gestiona toda la entrada durante el juego
|
||||||
|
|||||||
@@ -178,22 +178,17 @@ void HiScoreTable::updateFade(float delta_time) {
|
|||||||
|
|
||||||
// Convierte un entero a un string con separadores de miles
|
// Convierte un entero a un string con separadores de miles
|
||||||
auto HiScoreTable::format(int number) -> std::string {
|
auto HiScoreTable::format(int number) -> std::string {
|
||||||
const std::string SEPARATOR = ".";
|
|
||||||
const std::string SCORE = std::to_string(number);
|
const std::string SCORE = std::to_string(number);
|
||||||
|
const size_t SIZE = SCORE.size();
|
||||||
|
|
||||||
auto index = static_cast<int>(SCORE.size()) - 1;
|
|
||||||
std::string result;
|
std::string result;
|
||||||
auto i = 0;
|
result.reserve(SIZE + (SIZE / 3));
|
||||||
while (index >= 0) {
|
for (size_t i = 0; i < SIZE; ++i) {
|
||||||
result = SCORE.at(index) + result;
|
if (i > 0 && (SIZE - i) % 3 == 0) {
|
||||||
index--;
|
result += '.';
|
||||||
i++;
|
|
||||||
if (i == 3) {
|
|
||||||
i = 0;
|
|
||||||
result = SEPARATOR + result;
|
|
||||||
}
|
}
|
||||||
|
result += SCORE[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,7 +210,7 @@ void HiScoreTable::createSprites() {
|
|||||||
const int FIRST_LINE = (param.game.height - SIZE) / 2;
|
const int FIRST_LINE = (param.game.height - SIZE) / 2;
|
||||||
|
|
||||||
// Crea el sprite para el texto de cabecera
|
// Crea el sprite para el texto de cabecera
|
||||||
header_ = std::make_unique<Sprite>(header_text->writeDXToTexture(Text::COLOR, Lang::getText("[HIGHSCORE_TABLE] CAPTION"), -2, background_fade_color_.INVERSE().LIGHTEN(25)));
|
header_ = std::make_unique<Sprite>(header_text->writeDXToTexture(Text::COLOR, Lang::getText("[HIGHSCORE_TABLE] CAPTION"), -2, background_fade_color_.inverse().lighten(25)));
|
||||||
header_->setPosition(param.game.game_area.center_x - (header_->getWidth() / 2), FIRST_LINE);
|
header_->setPosition(param.game.game_area.center_x - (header_->getWidth() / 2), FIRST_LINE);
|
||||||
|
|
||||||
// Crea los sprites para las entradas en la tabla de puntuaciones
|
// Crea los sprites para las entradas en la tabla de puntuaciones
|
||||||
@@ -228,13 +223,14 @@ void HiScoreTable::createSprites() {
|
|||||||
const auto SCORE = format(Options::settings.hi_score_table.at(i).score);
|
const auto SCORE = format(Options::settings.hi_score_table.at(i).score);
|
||||||
const auto NUM_DOTS = ENTRY_LENGTH - Options::settings.hi_score_table.at(i).name.size() - SCORE.size();
|
const auto NUM_DOTS = ENTRY_LENGTH - Options::settings.hi_score_table.at(i).name.size() - SCORE.size();
|
||||||
const auto* const ONE_CC = Options::settings.hi_score_table.at(i).one_credit_complete ? " }" : "";
|
const auto* const ONE_CC = Options::settings.hi_score_table.at(i).one_credit_complete ? " }" : "";
|
||||||
std::string dots;
|
const std::string DOTS(NUM_DOTS, '.');
|
||||||
for (int j = 0; std::cmp_less(j, NUM_DOTS); ++j) {
|
std::string line = TABLE_POSITION;
|
||||||
dots = dots + ".";
|
line += Options::settings.hi_score_table.at(i).name;
|
||||||
}
|
line += DOTS;
|
||||||
const auto LINE = TABLE_POSITION + Options::settings.hi_score_table.at(i).name + dots + SCORE + ONE_CC;
|
line += SCORE;
|
||||||
|
line += ONE_CC;
|
||||||
|
|
||||||
entry_names_.emplace_back(std::make_shared<PathSprite>(entry_text->writeDXToTexture(Text::SHADOW, LINE, 1, Colors::NO_COLOR_MOD, 1, Colors::SHADOW_TEXT)));
|
entry_names_.emplace_back(std::make_shared<PathSprite>(entry_text->writeDXToTexture(Text::SHADOW, line, 1, Colors::NO_COLOR_MOD, 1, Colors::SHADOW_TEXT)));
|
||||||
const int DEFAULT_POS_X = (backbuffer_width - ENTRY_WIDTH) / 2;
|
const int DEFAULT_POS_X = (backbuffer_width - ENTRY_WIDTH) / 2;
|
||||||
const int POS_X = (i < 9) ? DEFAULT_POS_X : DEFAULT_POS_X - entry_text->getCharacterSize();
|
const int POS_X = (i < 9) ? DEFAULT_POS_X : DEFAULT_POS_X - entry_text->getCharacterSize();
|
||||||
const int POS_Y = (i * SPACE_BETWEEN_LINES) + FIRST_LINE + SPACE_BETWEEN_HEADER;
|
const int POS_Y = (i * SPACE_BETWEEN_LINES) + FIRST_LINE + SPACE_BETWEEN_HEADER;
|
||||||
@@ -367,10 +363,10 @@ auto HiScoreTable::getEntryColor(int counter) -> Color {
|
|||||||
// Inicializa los colores de las entradas
|
// Inicializa los colores de las entradas
|
||||||
void HiScoreTable::iniEntryColors() {
|
void HiScoreTable::iniEntryColors() {
|
||||||
entry_colors_.clear();
|
entry_colors_.clear();
|
||||||
entry_colors_.emplace_back(background_fade_color_.INVERSE().LIGHTEN(75));
|
entry_colors_.emplace_back(background_fade_color_.inverse().lighten(75));
|
||||||
entry_colors_.emplace_back(background_fade_color_.INVERSE().LIGHTEN(50));
|
entry_colors_.emplace_back(background_fade_color_.inverse().lighten(50));
|
||||||
entry_colors_.emplace_back(background_fade_color_.INVERSE().LIGHTEN(25));
|
entry_colors_.emplace_back(background_fade_color_.inverse().lighten(25));
|
||||||
entry_colors_.emplace_back(background_fade_color_.INVERSE());
|
entry_colors_.emplace_back(background_fade_color_.inverse());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hace brillar los nombres de la tabla de records
|
// Hace brillar los nombres de la tabla de records
|
||||||
@@ -387,7 +383,7 @@ void HiScoreTable::glowEntryNames() {
|
|||||||
// Gestiona el contador
|
// Gestiona el contador
|
||||||
void HiScoreTable::updateCounter() {
|
void HiScoreTable::updateCounter() {
|
||||||
if (elapsed_time_ >= BACKGROUND_CHANGE_S && !hiscore_flags_.background_changed) {
|
if (elapsed_time_ >= BACKGROUND_CHANGE_S && !hiscore_flags_.background_changed) {
|
||||||
background_->setColor(background_fade_color_.DARKEN());
|
background_->setColor(background_fade_color_.darken());
|
||||||
background_->setAlpha(96);
|
background_->setAlpha(96);
|
||||||
hiscore_flags_.background_changed = true;
|
hiscore_flags_.background_changed = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -392,17 +392,17 @@ void Intro::initSprites() {
|
|||||||
|
|
||||||
const CardConfig CARD_CONFIGS[] = {
|
const CardConfig CARD_CONFIGS[] = {
|
||||||
// 0: Entra desde la izquierda. La 1 entra desde la derecha → sale empujada hacia la izquierda
|
// 0: Entra desde la izquierda. La 1 entra desde la derecha → sale empujada hacia la izquierda
|
||||||
{-CARD_WIDTH, Y_DEST - 20.0F, CARD_ANGLE_0, -S, S * 0.1F, -A, 0.0F, -R},
|
{.entry_x = -CARD_WIDTH, .entry_y = Y_DEST - 20.0F, .entry_angle = CARD_ANGLE_0, .exit_vx = -S, .exit_vy = S * 0.1F, .exit_ax = -A, .exit_ay = 0.0F, .exit_rotation = -R},
|
||||||
// 1: Entra desde la derecha. La 2 entra desde arriba → sale empujada hacia abajo
|
// 1: Entra desde la derecha. La 2 entra desde arriba → sale empujada hacia abajo
|
||||||
{W + CARD_WIDTH, Y_DEST + 15.0F, CARD_ANGLE_1, S * 0.15F, S, 0.0F, A, R * 1.1},
|
{.entry_x = W + CARD_WIDTH, .entry_y = Y_DEST + 15.0F, .entry_angle = CARD_ANGLE_1, .exit_vx = S * 0.15F, .exit_vy = S, .exit_ax = 0.0F, .exit_ay = A, .exit_rotation = R * 1.1},
|
||||||
// 2: Entra desde arriba. La 3 entra desde abajo → sale empujada hacia arriba
|
// 2: Entra desde arriba. La 3 entra desde abajo → sale empujada hacia arriba
|
||||||
{X_DEST + 30.0F, -CARD_HEIGHT, CARD_ANGLE_2, -S * 0.15F, -S, 0.0F, -A, -R * 0.9},
|
{.entry_x = X_DEST + 30.0F, .entry_y = -CARD_HEIGHT, .entry_angle = CARD_ANGLE_2, .exit_vx = -S * 0.15F, .exit_vy = -S, .exit_ax = 0.0F, .exit_ay = -A, .exit_rotation = -R * 0.9},
|
||||||
// 3: Entra desde abajo. La 4 entra desde arriba-izquierda → sale empujada hacia abajo-derecha
|
// 3: Entra desde abajo. La 4 entra desde arriba-izquierda → sale empujada hacia abajo-derecha
|
||||||
{X_DEST - 25.0F, H + CARD_HEIGHT, CARD_ANGLE_3, S * 0.8F, S * 0.6F, A * 0.5F, A * 0.4F, R},
|
{.entry_x = X_DEST - 25.0F, .entry_y = H + CARD_HEIGHT, .entry_angle = CARD_ANGLE_3, .exit_vx = S * 0.8F, .exit_vy = S * 0.6F, .exit_ax = A * 0.5F, .exit_ay = A * 0.4F, .exit_rotation = R},
|
||||||
// 4: Entra desde arriba-izquierda. La 5 entra desde derecha-abajo → sale empujada hacia arriba-izquierda
|
// 4: Entra desde arriba-izquierda. La 5 entra desde derecha-abajo → sale empujada hacia arriba-izquierda
|
||||||
{-CARD_WIDTH * 0.5F, -CARD_HEIGHT, CARD_ANGLE_4, -S * 0.7F, -S * 0.5F, -A * 0.5F, -A * 0.3F, -R * 1.2},
|
{.entry_x = -CARD_WIDTH * 0.5F, .entry_y = -CARD_HEIGHT, .entry_angle = CARD_ANGLE_4, .exit_vx = -S * 0.7F, .exit_vy = -S * 0.5F, .exit_ax = -A * 0.5F, .exit_ay = -A * 0.3F, .exit_rotation = -R * 1.2},
|
||||||
// 5: Entra desde la derecha-abajo. Última: sale hacia la izquierda suave (viento)
|
// 5: Entra desde la derecha-abajo. Última: sale hacia la izquierda suave (viento)
|
||||||
{W + CARD_WIDTH, H * 0.6F, CARD_ANGLE_5, -S * 0.6F, -S * 0.1F, -A * 0.5F, 0.0F, -R * 0.7},
|
{.entry_x = W + CARD_WIDTH, .entry_y = H * 0.6F, .entry_angle = CARD_ANGLE_5, .exit_vx = -S * 0.6F, .exit_vy = -S * 0.1F, .exit_ax = -A * 0.5F, .exit_ay = 0.0F, .exit_rotation = -R * 0.7},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Inicializa los CardSprites
|
// Inicializa los CardSprites
|
||||||
@@ -534,15 +534,15 @@ void Intro::updatePostState() {
|
|||||||
if (ELAPSED_TIME >= POST_BG_STOP_DELAY_S) {
|
if (ELAPSED_TIME >= POST_BG_STOP_DELAY_S) {
|
||||||
tiled_bg_->stopGracefully();
|
tiled_bg_->stopGracefully();
|
||||||
|
|
||||||
if (!bg_color_.IS_EQUAL_TO(param.title.bg_color)) {
|
if (!bg_color_.isEqualTo(param.title.bg_color)) {
|
||||||
bg_color_ = bg_color_.APPROACH_TO(param.title.bg_color, 1);
|
bg_color_ = bg_color_.approachTo(param.title.bg_color, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
tiled_bg_->setColor(bg_color_);
|
tiled_bg_->setColor(bg_color_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cambia de estado si el fondo se ha detenido y recuperado el color
|
// Cambia de estado si el fondo se ha detenido y recuperado el color
|
||||||
if (tiled_bg_->isStopped() && bg_color_.IS_EQUAL_TO(param.title.bg_color)) {
|
if (tiled_bg_->isStopped() && bg_color_.isEqualTo(param.title.bg_color)) {
|
||||||
post_state_ = PostState::END;
|
post_state_ = PostState::END;
|
||||||
state_start_time_ = SDL_GetTicks() / 1000.0F;
|
state_start_time_ = SDL_GetTicks() / 1000.0F;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h> // Para Uint32, Uint64
|
#include <SDL3/SDL.h> // Para Uint32, Uint64
|
||||||
|
|
||||||
#include <memory> // Para unique_ptr
|
#include <cstdint> // Para std::uint8_t
|
||||||
#include <vector> // Para vector
|
#include <memory> // Para unique_ptr
|
||||||
|
#include <vector> // Para vector
|
||||||
|
|
||||||
#include "core/rendering/sprite/card_sprite.hpp" // Para CardSprite
|
#include "core/rendering/sprite/card_sprite.hpp" // Para CardSprite
|
||||||
#include "core/rendering/tiled_bg.hpp" // Para TiledBG
|
#include "core/rendering/tiled_bg.hpp" // Para TiledBG
|
||||||
@@ -79,12 +80,12 @@ class Intro {
|
|||||||
static constexpr double CARD_ANGLE_5 = -7.0;
|
static constexpr double CARD_ANGLE_5 = -7.0;
|
||||||
|
|
||||||
// --- Estados internos ---
|
// --- Estados internos ---
|
||||||
enum class State {
|
enum class State : std::uint8_t {
|
||||||
SCENES,
|
SCENES,
|
||||||
POST,
|
POST,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class PostState {
|
enum class PostState : std::uint8_t {
|
||||||
STOP_BG,
|
STOP_BG,
|
||||||
END,
|
END,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,6 +14,6 @@ class Preload {
|
|||||||
~Preload() = default;
|
~Preload() = default;
|
||||||
|
|
||||||
// --- Callbacks para el bucle SDL_MAIN_USE_CALLBACKS ---
|
// --- Callbacks para el bucle SDL_MAIN_USE_CALLBACKS ---
|
||||||
void iterate(); // Repinta la barra de progreso
|
static void iterate(); // Repinta la barra de progreso
|
||||||
void handleEvent(const SDL_Event& event); // Detecta pulsación en modo wait_for_input
|
static void handleEvent(const SDL_Event& event); // Detecta pulsación en modo wait_for_input
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,14 +2,12 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h> // Para SDL_GetTicks, SDL_Event, SDL_Keycode, SDL_PollEvent, SDLK_A, SDLK_C, SDLK_D, SDLK_F, SDLK_S, SDLK_V, SDLK_X, SDLK_Z, SDL_EventType, Uint64
|
#include <SDL3/SDL.h> // Para SDL_GetTicks, SDL_Event, SDL_Keycode, SDL_PollEvent, SDLK_A, SDLK_C, SDLK_D, SDLK_F, SDLK_S, SDLK_V, SDLK_X, SDLK_Z, SDL_EventType, Uint64
|
||||||
|
|
||||||
#include <ranges> // Para __find_if_fn, find_if
|
|
||||||
#include <string> // Para basic_string, char_traits, operator+, to_string, string
|
#include <string> // Para basic_string, char_traits, operator+, to_string, string
|
||||||
#include <vector> // Para vector
|
#include <vector> // Para vector
|
||||||
|
|
||||||
#include "core/audio/audio.hpp" // Para Audio
|
#include "core/audio/audio.hpp" // Para Audio
|
||||||
#include "core/input/global_inputs.hpp" // Para check
|
#include "core/input/global_inputs.hpp" // Para check
|
||||||
#include "core/input/input.hpp" // Para Input
|
#include "core/input/input.hpp" // Para Input
|
||||||
#include "core/input/input_types.hpp" // Para InputAction
|
|
||||||
#include "core/locale/lang.hpp" // Para getText
|
#include "core/locale/lang.hpp" // Para getText
|
||||||
#include "core/rendering/fade.hpp" // Para Fade
|
#include "core/rendering/fade.hpp" // Para Fade
|
||||||
#include "core/rendering/screen.hpp" // Para Screen
|
#include "core/rendering/screen.hpp" // Para Screen
|
||||||
@@ -245,9 +243,6 @@ void Title::run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reinicia el contador interno
|
|
||||||
void Title::resetCounter() { counter_time_ = 0.0F; }
|
|
||||||
|
|
||||||
// Intercambia la asignación de mandos a los jugadores
|
// Intercambia la asignación de mandos a los jugadores
|
||||||
void Title::swapControllers() {
|
void Title::swapControllers() {
|
||||||
if (Input::get()->getNumGamepads() == 0) {
|
if (Input::get()->getNumGamepads() == 0) {
|
||||||
@@ -303,6 +298,9 @@ void Title::updateFade() {
|
|||||||
Section::options = Section::Options::GAME_PLAY_BOTH;
|
Section::options = Section::Options::GAME_PLAY_BOTH;
|
||||||
Audio::get()->stopMusic();
|
Audio::get()->stopMusic();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break; // COMBO és un bitmask 2-bit (0..3); arribar ací és impossible.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h> // Para SDL_Keycode, SDL_Event, Uint64
|
#include <SDL3/SDL.h> // Para SDL_Keycode, SDL_Event, Uint64
|
||||||
|
|
||||||
|
#include <cstdint> // Para std::uint8_t
|
||||||
#include <memory> // Para shared_ptr, unique_ptr
|
#include <memory> // Para shared_ptr, unique_ptr
|
||||||
#include <string_view> // Para string_view
|
#include <string_view> // Para string_view
|
||||||
#include <vector> // Para vector
|
#include <vector> // Para vector
|
||||||
@@ -69,7 +70,7 @@ class Title {
|
|||||||
static constexpr bool ALLOW_TITLE_ANIMATION_SKIP = false; // Permite saltar la animación del título
|
static constexpr bool ALLOW_TITLE_ANIMATION_SKIP = false; // Permite saltar la animación del título
|
||||||
|
|
||||||
// --- Enums ---
|
// --- Enums ---
|
||||||
enum class State {
|
enum class State : std::uint8_t {
|
||||||
LOGO_ANIMATING, // El logo está animándose
|
LOGO_ANIMATING, // El logo está animándose
|
||||||
LOGO_FINISHED, // El logo ha terminado de animarse
|
LOGO_FINISHED, // El logo ha terminado de animarse
|
||||||
START_HAS_BEEN_PRESSED, // Se ha pulsado el botón de start
|
START_HAS_BEEN_PRESSED, // Se ha pulsado el botón de start
|
||||||
@@ -107,7 +108,6 @@ class Title {
|
|||||||
auto calculateDeltaTime() -> float; // Calcula el tiempo transcurrido desde el último frame
|
auto calculateDeltaTime() -> float; // Calcula el tiempo transcurrido desde el último frame
|
||||||
void updateState(float delta_time); // Actualiza el estado actual del título
|
void updateState(float delta_time); // Actualiza el estado actual del título
|
||||||
void setState(State state); // Cambia el estado del título
|
void setState(State state); // Cambia el estado del título
|
||||||
void resetCounter(); // Reinicia el contador interno
|
|
||||||
|
|
||||||
// --- Entrada de usuario ---
|
// --- Entrada de usuario ---
|
||||||
void checkEvents(); // Comprueba los eventos
|
void checkEvents(); // Comprueba los eventos
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm> // Para max, clamp
|
#include <algorithm> // Para max, clamp
|
||||||
|
#include <cmath> // Para std::lround
|
||||||
#include <cstddef> // Para size_t
|
#include <cstddef> // Para size_t
|
||||||
|
#include <cstdint> // Para std::uint8_t
|
||||||
#include <functional> // Para function
|
#include <functional> // Para function
|
||||||
|
#include <numeric> // Para accumulate
|
||||||
#include <string> // Para allocator, string, basic_string, to_string, operator==, char_traits
|
#include <string> // Para allocator, string, basic_string, to_string, operator==, char_traits
|
||||||
#include <utility> // Para move
|
#include <utility> // Para move
|
||||||
#include <vector> // Para vector
|
#include <vector> // Para vector
|
||||||
@@ -15,7 +18,7 @@
|
|||||||
class MenuOption {
|
class MenuOption {
|
||||||
public:
|
public:
|
||||||
// --- Enums ---
|
// --- Enums ---
|
||||||
enum class Behavior {
|
enum class Behavior : std::uint8_t {
|
||||||
ADJUST, // Solo puede ajustar valor (como IntOption, BoolOption, ListOption)
|
ADJUST, // Solo puede ajustar valor (como IntOption, BoolOption, ListOption)
|
||||||
SELECT, // Solo puede ejecutar acción (como ActionOption, FolderOption)
|
SELECT, // Solo puede ejecutar acción (como ActionOption, FolderOption)
|
||||||
BOTH // Puede tanto ajustar como ejecutar acción (como ActionListOption)
|
BOTH // Puede tanto ajustar como ejecutar acción (como ActionListOption)
|
||||||
@@ -106,6 +109,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>(std::lround(*linked_variable_ * 100.0F));
|
||||||
|
return std::to_string(pct);
|
||||||
|
}
|
||||||
|
void adjustValue(bool adjust_up) override {
|
||||||
|
int current = static_cast<int>(std::lround(*linked_variable_ * 100.0F));
|
||||||
|
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)
|
||||||
@@ -140,11 +177,7 @@ class ListOption : public MenuOption {
|
|||||||
setter_(value_list_[list_index_]);
|
setter_(value_list_[list_index_]);
|
||||||
}
|
}
|
||||||
auto getMaxValueWidth(Text* text_renderer) const -> int override {
|
auto getMaxValueWidth(Text* text_renderer) const -> int override {
|
||||||
int max_w = 0;
|
return std::accumulate(value_list_.begin(), value_list_.end(), 0, [text_renderer](int max_w, const auto& val) { return std::max(max_w, text_renderer->length(val, -2)); });
|
||||||
for (const auto& val : value_list_) {
|
|
||||||
max_w = std::max(max_w, text_renderer->length(val, -2));
|
|
||||||
}
|
|
||||||
return max_w;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ void MenuRenderer::render(const ServiceMenu* menu_state) {
|
|||||||
SDL_RenderFillRect(Screen::get()->getRenderer(), &rect_);
|
SDL_RenderFillRect(Screen::get()->getRenderer(), &rect_);
|
||||||
|
|
||||||
// Dibuja el borde
|
// Dibuja el borde
|
||||||
const Color BORDER_COLOR = param.service_menu.title_color.DARKEN();
|
const Color BORDER_COLOR = param.service_menu.title_color.darken();
|
||||||
SDL_SetRenderDrawColor(Screen::get()->getRenderer(), BORDER_COLOR.r, BORDER_COLOR.g, BORDER_COLOR.b, 255);
|
SDL_SetRenderDrawColor(Screen::get()->getRenderer(), BORDER_COLOR.r, BORDER_COLOR.g, BORDER_COLOR.b, 255);
|
||||||
SDL_RenderRect(Screen::get()->getRenderer(), &rect_);
|
SDL_RenderRect(Screen::get()->getRenderer(), &rect_);
|
||||||
SDL_RenderRect(Screen::get()->getRenderer(), &border_rect_);
|
SDL_RenderRect(Screen::get()->getRenderer(), &border_rect_);
|
||||||
@@ -111,7 +111,7 @@ void MenuRenderer::render(const ServiceMenu* menu_state) {
|
|||||||
// Dibuja las opciones
|
// Dibuja las opciones
|
||||||
y = options_y_;
|
y = options_y_;
|
||||||
const auto& option_pairs = menu_state->getOptionPairs();
|
const auto& option_pairs = menu_state->getOptionPairs();
|
||||||
const float ROW_HEIGHT = static_cast<float>(options_height_ + options_padding_);
|
const auto ROW_HEIGHT = static_cast<float>(options_height_ + options_padding_);
|
||||||
|
|
||||||
for (size_t i = 0; i < option_pairs.size(); ++i) {
|
for (size_t i = 0; i < option_pairs.size(); ++i) {
|
||||||
const bool IS_SELECTED = (i == menu_state->getSelectedIndex());
|
const bool IS_SELECTED = (i == menu_state->getSelectedIndex());
|
||||||
@@ -381,9 +381,7 @@ void MenuRenderer::updatePosition() {
|
|||||||
// Resto de métodos (sin cambios significativos)
|
// Resto de métodos (sin cambios significativos)
|
||||||
|
|
||||||
void MenuRenderer::precalculateMenuWidths(const std::vector<std::unique_ptr<MenuOption>>& all_options, const ServiceMenu* menu_state) { // NOLINT(readability-named-parameter)
|
void MenuRenderer::precalculateMenuWidths(const std::vector<std::unique_ptr<MenuOption>>& all_options, const ServiceMenu* menu_state) { // NOLINT(readability-named-parameter)
|
||||||
for (int& w : group_menu_widths_) {
|
std::ranges::fill(group_menu_widths_, ServiceMenu::MIN_WIDTH);
|
||||||
w = ServiceMenu::MIN_WIDTH;
|
|
||||||
}
|
|
||||||
for (int group = 0; group < 5; ++group) {
|
for (int group = 0; group < 5; ++group) {
|
||||||
auto sg = static_cast<ServiceMenu::SettingsGroup>(group);
|
auto sg = static_cast<ServiceMenu::SettingsGroup>(group);
|
||||||
int max_option_width = 0;
|
int max_option_width = 0;
|
||||||
@@ -433,33 +431,6 @@ auto MenuRenderer::getAnimatedSelectedColor() const -> Color {
|
|||||||
static auto color_cycle_ = Colors::generateMirroredCycle(param.service_menu.selected_color, ColorCycleStyle::HUE_WAVE);
|
static auto color_cycle_ = Colors::generateMirroredCycle(param.service_menu.selected_color, ColorCycleStyle::HUE_WAVE);
|
||||||
return color_cycle_.at(color_counter_ % color_cycle_.size());
|
return color_cycle_.at(color_counter_ % color_cycle_.size());
|
||||||
}
|
}
|
||||||
auto MenuRenderer::setRect(SDL_FRect rect) -> SDL_FRect {
|
|
||||||
border_rect_ = {.x = rect.x - 1, .y = rect.y + 1, .w = rect.w + 2, .h = rect.h - 2};
|
|
||||||
return rect;
|
|
||||||
}
|
|
||||||
auto MenuRenderer::getTruncatedValueWidth(const std::string& value, int available_width) const -> int {
|
|
||||||
int value_width = element_text_->length(value, -2);
|
|
||||||
if (value_width <= available_width) {
|
|
||||||
return value_width;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculamos cuántos caracteres podemos mostrar más los puntos suspensivos
|
|
||||||
// Estimamos el ancho de los puntos suspensivos como 3 caracteres promedio
|
|
||||||
int ellipsis_width = element_text_->length("...", -2);
|
|
||||||
int available_for_text = available_width - ellipsis_width;
|
|
||||||
|
|
||||||
if (available_for_text <= 0) {
|
|
||||||
return ellipsis_width; // Solo mostramos los puntos suspensivos
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculamos aproximadamente cuántos caracteres caben
|
|
||||||
float char_width = static_cast<float>(value_width) / value.length();
|
|
||||||
auto max_chars = static_cast<size_t>(available_for_text / char_width);
|
|
||||||
|
|
||||||
// Verificamos el ancho real del texto truncado
|
|
||||||
std::string truncated = truncateWithEllipsis(value, max_chars);
|
|
||||||
return element_text_->length(truncated, -2);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto MenuRenderer::getTruncatedValue(const std::string& value, int available_width) const -> std::string {
|
auto MenuRenderer::getTruncatedValue(const std::string& value, int available_width) const -> std::string {
|
||||||
int value_width = element_text_->length(value, -2);
|
int value_width = element_text_->length(value, -2);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -17,7 +18,7 @@ class Text;
|
|||||||
class MenuRenderer {
|
class MenuRenderer {
|
||||||
public:
|
public:
|
||||||
// --- Nuevo: Enum para el modo de posicionamiento ---
|
// --- Nuevo: Enum para el modo de posicionamiento ---
|
||||||
enum class PositionMode {
|
enum class PositionMode : std::uint8_t {
|
||||||
CENTERED, // La ventana se centra en el punto especificado
|
CENTERED, // La ventana se centra en el punto especificado
|
||||||
FIXED // La esquina superior izquierda coincide con el punto
|
FIXED // La esquina superior izquierda coincide con el punto
|
||||||
};
|
};
|
||||||
@@ -82,8 +83,10 @@ class MenuRenderer {
|
|||||||
// --- Estructuras de Animación ---
|
// --- Estructuras de Animación ---
|
||||||
struct ResizeAnimation {
|
struct ResizeAnimation {
|
||||||
bool active = false;
|
bool active = false;
|
||||||
float start_width, start_height;
|
float start_width = 0.0F;
|
||||||
float target_width, target_height;
|
float start_height = 0.0F;
|
||||||
|
float target_width = 0.0F;
|
||||||
|
float target_height = 0.0F;
|
||||||
float elapsed = 0.0F;
|
float elapsed = 0.0F;
|
||||||
float duration = 0.2F;
|
float duration = 0.2F;
|
||||||
|
|
||||||
@@ -92,12 +95,13 @@ class MenuRenderer {
|
|||||||
} resize_animation_;
|
} resize_animation_;
|
||||||
|
|
||||||
struct ShowHideAnimation {
|
struct ShowHideAnimation {
|
||||||
enum class Type { NONE,
|
enum class Type : std::uint8_t { NONE,
|
||||||
SHOWING,
|
SHOWING,
|
||||||
HIDING };
|
HIDING };
|
||||||
Type type = Type::NONE;
|
Type type = Type::NONE;
|
||||||
bool active = false;
|
bool active = false;
|
||||||
float target_width, target_height;
|
float target_width = 0.0F;
|
||||||
|
float target_height = 0.0F;
|
||||||
float elapsed = 0.0F;
|
float elapsed = 0.0F;
|
||||||
float duration = 0.25F;
|
float duration = 0.25F;
|
||||||
|
|
||||||
@@ -140,8 +144,6 @@ class MenuRenderer {
|
|||||||
[[nodiscard]] auto getMenuWidthForGroup(ServiceMenu::SettingsGroup group) const -> int;
|
[[nodiscard]] auto getMenuWidthForGroup(ServiceMenu::SettingsGroup group) const -> int;
|
||||||
[[nodiscard]] auto getAnimatedSelectedColor() const -> Color;
|
[[nodiscard]] auto getAnimatedSelectedColor() const -> Color;
|
||||||
void updateColorCounter();
|
void updateColorCounter();
|
||||||
auto setRect(SDL_FRect rect) -> SDL_FRect;
|
|
||||||
[[nodiscard]] auto getTruncatedValueWidth(const std::string& value, int available_width) const -> int;
|
|
||||||
[[nodiscard]] auto getTruncatedValue(const std::string& value, int available_width) const -> std::string;
|
[[nodiscard]] auto getTruncatedValue(const std::string& value, int available_width) const -> std::string;
|
||||||
[[nodiscard]] static auto easeOut(float t) -> float;
|
[[nodiscard]] static auto easeOut(float t) -> float;
|
||||||
[[nodiscard]] auto shouldShowContent() const -> bool;
|
[[nodiscard]] auto shouldShowContent() const -> bool;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user