Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6246b5d89d | |||
| 34a41ad25c | |||
| 20b9a95619 | |||
| 513eacf356 | |||
| 5889df2a47 | |||
| 7f703390f9 | |||
| 1bb0ebdef8 | |||
| 5fec0110b3 | |||
| 55caef3210 | |||
| 007c1d3554 | |||
| 28606a9fe1 | |||
| 294e665b11 | |||
| 0faa605ad9 | |||
| c3534ace9c | |||
| 517bc2caa1 | |||
| f9b0f64b81 | |||
| e0498d642d | |||
| ccdf9732d1 | |||
| 1451327fcc | |||
| a035fecb04 | |||
| 9d70138855 | |||
| dfe0a3d4e6 | |||
| 66c3e0089c | |||
| 86323a0e56 | |||
| 58cacf7bda | |||
| 978cbcc9fc | |||
| fb023df1e1 | |||
| 555f347375 | |||
| 85a47c1a2b | |||
| 06d4712493 | |||
| 18c4d6032d | |||
| 9365f80e8b | |||
| 4bd07216f3 |
@@ -2,8 +2,8 @@ BasedOnStyle: Google
|
|||||||
IndentWidth: 4
|
IndentWidth: 4
|
||||||
NamespaceIndentation: All
|
NamespaceIndentation: All
|
||||||
IndentAccessModifiers: false
|
IndentAccessModifiers: false
|
||||||
ColumnLimit: 0 # Sin limite de longitud de linea
|
ColumnLimit: 0 # Sin límite de longitud de línea
|
||||||
BreakBeforeBraces: Attach # Llaves en la misma linea
|
BreakBeforeBraces: Attach # Llaves en la misma línea
|
||||||
AllowShortIfStatementsOnASingleLine: true
|
AllowShortIfStatementsOnASingleLine: true
|
||||||
AllowShortBlocksOnASingleLine: true
|
AllowShortBlocksOnASingleLine: true
|
||||||
AllowShortFunctionsOnASingleLine: All
|
AllowShortFunctionsOnASingleLine: All
|
||||||
|
|||||||
29
.clang-tidy
29
.clang-tidy
@@ -2,28 +2,23 @@ Checks:
|
|||||||
- readability-*
|
- readability-*
|
||||||
- modernize-*
|
- modernize-*
|
||||||
- performance-*
|
- performance-*
|
||||||
- bugprone-unchecked-optional-access
|
- bugprone-*
|
||||||
- bugprone-sizeof-expression
|
|
||||||
- bugprone-suspicious-missing-comma
|
|
||||||
- bugprone-suspicious-index
|
|
||||||
- bugprone-undefined-memory-manipulation
|
|
||||||
- bugprone-use-after-move
|
|
||||||
- bugprone-out-of-bound-access
|
|
||||||
- -readability-identifier-length
|
- -readability-identifier-length
|
||||||
- -readability-magic-numbers
|
- -readability-magic-numbers
|
||||||
- -bugprone-narrowing-conversions
|
|
||||||
- -performance-enum-size
|
|
||||||
- -performance-inefficient-string-concatenation
|
|
||||||
- -bugprone-integer-division
|
- -bugprone-integer-division
|
||||||
- -bugprone-easily-swappable-parameters
|
- -bugprone-easily-swappable-parameters
|
||||||
|
- -bugprone-narrowing-conversions
|
||||||
- -modernize-avoid-c-arrays,-warnings-as-errors
|
- -modernize-avoid-c-arrays,-warnings-as-errors
|
||||||
|
|
||||||
WarningsAsErrors: '*'
|
WarningsAsErrors: '*'
|
||||||
# Excluye jail_audio.hpp, stb_image.h y stb_vorbis.c del analisis
|
# Solo headers del propio código fuente (external/ y spv/ tienen su propio .clang-tidy dummy)
|
||||||
HeaderFilterRegex: 'source/(?!jail_audio\.hpp|stb_image\.h|stb_vorbis\.c).*'
|
HeaderFilterRegex: 'source/.*'
|
||||||
FormatStyle: file
|
FormatStyle: file
|
||||||
|
|
||||||
CheckOptions:
|
CheckOptions:
|
||||||
|
# bugprone-empty-catch: aceptar catches vacíos marcados con @INTENTIONAL en un comentario
|
||||||
|
- { key: bugprone-empty-catch.IgnoreCatchWithKeywords, value: '@INTENTIONAL' }
|
||||||
|
|
||||||
# Variables locales en snake_case
|
# Variables locales en snake_case
|
||||||
- { key: readability-identifier-naming.VariableCase, value: lower_case }
|
- { key: readability-identifier-naming.VariableCase, value: lower_case }
|
||||||
|
|
||||||
@@ -35,17 +30,17 @@ CheckOptions:
|
|||||||
- { key: readability-identifier-naming.ProtectedMemberCase, value: lower_case }
|
- { key: readability-identifier-naming.ProtectedMemberCase, value: lower_case }
|
||||||
- { key: readability-identifier-naming.ProtectedMemberSuffix, value: _ }
|
- { key: readability-identifier-naming.ProtectedMemberSuffix, value: _ }
|
||||||
|
|
||||||
# Miembros publicos en snake_case (sin sufijo)
|
# Miembros públicos en snake_case (sin sufijo)
|
||||||
- { key: readability-identifier-naming.PublicMemberCase, value: lower_case }
|
- { key: readability-identifier-naming.PublicMemberCase, value: lower_case }
|
||||||
|
|
||||||
# Namespaces en CamelCase
|
# Namespaces en CamelCase
|
||||||
- { key: readability-identifier-naming.NamespaceCase, value: CamelCase }
|
- { key: readability-identifier-naming.NamespaceCase, value: CamelCase }
|
||||||
|
|
||||||
# Variables estaticas privadas como miembros privados
|
# 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 estaticas sin sufijo
|
# Constantes estáticas sin sufijo
|
||||||
- { key: readability-identifier-naming.StaticConstantCase, value: UPPER_CASE }
|
- { key: readability-identifier-naming.StaticConstantCase, value: UPPER_CASE }
|
||||||
|
|
||||||
# Constantes globales en UPPER_CASE
|
# Constantes globales en UPPER_CASE
|
||||||
@@ -71,7 +66,7 @@ CheckOptions:
|
|||||||
# Valores de enums en UPPER_CASE
|
# Valores de enums en UPPER_CASE
|
||||||
- { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE }
|
- { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE }
|
||||||
|
|
||||||
# Metodos en camelBack (sin sufijos)
|
# Métodos en camelBack (sin sufijos)
|
||||||
- { 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 }
|
||||||
@@ -80,5 +75,5 @@ CheckOptions:
|
|||||||
# Funciones en camelBack
|
# Funciones en camelBack
|
||||||
- { key: readability-identifier-naming.FunctionCase, value: camelBack }
|
- { key: readability-identifier-naming.FunctionCase, value: camelBack }
|
||||||
|
|
||||||
# Parametros en lower_case
|
# Parámetros en lower_case
|
||||||
- { key: readability-identifier-naming.ParameterCase, value: lower_case }
|
- { key: readability-identifier-naming.ParameterCase, value: lower_case }
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,7 +1,6 @@
|
|||||||
.vscode
|
.vscode
|
||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
data/config/config.txt
|
|
||||||
*.DS_Store
|
*.DS_Store
|
||||||
thumbs.db
|
thumbs.db
|
||||||
*.exe
|
*.exe
|
||||||
@@ -14,3 +13,5 @@ thumbs.db
|
|||||||
coffee_crisis
|
coffee_crisis
|
||||||
coffee_crisis_debug
|
coffee_crisis_debug
|
||||||
release/windows/coffee.res
|
release/windows/coffee.res
|
||||||
|
resources.pack
|
||||||
|
tools/pack_resources/pack_resources
|
||||||
|
|||||||
110
CLAUDE.md
110
CLAUDE.md
@@ -4,67 +4,90 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
Coffee Crisis is a C++20 arcade game built with SDL2. The player controls a character defending the UPV (university) from bouncing coffee-ball enemies across 10 stages. Supports 1-2 players, keyboard and gamepad input, and multiple languages (Spanish, Basque, English).
|
Coffee Crisis is a C++20 arcade game built with SDL3. The player controls a character defending the UPV (university) from bouncing coffee-ball enemies across 10 stages. Supports 1-2 players, keyboard and gamepad input, and multiple languages (Spanish, Basque, English).
|
||||||
|
|
||||||
## Build Commands
|
## Build Commands
|
||||||
|
|
||||||
Dependencies: `libsdl2-dev` and `g++` (Linux) or `clang++` (macOS).
|
Dependencies: `libsdl3-dev` and `g++` (Linux) or `clang++` (macOS). Build system is CMake (driven by `Makefile` wrappers).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Linux
|
make # Release build
|
||||||
make linux # Release build → ./coffee_crisis
|
make debug # Debug build (defines DEBUG and PAUSE)
|
||||||
make linux_debug # Debug build (defines DEBUG and PAUSE) → ./coffee_crisis_debug
|
make release # Empaqueta .tar.gz / .dmg / .zip segons SO
|
||||||
|
make pack # Regenera resources.pack
|
||||||
# macOS
|
make compile_shaders # Compila shaders GLSL → headers SPIR-V (requereix glslc)
|
||||||
make macos # Release build with clang++
|
make controllerdb # Descarga gamecontrollerdb.txt
|
||||||
make macos_debug # Debug build
|
make format # clang-format -i
|
||||||
|
make tidy # clang-tidy
|
||||||
# Windows (MinGW)
|
make cppcheck # cppcheck
|
||||||
make windows # Release build → coffee_crisis.exe
|
|
||||||
make windows_debug # Debug build
|
|
||||||
|
|
||||||
# Release packaging
|
|
||||||
make linux_release # Builds and creates .tar.gz
|
|
||||||
make macos_release # Builds Intel + Apple Silicon .dmg files
|
|
||||||
make windows_release # Builds and creates .zip
|
|
||||||
```
|
```
|
||||||
|
|
||||||
There is also a CMakeLists.txt available as an alternative build system.
|
|
||||||
|
|
||||||
There are no tests or linter configured.
|
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
All source code is in `source/`. The game uses a section-based architecture controlled by the **Director** class:
|
Source layout (alineat amb la resta de projectes germans):
|
||||||
|
|
||||||
- **Director** (`director.h/cpp`): Top-level controller. Initializes SDL, manages the window/renderer, and runs sections in sequence: Logo → Intro → Title → Game → Quit. Owns all shared objects (Screen, Input, Lang, Asset).
|
```
|
||||||
- **Game** (`game.h/cpp`): Core gameplay logic. Manages players, balloons (enemies), bullets, items, stages, menace level, and collision detection. Contains its own update/render loop plus sub-loops for pause and game over screens.
|
source/
|
||||||
- **Screen** (`screen.h/cpp`): Rendering abstraction. Manages a virtual canvas (256×192) that gets scaled to the actual window. Handles fullscreen/windowed modes, border rendering, and fade effects.
|
├── main.cpp
|
||||||
- **Input** (`input.h/cpp`): Abstracts keyboard and gamepad input.
|
├── core/
|
||||||
- **Asset** (`asset.h/cpp`): Resource file index. Files are registered with `add()` and retrieved by name with `get()`. All paths are relative to the executable.
|
│ ├── audio/ jail_audio.hpp
|
||||||
- **Lang** (`lang.h/cpp`): i18n system loading text strings from files in `data/lang/`.
|
│ ├── input/ input.*, mouse.*
|
||||||
|
│ ├── locale/ lang.*
|
||||||
|
│ ├── rendering/ screen, fade, text, writer, texture, sprite + animated/moving/smart
|
||||||
|
│ │ ├── shader_backend.hpp (interfície abstracta de post-procesado)
|
||||||
|
│ │ └── sdl3gpu/ (pipeline SDL3 GPU)
|
||||||
|
│ │ ├── sdl3gpu_shader.* (implementació del backend GPU)
|
||||||
|
│ │ └── spv/ (headers SPIR-V generats — protegits amb dummies `.clang-*`)
|
||||||
|
│ ├── resources/ asset, resource, resource_pack, resource_loader, resource_helper
|
||||||
|
│ └── system/ director
|
||||||
|
├── game/
|
||||||
|
│ ├── defaults.hpp (constants de gameplay: block size, canvas, áreas, colors)
|
||||||
|
│ ├── game.* (hub de gameplay)
|
||||||
|
│ ├── entities/ player, balloon, bullet, item
|
||||||
|
│ ├── scenes/ logo, intro, title, instructions
|
||||||
|
│ └── ui/ menu
|
||||||
|
├── utils/
|
||||||
|
│ ├── defines.hpp (macros de build)
|
||||||
|
│ └── utils.* (helpers, enum de dificultat, circle_t, ...)
|
||||||
|
└── external/ (stb_image, stb_vorbis — protegits amb dummies `.clang-*`)
|
||||||
|
```
|
||||||
|
|
||||||
### Sprite hierarchy
|
Flux general controlat per la classe **Director** (`core/system/director.h`): inicialitza SDL, finestra/renderer i executa seccions en seqüència **Logo → Intro → Title → Game → Quit**. Les classes principals:
|
||||||
|
|
||||||
- **Sprite** → base class for drawing from a PNG spritesheet
|
- **Game** (`game/game.h`): gameplay nuclear. Gestiona jugadors, balloons, bullets, items, stages, nivell d'amenaça i col·lisions. Té el seu bucle d'update/render i sub-bucles per pausa i game-over.
|
||||||
- **AnimatedSprite** → extends Sprite with frame-based animation (loaded from `.ani` files)
|
- **Screen** (`core/rendering/screen.h`): abstracció de render. Canvas virtual 256×192 escalat a la finestra. Fullscreen/windowed, borders, fades.
|
||||||
- **MovingSprite** → sprite with movement
|
- **Input** (`core/input/input.h`): abstracció de teclat i gamepad.
|
||||||
- **SmartSprite** → sprite with autonomous behavior (score popups, thrown items)
|
- **Asset** (`core/resources/asset.h`): índex de fitxers de recurs (`add`/`get` per nom).
|
||||||
|
- **Lang** (`core/locale/lang.h`): i18n, carrega strings des de `data/lang/`.
|
||||||
|
|
||||||
### Game entities
|
### Sprite hierarchy (`core/rendering/`)
|
||||||
|
|
||||||
- **Player** (`player.h/cpp`): Player character state and rendering
|
- **Sprite** → base per dibuixar des d'un spritesheet PNG
|
||||||
- **Balloon** (`balloon.h/cpp`): Enemy entities with multiple types and split-on-pop behavior
|
- **AnimatedSprite** → afegeix animació per frames (arxius `.ani`)
|
||||||
- **Bullet** (`bullet.h/cpp`): Projectiles fired by the player (left/center/right)
|
- **MovingSprite** → sprite amb posició/velocitat
|
||||||
- **Item** (`item.h/cpp`): Collectible items (points, clock, coffee, power-ups)
|
- **SmartSprite** → sprite autònom (score popups, objectes llençats)
|
||||||
|
|
||||||
### Audio
|
### Audio
|
||||||
|
|
||||||
**jail_audio** (`jail_audio.h/cpp`): Custom audio library wrapping SDL2 audio. Uses stb_vorbis for OGG decoding. Provides `JA_*` functions for music and sound effects with channel-based mixing.
|
**jail_audio** (`core/audio/jail_audio.hpp`): wrapper audio SDL3 first-party. Usa stb_vorbis per OGG. API `JA_*` per música i efectes amb mesclat per canals.
|
||||||
|
|
||||||
### Key constants
|
### GPU / shaders (post-procesado)
|
||||||
|
|
||||||
Defined in `const.h`: block size (8px), virtual canvas (256×192), play area bounds, section/subsection IDs, and color definitions.
|
Pipeline SDL3 GPU portat de `coffee_crisis_arcade_edition`. El canvas 256×192 es pot passar per un backend GPU que aplica PostFX (vinyeta, scanlines, chroma, gamma, mask, curvatura, bleeding, flicker) o CrtPi (scanlines continues amb bloom). Fallback transparent al `SDL_Renderer` clàssic si la GPU falla o si es desactiva.
|
||||||
|
|
||||||
|
- **Interfície**: `core/rendering/shader_backend.hpp` (`Rendering::ShaderBackend`).
|
||||||
|
- **Implementació**: `core/rendering/sdl3gpu/sdl3gpu_shader.*` + shaders GLSL a `data/shaders/` compilats a `spv/*_spv.h` via `glslc` (o precompilats si no hi ha `glslc`).
|
||||||
|
- **Emscripten**: compile-time `NO_SHADERS` → sempre ruta clàssica.
|
||||||
|
- **macOS**: shaders Metal (MSL) inline dins `sdl3gpu_shader.cpp`; no SPIR-V.
|
||||||
|
- **Opcions persistents** a `config.txt` (migració a YAML pendent):
|
||||||
|
- `videoGpuAcceleration` (bool)
|
||||||
|
- `videoGpuPreferredDriver` (string, buit = auto)
|
||||||
|
- `videoShaderEnabled` (bool)
|
||||||
|
- `videoShaderType` (0=POSTFX, 1=CRTPI)
|
||||||
|
- **Hotkeys** (provisionals fins que hi hagi menú d'opcions): `F9` toggle GPU · `F10` toggle shader · `F11` alterna POSTFX ↔ CRTPI.
|
||||||
|
- **API** a `Screen`: `setGpuAcceleration`/`toggleGpuAcceleration`/`isGpuAccelerated`, `setShaderEnabled`/`toggleShaderEnabled`/`isShaderEnabled`, `setActiveShader`/`toggleActiveShader`/`getActiveShader`.
|
||||||
|
|
||||||
|
Presets PostFX/CrtPi i cicle de presets encara **no** estan implementats — arribaran amb la migració a YAML. Per ara, valors per defecte hardcoded.
|
||||||
|
|
||||||
## Data Directory
|
## Data Directory
|
||||||
|
|
||||||
@@ -72,8 +95,9 @@ Defined in `const.h`: block size (8px), virtual canvas (256×192), play area bou
|
|||||||
- `data/font/` — bitmap font files
|
- `data/font/` — bitmap font files
|
||||||
- `data/music/` and `data/sound/` — audio assets
|
- `data/music/` and `data/sound/` — audio assets
|
||||||
- `data/lang/` — language files (es_ES, ba_BA, en_UK)
|
- `data/lang/` — language files (es_ES, ba_BA, en_UK)
|
||||||
- `data/config/` — gamecontroller DB, demo recording data
|
- `data/demo/` — demo recording data (gamecontrollerdb.txt vive en la raíz del proyecto, fuera del pack)
|
||||||
- `data/menu/` — menu definition files
|
- `data/menu/` — menu definition files
|
||||||
|
- `data/shaders/` — fonts GLSL per al post-procesado SDL3 GPU (no van al pack; s'empotren al binari via headers SPIR-V)
|
||||||
|
|
||||||
## Language
|
## Language
|
||||||
|
|
||||||
|
|||||||
196
CMakeLists.txt
196
CMakeLists.txt
@@ -3,6 +3,11 @@
|
|||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(coffee_crisis VERSION 1.00)
|
project(coffee_crisis VERSION 1.00)
|
||||||
|
|
||||||
|
# Tipus de build per defecte (Debug) si no se n'ha especificat cap
|
||||||
|
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||||
|
set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Configuración de compilador para MinGW en Windows
|
# Configuración de compilador para MinGW en Windows
|
||||||
if(WIN32 AND NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
|
if(WIN32 AND NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
|
||||||
set(CMAKE_CXX_COMPILER "g++")
|
set(CMAKE_CXX_COMPILER "g++")
|
||||||
@@ -21,8 +26,15 @@ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Os -ffunction-sections
|
|||||||
# Define el directorio de los archivos fuente
|
# Define el directorio de los archivos fuente
|
||||||
set(DIR_SOURCES "${CMAKE_SOURCE_DIR}/source")
|
set(DIR_SOURCES "${CMAKE_SOURCE_DIR}/source")
|
||||||
|
|
||||||
# Cargar todos los archivos fuente en DIR_SOURCES
|
# Cargar todos los archivos fuente en DIR_SOURCES (recursivo, sin external/)
|
||||||
file(GLOB SOURCES "${DIR_SOURCES}/*.cpp")
|
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "${DIR_SOURCES}/*.cpp")
|
||||||
|
list(FILTER SOURCES EXCLUDE REGEX "${DIR_SOURCES}/external/.*")
|
||||||
|
|
||||||
|
# En Emscripten no compilamos sdl3gpu_shader (SDL3 GPU no está soportado en WebGL2).
|
||||||
|
# Define NO_SHADERS más abajo y filtra el fuente aquí.
|
||||||
|
if(EMSCRIPTEN)
|
||||||
|
list(REMOVE_ITEM SOURCES "${DIR_SOURCES}/core/rendering/sdl3gpu/sdl3gpu_shader.cpp")
|
||||||
|
endif()
|
||||||
|
|
||||||
# Verificar si se encontraron archivos fuente
|
# Verificar si se encontraron archivos fuente
|
||||||
if(NOT SOURCES)
|
if(NOT SOURCES)
|
||||||
@@ -30,15 +42,107 @@ if(NOT SOURCES)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Configuración de SDL3
|
# Configuración de SDL3
|
||||||
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
|
if(EMSCRIPTEN)
|
||||||
message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
|
# En Emscripten, SDL3 se compila desde source con FetchContent
|
||||||
|
include(FetchContent)
|
||||||
|
FetchContent_Declare(
|
||||||
|
SDL3
|
||||||
|
GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
|
||||||
|
GIT_TAG release-3.4.4
|
||||||
|
GIT_SHALLOW TRUE
|
||||||
|
)
|
||||||
|
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
||||||
|
set(SDL_STATIC ON CACHE BOOL "" FORCE)
|
||||||
|
set(SDL_TEST_LIBRARY OFF CACHE BOOL "" FORCE)
|
||||||
|
FetchContent_MakeAvailable(SDL3)
|
||||||
|
message(STATUS "SDL3 compilado desde source para Emscripten")
|
||||||
|
else()
|
||||||
|
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
|
||||||
|
message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
|
||||||
|
endif()
|
||||||
|
|
||||||
# Configuración común de salida de ejecutables en el directorio raíz
|
# --- SHADER COMPILATION (Linux/Windows only - macOS uses Metal, Emscripten no soporta SDL3 GPU) ---
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR})
|
if(NOT APPLE AND NOT EMSCRIPTEN)
|
||||||
|
find_program(GLSLC_EXE NAMES glslc)
|
||||||
|
|
||||||
|
set(SHADER_VERT_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.vert")
|
||||||
|
set(SHADER_FRAG_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.frag")
|
||||||
|
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/spv/postfx_vert_spv.h")
|
||||||
|
set(SHADER_FRAG_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv/postfx_frag_spv.h")
|
||||||
|
set(SHADER_CRTPI_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv/crtpi_frag_spv.h")
|
||||||
|
set(SHADER_UPSCALE_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv/upscale_frag_spv.h")
|
||||||
|
set(SHADER_DOWNSCALE_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv/downscale_frag_spv.h")
|
||||||
|
|
||||||
|
set(ALL_SHADER_HEADERS "${SHADER_VERT_H}" "${SHADER_FRAG_H}" "${SHADER_CRTPI_H}" "${SHADER_UPSCALE_H}" "${SHADER_DOWNSCALE_H}")
|
||||||
|
|
||||||
|
if(GLSLC_EXE)
|
||||||
|
set(COMPILE_SHADER_SCRIPT "${CMAKE_SOURCE_DIR}/tools/shaders/compile_shader.cmake")
|
||||||
|
|
||||||
|
macro(add_shader SRC_FILE OUT_H VAR_NAME)
|
||||||
|
cmake_parse_arguments(S "" "STAGE" "" ${ARGN})
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${OUT_H}"
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
"-DGLSLC=${GLSLC_EXE}"
|
||||||
|
"-DSRC=${SRC_FILE}"
|
||||||
|
"-DOUT_H=${OUT_H}"
|
||||||
|
"-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})
|
||||||
|
message(STATUS "glslc encontrado: shaders se compilarán automáticamente")
|
||||||
|
else()
|
||||||
|
foreach(_h IN LISTS ALL_SHADER_HEADERS)
|
||||||
|
if(NOT EXISTS "${_h}")
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"glslc no encontrado y header SPIR-V no existe: ${_h}\n"
|
||||||
|
" Instala glslc: sudo apt install glslang-tools (Linux)\n"
|
||||||
|
" choco install vulkan-sdk (Windows)"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
message(STATUS "glslc no encontrado - usando headers SPIR-V precompilados")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
if(EMSCRIPTEN)
|
||||||
|
message(STATUS "Emscripten: shaders SPIR-V omitidos (SDL3 GPU no soportado en WebGL2)")
|
||||||
|
else()
|
||||||
|
message(STATUS "macOS: shaders SPIR-V omitidos (usa Metal inline)")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
# Añadir ejecutable principal
|
# Añadir ejecutable principal
|
||||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||||
|
|
||||||
|
if(NOT APPLE AND NOT EMSCRIPTEN AND GLSLC_EXE)
|
||||||
|
add_dependencies(${PROJECT_NAME} shaders)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Includes relatius a source/ (p.e. `#include "core/rendering/texture.h"`)
|
||||||
|
target_include_directories(${PROJECT_NAME} PRIVATE ${DIR_SOURCES})
|
||||||
|
|
||||||
|
# Configuración de salida: el ejecutable principal va a la raíz del proyecto.
|
||||||
|
# Per-target (no global) perquè `pack_resources` acabe a `build/` com la resta
|
||||||
|
# de projectes.
|
||||||
|
if(NOT EMSCRIPTEN)
|
||||||
|
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||||
|
endif()
|
||||||
|
|
||||||
# Añadir definiciones de compilación dependiendo del tipo de build
|
# Añadir definiciones de compilación dependiendo del tipo de build
|
||||||
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
||||||
$<$<CONFIG:DEBUG>:DEBUG PAUSE>
|
$<$<CONFIG:DEBUG>:DEBUG PAUSE>
|
||||||
@@ -66,6 +170,19 @@ elseif(APPLE)
|
|||||||
-rpath @executable_path/../Frameworks/
|
-rpath @executable_path/../Frameworks/
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
elseif(EMSCRIPTEN)
|
||||||
|
target_compile_definitions(${PROJECT_NAME} PRIVATE EMSCRIPTEN_BUILD NO_SHADERS)
|
||||||
|
# En wasm NO empaquetamos un resources.pack: el propio --preload-file de
|
||||||
|
# emscripten ya hace el mismo trabajo (bundle del directorio en un .data),
|
||||||
|
# así que metemos directamente 'data' y dejamos que el Resource lea por
|
||||||
|
# filesystem (MEMFS). Evita doble empaquetado y el uso de memoria extra.
|
||||||
|
target_link_options(${PROJECT_NAME} PRIVATE
|
||||||
|
"SHELL:--preload-file ${CMAKE_SOURCE_DIR}/data@/data"
|
||||||
|
"SHELL:--preload-file ${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt@/gamecontrollerdb.txt"
|
||||||
|
-sALLOW_MEMORY_GROWTH=1
|
||||||
|
-sMAX_WEBGL_VERSION=2
|
||||||
|
)
|
||||||
|
set_target_properties(${PROJECT_NAME} PROPERTIES SUFFIX ".html")
|
||||||
elseif(UNIX AND NOT APPLE)
|
elseif(UNIX AND NOT APPLE)
|
||||||
target_compile_definitions(${PROJECT_NAME} PRIVATE LINUX_BUILD)
|
target_compile_definitions(${PROJECT_NAME} PRIVATE LINUX_BUILD)
|
||||||
target_link_options(${PROJECT_NAME} PRIVATE -Wl,--gc-sections)
|
target_link_options(${PROJECT_NAME} PRIVATE -Wl,--gc-sections)
|
||||||
@@ -77,6 +194,7 @@ endif()
|
|||||||
|
|
||||||
find_program(CLANG_TIDY_EXE NAMES clang-tidy)
|
find_program(CLANG_TIDY_EXE NAMES clang-tidy)
|
||||||
find_program(CLANG_FORMAT_EXE NAMES clang-format)
|
find_program(CLANG_FORMAT_EXE NAMES clang-format)
|
||||||
|
find_program(CPPCHECK_EXE NAMES cppcheck)
|
||||||
|
|
||||||
# Recopilar todos los archivos fuente para analisis
|
# Recopilar todos los archivos fuente para analisis
|
||||||
file(GLOB_RECURSE ALL_SOURCE_FILES
|
file(GLOB_RECURSE ALL_SOURCE_FILES
|
||||||
@@ -84,17 +202,14 @@ file(GLOB_RECURSE ALL_SOURCE_FILES
|
|||||||
"${CMAKE_SOURCE_DIR}/source/*.h"
|
"${CMAKE_SOURCE_DIR}/source/*.h"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Excluir stb_image.h y stb_vorbis.c del analisis
|
|
||||||
set(CLANG_TIDY_SOURCES ${ALL_SOURCE_FILES})
|
set(CLANG_TIDY_SOURCES ${ALL_SOURCE_FILES})
|
||||||
list(FILTER CLANG_TIDY_SOURCES EXCLUDE REGEX ".*stb_image\\.h$")
|
|
||||||
list(FILTER CLANG_TIDY_SOURCES EXCLUDE REGEX ".*stb_vorbis\\.c$")
|
|
||||||
list(FILTER CLANG_TIDY_SOURCES EXCLUDE REGEX ".*jail_audio\\.hpp$")
|
|
||||||
|
|
||||||
# Excluir stb y jail_audio del formateo tambien
|
|
||||||
set(FORMAT_SOURCES ${ALL_SOURCE_FILES})
|
set(FORMAT_SOURCES ${ALL_SOURCE_FILES})
|
||||||
list(FILTER FORMAT_SOURCES EXCLUDE REGEX ".*stb_image\\.h$")
|
|
||||||
list(FILTER FORMAT_SOURCES EXCLUDE REGEX ".*stb_vorbis\\.c$")
|
# Para cppcheck, pasar solo .cpp (los headers se procesan transitivamente).
|
||||||
list(FILTER FORMAT_SOURCES EXCLUDE REGEX ".*jail_audio\\.hpp$")
|
set(CPPCHECK_SOURCES ${ALL_SOURCE_FILES})
|
||||||
|
list(FILTER CPPCHECK_SOURCES INCLUDE REGEX ".*\\.cpp$")
|
||||||
|
list(FILTER CPPCHECK_SOURCES EXCLUDE REGEX ".*/source/external/.*")
|
||||||
|
|
||||||
# Targets de clang-tidy
|
# Targets de clang-tidy
|
||||||
if(CLANG_TIDY_EXE)
|
if(CLANG_TIDY_EXE)
|
||||||
@@ -139,3 +254,56 @@ if(CLANG_FORMAT_EXE)
|
|||||||
else()
|
else()
|
||||||
message(STATUS "clang-format no encontrado - targets 'format' y 'format-check' no disponibles")
|
message(STATUS "clang-format no encontrado - targets 'format' y 'format-check' no disponibles")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Target de cppcheck
|
||||||
|
if(CPPCHECK_EXE)
|
||||||
|
add_custom_target(cppcheck
|
||||||
|
COMMAND ${CPPCHECK_EXE}
|
||||||
|
--enable=warning,style,performance,portability
|
||||||
|
--std=c++20
|
||||||
|
--language=c++
|
||||||
|
--inline-suppr
|
||||||
|
--suppress=missingIncludeSystem
|
||||||
|
--suppress=toomanyconfigs
|
||||||
|
--suppress=*:*/source/external/*
|
||||||
|
--quiet
|
||||||
|
-I ${CMAKE_SOURCE_DIR}/source
|
||||||
|
${CPPCHECK_SOURCES}
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
|
COMMENT "Running cppcheck..."
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
message(STATUS "cppcheck no encontrado - target 'cppcheck' no disponible")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# --- EINA STANDALONE: pack_resources ---
|
||||||
|
# Executable auxiliar que empaqueta `data/` a `resources.pack`.
|
||||||
|
# No es compila per defecte (EXCLUDE_FROM_ALL). Build explícit:
|
||||||
|
# cmake --build build --target pack_resources
|
||||||
|
# Després executar: ./build/pack_resources data resources.pack
|
||||||
|
if(NOT EMSCRIPTEN)
|
||||||
|
add_executable(pack_resources EXCLUDE_FROM_ALL
|
||||||
|
tools/pack_resources/pack_resources.cpp
|
||||||
|
source/core/resources/resource_pack.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(pack_resources PRIVATE "${CMAKE_SOURCE_DIR}/source")
|
||||||
|
target_compile_options(pack_resources PRIVATE -Wall)
|
||||||
|
|
||||||
|
# Regeneració automàtica de resources.pack en cada build si canvia data/.
|
||||||
|
file(GLOB_RECURSE DATA_FILES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/data/*")
|
||||||
|
set(RESOURCE_PACK "${CMAKE_SOURCE_DIR}/resources.pack")
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${RESOURCE_PACK}
|
||||||
|
COMMAND $<TARGET_FILE:pack_resources>
|
||||||
|
"${CMAKE_SOURCE_DIR}/data"
|
||||||
|
"${RESOURCE_PACK}"
|
||||||
|
DEPENDS pack_resources ${DATA_FILES}
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
|
COMMENT "Empaquetant data/ → resources.pack"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_target(resource_pack ALL DEPENDS ${RESOURCE_PACK})
|
||||||
|
add_dependencies(${PROJECT_NAME} resource_pack)
|
||||||
|
endif()
|
||||||
|
|||||||
179
Makefile
179
Makefile
@@ -10,12 +10,34 @@ DIR_BIN := $(addsuffix /, $(DIR_ROOT))
|
|||||||
TARGET_NAME := coffee_crisis
|
TARGET_NAME := coffee_crisis
|
||||||
TARGET_FILE := $(DIR_BIN)$(TARGET_NAME)
|
TARGET_FILE := $(DIR_BIN)$(TARGET_NAME)
|
||||||
APP_NAME := Coffee Crisis
|
APP_NAME := Coffee Crisis
|
||||||
VERSION := v2.3.3
|
|
||||||
DIST_DIR := dist
|
DIST_DIR := dist
|
||||||
RELEASE_FOLDER := dist/_tmp
|
RELEASE_FOLDER := dist/_tmp
|
||||||
RELEASE_FILE := $(RELEASE_FOLDER)/$(TARGET_NAME)
|
RELEASE_FILE := $(RELEASE_FOLDER)/$(TARGET_NAME)
|
||||||
RESOURCE_FILE := release/windows/coffee.res
|
RESOURCE_FILE := release/windows/coffee.res
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# VERSION (extracted from defines.hpp)
|
||||||
|
# ==============================================================================
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
VERSION := $(shell powershell -Command "(Select-String -Path 'source/utils/defines.hpp' -Pattern 'constexpr const char\* VERSION = \"(.+?)\"').Matches.Groups[1].Value")
|
||||||
|
else
|
||||||
|
VERSION := $(shell grep 'constexpr const char\* VERSION' source/utils/defines.hpp | sed -E 's/.*VERSION = "([^"]+)".*/\1/')
|
||||||
|
endif
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# GIT HASH (computat al host, passat a CMake via -DGIT_HASH)
|
||||||
|
# Evita que CMake haja de cridar git des de Docker/emscripten on falla per
|
||||||
|
# "dubious ownership" del volum muntat.
|
||||||
|
# ==============================================================================
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
GIT_HASH := $(shell git rev-parse --short=7 HEAD 2>NUL)
|
||||||
|
else
|
||||||
|
GIT_HASH := $(shell git rev-parse --short=7 HEAD 2>/dev/null)
|
||||||
|
endif
|
||||||
|
ifeq ($(GIT_HASH),)
|
||||||
|
GIT_HASH := unknown
|
||||||
|
endif
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# RELEASE NAMES
|
# RELEASE NAMES
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
@@ -52,45 +74,67 @@ endif
|
|||||||
ifeq ($(OS),Windows_NT)
|
ifeq ($(OS),Windows_NT)
|
||||||
WIN_TARGET_FILE := $(DIR_BIN)$(APP_NAME)
|
WIN_TARGET_FILE := $(DIR_BIN)$(APP_NAME)
|
||||||
WIN_RELEASE_FILE := $(RELEASE_FOLDER)/$(APP_NAME)
|
WIN_RELEASE_FILE := $(RELEASE_FOLDER)/$(APP_NAME)
|
||||||
|
# Escapa apòstrofs per a PowerShell (duplica ' → ''). Sense això, APP_NAMEs
|
||||||
|
# com "JailDoctor's Dilemma" trencarien el parsing de -Destination '...'.
|
||||||
|
WIN_RELEASE_FILE_PS := $(subst ','',$(WIN_RELEASE_FILE))
|
||||||
else
|
else
|
||||||
WIN_TARGET_FILE := $(TARGET_FILE)
|
WIN_TARGET_FILE := $(TARGET_FILE)
|
||||||
WIN_RELEASE_FILE := $(RELEASE_FILE)
|
WIN_RELEASE_FILE := $(RELEASE_FILE)
|
||||||
|
WIN_RELEASE_FILE_PS := $(WIN_RELEASE_FILE)
|
||||||
|
endif
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# CMAKE GENERATOR (Windows needs explicit MinGW Makefiles generator)
|
||||||
|
# ==============================================================================
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
CMAKE_GEN := -G "MinGW Makefiles"
|
||||||
|
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
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# EMPAQUETADO DE RECURSOS (build previ de l'eina + execució)
|
||||||
|
# ==============================================================================
|
||||||
|
pack:
|
||||||
|
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
|
||||||
|
@cmake --build build --target pack_resources
|
||||||
|
@./build/pack_resources data resources.pack
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# 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
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# COMPILACIÓN PARA WINDOWS (RELEASE)
|
# COMPILACIÓN PARA WINDOWS (RELEASE)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
windows_release:
|
_windows_release:
|
||||||
|
@$(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
|
# Crea carpeta de distribución y carpeta temporal
|
||||||
@@ -99,11 +143,12 @@ windows_release:
|
|||||||
@powershell -Command "if (-not (Test-Path '$(RELEASE_FOLDER)')) {New-Item '$(RELEASE_FOLDER)' -ItemType Directory}"
|
@powershell -Command "if (-not (Test-Path '$(RELEASE_FOLDER)')) {New-Item '$(RELEASE_FOLDER)' -ItemType Directory}"
|
||||||
|
|
||||||
# Copia ficheros
|
# Copia ficheros
|
||||||
@powershell -Command "Copy-Item -Path 'data' -Destination '$(RELEASE_FOLDER)' -Recurse -Force"
|
@powershell -Command "Copy-Item 'resources.pack' -Destination '$(RELEASE_FOLDER)'"
|
||||||
|
@powershell -Command "Copy-Item 'gamecontrollerdb.txt' -Destination '$(RELEASE_FOLDER)'"
|
||||||
@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
|
||||||
@@ -117,14 +162,15 @@ windows_release:
|
|||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# COMPILACIÓN PARA MACOS (RELEASE)
|
# COMPILACIÓN PARA MACOS (RELEASE)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
macos_release:
|
_macos_release:
|
||||||
|
@$(MAKE) pack
|
||||||
@echo "Creando release para macOS - Version: $(VERSION)"
|
@echo "Creando release para macOS - Version: $(VERSION)"
|
||||||
|
|
||||||
# Verificar e instalar create-dmg si es necesario
|
# Verificar e instalar create-dmg si es necesario
|
||||||
@which create-dmg > /dev/null || (echo "Instalando create-dmg..." && brew install create-dmg)
|
@which create-dmg > /dev/null || (echo "Instalando create-dmg..." && brew install create-dmg)
|
||||||
|
|
||||||
# Compila la versión para procesadores Intel con cmake
|
# Compila la versión para procesadores Intel con cmake
|
||||||
@cmake -S . -B build/intel -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DMACOS_BUNDLE=ON
|
@cmake -S . -B build/intel -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DMACOS_BUNDLE=ON -DGIT_HASH=$(GIT_HASH)
|
||||||
@cmake --build build/intel
|
@cmake --build build/intel
|
||||||
|
|
||||||
# Elimina datos de compilaciones anteriores
|
# Elimina datos de compilaciones anteriores
|
||||||
@@ -140,7 +186,8 @@ macos_release:
|
|||||||
$(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
$(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
||||||
|
|
||||||
# Copia carpetas y ficheros
|
# Copia carpetas y ficheros
|
||||||
cp -R data "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
cp resources.pack "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
||||||
|
cp gamecontrollerdb.txt "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
||||||
cp -R release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64/SDL3.framework "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks"
|
cp -R release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64/SDL3.framework "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks"
|
||||||
cp release/icons/*.icns "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
cp release/icons/*.icns "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
||||||
cp release/macos/Info.plist "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents"
|
cp release/macos/Info.plist "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents"
|
||||||
@@ -177,7 +224,7 @@ macos_release:
|
|||||||
@echo "Release Intel creado: $(MACOS_INTEL_RELEASE)"
|
@echo "Release Intel creado: $(MACOS_INTEL_RELEASE)"
|
||||||
|
|
||||||
# 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
|
@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)"
|
||||||
|
|
||||||
@@ -210,11 +257,12 @@ macos_release:
|
|||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# COMPILACIÓN PARA LINUX (RELEASE)
|
# COMPILACIÓN PARA LINUX (RELEASE)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
linux_release:
|
_linux_release:
|
||||||
|
@$(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
|
# Elimina carpeta temporal previa y la recrea
|
||||||
@@ -222,7 +270,8 @@ linux_release:
|
|||||||
$(MKDIR) "$(RELEASE_FOLDER)"
|
$(MKDIR) "$(RELEASE_FOLDER)"
|
||||||
|
|
||||||
# Copia ficheros
|
# Copia ficheros
|
||||||
cp -R data "$(RELEASE_FOLDER)"
|
cp resources.pack "$(RELEASE_FOLDER)"
|
||||||
|
cp gamecontrollerdb.txt "$(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)"
|
||||||
@@ -236,6 +285,83 @@ linux_release:
|
|||||||
# Elimina la carpeta temporal
|
# Elimina la carpeta temporal
|
||||||
$(RMDIR) "$(RELEASE_FOLDER)"
|
$(RMDIR) "$(RELEASE_FOLDER)"
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# COMPILACIÓN PARA WEBASSEMBLY (requiere Docker)
|
||||||
|
# ==============================================================================
|
||||||
|
wasm:
|
||||||
|
@$(MAKE) pack
|
||||||
|
@echo "Compilando para WebAssembly - Version: $(VERSION)"
|
||||||
|
docker run --rm \
|
||||||
|
--user $(shell id -u):$(shell id -g) \
|
||||||
|
-v $(DIR_ROOT):/src \
|
||||||
|
-w /src \
|
||||||
|
emscripten/emsdk:latest \
|
||||||
|
bash -c "emcmake cmake -S . -B build/wasm -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH) && cmake --build build/wasm"
|
||||||
|
$(MKDIR) "$(DIST_DIR)/wasm"
|
||||||
|
cp build/wasm/$(TARGET_NAME).html $(DIST_DIR)/wasm/
|
||||||
|
cp build/wasm/$(TARGET_NAME).js $(DIST_DIR)/wasm/
|
||||||
|
cp build/wasm/$(TARGET_NAME).wasm $(DIST_DIR)/wasm/
|
||||||
|
cp build/wasm/$(TARGET_NAME).data $(DIST_DIR)/wasm/
|
||||||
|
@echo "Output: $(DIST_DIR)/wasm/"
|
||||||
|
scp $(DIST_DIR)/wasm/$(TARGET_NAME).js $(DIST_DIR)/wasm/$(TARGET_NAME).wasm $(DIST_DIR)/wasm/$(TARGET_NAME).data \
|
||||||
|
maverick:/home/sergio/gitea/web_jailgames/static/games/coffee-crisis/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:
|
||||||
|
@$(MAKE) pack
|
||||||
|
@echo "Compilando WebAssembly Debug - Version: $(VERSION)"
|
||||||
|
docker run --rm \
|
||||||
|
--user $(shell id -u):$(shell id -g) \
|
||||||
|
-v $(DIR_ROOT):/src \
|
||||||
|
-w /src \
|
||||||
|
emscripten/emsdk:latest \
|
||||||
|
bash -c "emcmake cmake -S . -B build/wasm_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)
|
||||||
|
# ==============================================================================
|
||||||
|
format:
|
||||||
|
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
|
||||||
|
@cmake --build build --target format
|
||||||
|
|
||||||
|
format-check:
|
||||||
|
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
|
||||||
|
@cmake --build build --target format-check
|
||||||
|
|
||||||
|
tidy:
|
||||||
|
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
|
||||||
|
@cmake --build build --target tidy
|
||||||
|
|
||||||
|
tidy-fix:
|
||||||
|
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
|
||||||
|
@cmake --build build --target tidy-fix
|
||||||
|
|
||||||
|
cppcheck:
|
||||||
|
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
|
||||||
|
@cmake --build build --target cppcheck
|
||||||
|
|
||||||
|
# SHADERS (SPIR-V) — sólo Linux/Windows. Requiere glslc en el PATH.
|
||||||
|
compile_shaders:
|
||||||
|
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
|
||||||
|
@cmake --build build --target shaders
|
||||||
|
|
||||||
|
# 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
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
@@ -252,12 +378,23 @@ help:
|
|||||||
@echo ""
|
@echo ""
|
||||||
@echo " Release:"
|
@echo " Release:"
|
||||||
@echo " make release - Crear release (detecta SO automaticamente)"
|
@echo " make release - Crear release (detecta SO automaticamente)"
|
||||||
@echo " make windows_release - Crear release para Windows"
|
@echo " make wasm - Compilar para WebAssembly (requiere Docker) y deploy a maverick"
|
||||||
@echo " make linux_release - Crear release para Linux"
|
@echo " make wasm_debug - Compilar WebAssembly Debug local (sin deploy)"
|
||||||
@echo " make macos_release - Crear release para macOS"
|
@echo ""
|
||||||
|
@echo " Herramientas:"
|
||||||
|
@echo " make pack - Empaquetar recursos a resources.pack"
|
||||||
|
@echo " make compile_shaders - Compilar shaders GLSL a headers SPIR-V (requiere glslc)"
|
||||||
|
@echo " make controllerdb - Descargar gamecontrollerdb.txt actualizado"
|
||||||
|
@echo ""
|
||||||
|
@echo " Calidad de codigo:"
|
||||||
|
@echo " make format - Formatear codigo con clang-format"
|
||||||
|
@echo " make format-check - Verificar formato sin modificar"
|
||||||
|
@echo " make tidy - Analisis estatico con clang-tidy"
|
||||||
|
@echo " make tidy-fix - Analisis estatico con auto-fix"
|
||||||
|
@echo " make cppcheck - Analisis estatico con cppcheck"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo " Otros:"
|
@echo " Otros:"
|
||||||
@echo " make show_version - Mostrar version actual ($(VERSION))"
|
@echo " make show_version - Mostrar version actual ($(VERSION))"
|
||||||
@echo " make help - Mostrar esta ayuda"
|
@echo " make help - Mostrar esta ayuda"
|
||||||
|
|
||||||
.PHONY: all debug release windows_release macos_release linux_release show_version help
|
.PHONY: all debug release _windows_release _macos_release _linux_release wasm wasm_debug controllerdb pack format format-check tidy tidy-fix cppcheck compile_shaders show_version help
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -279,3 +279,9 @@ MODE FORA DE LINEA
|
|||||||
|
|
||||||
## 93 - MENU OPCIONES
|
## 93 - MENU OPCIONES
|
||||||
TAULER DE PUNTS
|
TAULER DE PUNTS
|
||||||
|
|
||||||
|
## 94 - NOTIFICACIO COMANDAMENT
|
||||||
|
CONNECTAT
|
||||||
|
|
||||||
|
## 95 - NOTIFICACIO COMANDAMENT
|
||||||
|
DESCONNECTAT
|
||||||
@@ -279,3 +279,9 @@ OFFLINE MODE
|
|||||||
|
|
||||||
## 93 - MENU OPCIONES
|
## 93 - MENU OPCIONES
|
||||||
HISCORE TABLE
|
HISCORE TABLE
|
||||||
|
|
||||||
|
## 94 - GAMEPAD NOTIFICATION
|
||||||
|
CONNECTED
|
||||||
|
|
||||||
|
## 95 - GAMEPAD NOTIFICATION
|
||||||
|
DISCONNECTED
|
||||||
@@ -279,3 +279,9 @@ MODO SIN CONEXION
|
|||||||
|
|
||||||
## 93 - MENU OPCIONES
|
## 93 - MENU OPCIONES
|
||||||
TABLA DE PUNTUACIONES
|
TABLA DE PUNTUACIONES
|
||||||
|
|
||||||
|
## 94 - NOTIFICACION MANDO
|
||||||
|
CONECTADO
|
||||||
|
|
||||||
|
## 95 - NOTIFICACION MANDO
|
||||||
|
DESCONECTADO
|
||||||
152
data/shaders/crtpi_frag.glsl
Normal file
152
data/shaders/crtpi_frag.glsl
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Vulkan GLSL fragment shader — CRT-Pi PostFX
|
||||||
|
// Algoritmo de scanlines continuas con pesos gaussianos, bloom y máscara de fósforo.
|
||||||
|
// Basado en el shader CRT-Pi original (GLSL 3.3), portado a GLSL 4.50 con parámetros uniformes.
|
||||||
|
//
|
||||||
|
// Compile: glslc -fshader-stage=frag --target-env=vulkan1.0 crtpi_frag.glsl -o crtpi_frag.spv
|
||||||
|
// xxd -i crtpi_frag.spv > ../../source/core/rendering/sdl3gpu/crtpi_frag_spv.h
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 v_uv;
|
||||||
|
layout(location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
layout(set = 2, binding = 0) uniform sampler2D Texture;
|
||||||
|
|
||||||
|
layout(set = 3, binding = 0) uniform CrtPiBlock {
|
||||||
|
// vec4 #0
|
||||||
|
float scanline_weight; // Ajuste gaussiano de scanlines (default 6.0)
|
||||||
|
float scanline_gap_brightness; // Brillo mínimo entre scanlines (default 0.12)
|
||||||
|
float bloom_factor; // Factor de brillo en zonas iluminadas (default 3.5)
|
||||||
|
float input_gamma; // Gamma de entrada — linealización (default 2.4)
|
||||||
|
// vec4 #1
|
||||||
|
float output_gamma; // Gamma de salida — codificación (default 2.2)
|
||||||
|
float mask_brightness; // Brillo sub-píxeles de la máscara (default 0.80)
|
||||||
|
float curvature_x; // Distorsión barrel eje X (default 0.05)
|
||||||
|
float curvature_y; // Distorsión barrel eje Y (default 0.10)
|
||||||
|
// vec4 #2
|
||||||
|
int mask_type; // 0=ninguna, 1=verde/magenta, 2=RGB fósforo
|
||||||
|
int enable_scanlines; // 0 = off, 1 = on
|
||||||
|
int enable_multisample; // 0 = off, 1 = on (antialiasing analítico de scanlines)
|
||||||
|
int enable_gamma; // 0 = off, 1 = on
|
||||||
|
// vec4 #3
|
||||||
|
int enable_curvature; // 0 = off, 1 = on
|
||||||
|
int enable_sharper; // 0 = off, 1 = on
|
||||||
|
float texture_width; // Ancho del canvas lógico en píxeles
|
||||||
|
float texture_height; // Alto del canvas lógico en píxeles
|
||||||
|
} u;
|
||||||
|
|
||||||
|
// Distorsión barrel CRT
|
||||||
|
vec2 distort(vec2 coord, vec2 screen_scale) {
|
||||||
|
vec2 curvature = vec2(u.curvature_x, u.curvature_y);
|
||||||
|
vec2 barrel_scale = 1.0 - (0.23 * curvature);
|
||||||
|
coord *= screen_scale;
|
||||||
|
coord -= vec2(0.5);
|
||||||
|
float rsq = coord.x * coord.x + coord.y * coord.y;
|
||||||
|
coord += coord * (curvature * rsq);
|
||||||
|
coord *= barrel_scale;
|
||||||
|
if (abs(coord.x) >= 0.5 || abs(coord.y) >= 0.5) {
|
||||||
|
return vec2(-1.0); // fuera de pantalla
|
||||||
|
}
|
||||||
|
coord += vec2(0.5);
|
||||||
|
coord /= screen_scale;
|
||||||
|
return coord;
|
||||||
|
}
|
||||||
|
|
||||||
|
float calcScanLineWeight(float dist) {
|
||||||
|
return max(1.0 - dist * dist * u.scanline_weight, u.scanline_gap_brightness);
|
||||||
|
}
|
||||||
|
|
||||||
|
float calcScanLine(float dy, float filter_width) {
|
||||||
|
float weight = calcScanLineWeight(dy);
|
||||||
|
if (u.enable_multisample != 0) {
|
||||||
|
weight += calcScanLineWeight(dy - filter_width);
|
||||||
|
weight += calcScanLineWeight(dy + filter_width);
|
||||||
|
weight *= 0.3333333;
|
||||||
|
}
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 tex_size = vec2(u.texture_width, u.texture_height);
|
||||||
|
|
||||||
|
// filterWidth: equivalente al original (768.0 / TextureSize.y) / 3.0
|
||||||
|
float filter_width = (768.0 / u.texture_height) / 3.0;
|
||||||
|
|
||||||
|
vec2 texcoord = v_uv;
|
||||||
|
|
||||||
|
// Curvatura barrel opcional
|
||||||
|
if (u.enable_curvature != 0) {
|
||||||
|
texcoord = distort(texcoord, vec2(1.0, 1.0));
|
||||||
|
if (texcoord.x < 0.0) {
|
||||||
|
out_color = vec4(0.0, 0.0, 0.0, 1.0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 texcoord_in_pixels = texcoord * tex_size;
|
||||||
|
vec2 tc;
|
||||||
|
float scan_line_weight;
|
||||||
|
|
||||||
|
if (u.enable_sharper != 0) {
|
||||||
|
// Modo SHARPER: filtrado bicúbico-like con subpixel sharpen
|
||||||
|
vec2 temp_coord = floor(texcoord_in_pixels) + 0.5;
|
||||||
|
tc = temp_coord / tex_size;
|
||||||
|
vec2 deltas = texcoord_in_pixels - temp_coord;
|
||||||
|
scan_line_weight = calcScanLine(deltas.y, filter_width);
|
||||||
|
vec2 signs = sign(deltas);
|
||||||
|
deltas.x *= 2.0;
|
||||||
|
deltas = deltas * deltas;
|
||||||
|
deltas.y = deltas.y * deltas.y;
|
||||||
|
deltas.x *= 0.5;
|
||||||
|
deltas.y *= 8.0;
|
||||||
|
deltas /= tex_size;
|
||||||
|
deltas *= signs;
|
||||||
|
tc = tc + deltas;
|
||||||
|
} else {
|
||||||
|
// Modo estándar
|
||||||
|
float temp_y = floor(texcoord_in_pixels.y) + 0.5;
|
||||||
|
float y_coord = temp_y / tex_size.y;
|
||||||
|
float dy = texcoord_in_pixels.y - temp_y;
|
||||||
|
scan_line_weight = calcScanLine(dy, filter_width);
|
||||||
|
float sign_y = sign(dy);
|
||||||
|
dy = dy * dy;
|
||||||
|
dy = dy * dy;
|
||||||
|
dy *= 8.0;
|
||||||
|
dy /= tex_size.y;
|
||||||
|
dy *= sign_y;
|
||||||
|
tc = vec2(texcoord.x, y_coord + dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 colour = texture(Texture, tc).rgb;
|
||||||
|
|
||||||
|
if (u.enable_scanlines != 0) {
|
||||||
|
if (u.enable_gamma != 0) {
|
||||||
|
colour = pow(colour, vec3(u.input_gamma));
|
||||||
|
}
|
||||||
|
colour *= scan_line_weight * u.bloom_factor;
|
||||||
|
if (u.enable_gamma != 0) {
|
||||||
|
colour = pow(colour, vec3(1.0 / u.output_gamma));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Máscara de fósforo
|
||||||
|
if (u.mask_type == 1) {
|
||||||
|
float which_mask = fract(gl_FragCoord.x * 0.5);
|
||||||
|
vec3 mask = (which_mask < 0.5)
|
||||||
|
? vec3(u.mask_brightness, 1.0, u.mask_brightness)
|
||||||
|
: vec3(1.0, u.mask_brightness, 1.0);
|
||||||
|
colour *= mask;
|
||||||
|
} else if (u.mask_type == 2) {
|
||||||
|
float which_mask = fract(gl_FragCoord.x * 0.3333333);
|
||||||
|
vec3 mask = vec3(u.mask_brightness);
|
||||||
|
if (which_mask < 0.3333333)
|
||||||
|
mask.x = 1.0;
|
||||||
|
else if (which_mask < 0.6666666)
|
||||||
|
mask.y = 1.0;
|
||||||
|
else
|
||||||
|
mask.z = 1.0;
|
||||||
|
colour *= mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_color = vec4(colour, 1.0);
|
||||||
|
}
|
||||||
48
data/shaders/downscale.frag
Normal file
48
data/shaders/downscale.frag
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#version 450
|
||||||
|
layout(location = 0) in vec2 v_uv;
|
||||||
|
layout(location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
layout(set = 2, binding = 0) uniform sampler2D source;
|
||||||
|
|
||||||
|
layout(set = 3, binding = 0) uniform DownscaleUniforms {
|
||||||
|
int algorithm; // 0 = Lanczos2 (ventana 2, ±2 taps), 1 = Lanczos3 (ventana 3, ±3 taps)
|
||||||
|
float pad0;
|
||||||
|
float pad1;
|
||||||
|
float pad2;
|
||||||
|
} u;
|
||||||
|
|
||||||
|
// Kernel Lanczos normalizado: sinc(t) * sinc(t/a) para |t| < a, 0 fuera.
|
||||||
|
float lanczos(float t, float a) {
|
||||||
|
t = abs(t);
|
||||||
|
if (t < 0.0001) { return 1.0; }
|
||||||
|
if (t >= a) { return 0.0; }
|
||||||
|
const float PI = 3.14159265358979;
|
||||||
|
float pt = PI * t;
|
||||||
|
return (a * sin(pt) * sin(pt / a)) / (pt * pt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 src_size = vec2(textureSize(source, 0));
|
||||||
|
// Posición en coordenadas de texel (centros de texel en N+0.5)
|
||||||
|
vec2 p = v_uv * src_size;
|
||||||
|
vec2 p_floor = floor(p);
|
||||||
|
|
||||||
|
float a = (u.algorithm == 0) ? 2.0 : 3.0;
|
||||||
|
int win = int(a);
|
||||||
|
|
||||||
|
vec4 color = vec4(0.0);
|
||||||
|
float weight_sum = 0.0;
|
||||||
|
|
||||||
|
for (int j = -win; j <= win; j++) {
|
||||||
|
for (int i = -win; i <= win; i++) {
|
||||||
|
// Centro del texel (i,j) relativo a p_floor
|
||||||
|
vec2 tap_center = p_floor + vec2(float(i), float(j)) + 0.5;
|
||||||
|
vec2 offset = tap_center - p;
|
||||||
|
float w = lanczos(offset.x, a) * lanczos(offset.y, a);
|
||||||
|
color += texture(source, tap_center / src_size) * w;
|
||||||
|
weight_sum += w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out_color = (weight_sum > 0.0) ? (color / weight_sum) : vec4(0.0, 0.0, 0.0, 1.0);
|
||||||
|
}
|
||||||
142
data/shaders/postfx.frag
Normal file
142
data/shaders/postfx.frag
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Vulkan GLSL fragment shader — PostFX effects
|
||||||
|
// Used for SDL3 GPU API (SPIR-V path, Win/Linux).
|
||||||
|
// Compile: glslc postfx.frag -o postfx.frag.spv
|
||||||
|
// xxd -i postfx.frag.spv > ../../source/core/rendering/sdl3gpu/postfx_frag_spv.h
|
||||||
|
//
|
||||||
|
// PostFXUniforms must match exactly the C++ struct in sdl3gpu_shader.hpp
|
||||||
|
// (8 floats, 32 bytes, std140/scalar layout).
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 v_uv;
|
||||||
|
layout(location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
layout(set = 2, binding = 0) uniform sampler2D scene;
|
||||||
|
|
||||||
|
layout(set = 3, binding = 0) uniform PostFXUniforms {
|
||||||
|
float vignette_strength;
|
||||||
|
float chroma_strength;
|
||||||
|
float scanline_strength;
|
||||||
|
float screen_height;
|
||||||
|
float mask_strength;
|
||||||
|
float gamma_strength;
|
||||||
|
float curvature;
|
||||||
|
float bleeding;
|
||||||
|
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
|
||||||
|
float time; // seconds since SDL init
|
||||||
|
float oversample; // supersampling factor (1.0 = off, 3.0 = 3×SS)
|
||||||
|
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz — 48 bytes total (3 × 16)
|
||||||
|
} u;
|
||||||
|
|
||||||
|
// YCbCr helpers for NTSC bleeding
|
||||||
|
vec3 rgb_to_ycc(vec3 rgb) {
|
||||||
|
return vec3(
|
||||||
|
0.299*rgb.r + 0.587*rgb.g + 0.114*rgb.b,
|
||||||
|
-0.169*rgb.r - 0.331*rgb.g + 0.500*rgb.b + 0.5,
|
||||||
|
0.500*rgb.r - 0.419*rgb.g - 0.081*rgb.b + 0.5
|
||||||
|
);
|
||||||
|
}
|
||||||
|
vec3 ycc_to_rgb(vec3 ycc) {
|
||||||
|
float y = ycc.x;
|
||||||
|
float cb = ycc.y - 0.5;
|
||||||
|
float cr = ycc.z - 0.5;
|
||||||
|
return clamp(vec3(
|
||||||
|
y + 1.402*cr,
|
||||||
|
y - 0.344*cb - 0.714*cr,
|
||||||
|
y + 1.772*cb
|
||||||
|
), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = v_uv;
|
||||||
|
|
||||||
|
// Curvatura barrel CRT
|
||||||
|
if (u.curvature > 0.0) {
|
||||||
|
vec2 c = uv - 0.5;
|
||||||
|
float rsq = dot(c, c);
|
||||||
|
vec2 dist = vec2(0.05, 0.1) * u.curvature;
|
||||||
|
vec2 barrelScale = vec2(1.0) - 0.23 * dist;
|
||||||
|
c += c * (dist * rsq);
|
||||||
|
c *= barrelScale;
|
||||||
|
if (abs(c.x) >= 0.5 || abs(c.y) >= 0.5) {
|
||||||
|
out_color = vec4(0.0, 0.0, 0.0, 1.0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uv = c + 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Muestra base
|
||||||
|
vec3 base = texture(scene, uv).rgb;
|
||||||
|
|
||||||
|
// Sangrado NTSC — difuminado horizontal de crominancia.
|
||||||
|
// step = 1 pixel lógico de juego en UV (corrige SS: textureSize.x = game_w * oversample).
|
||||||
|
vec3 colour;
|
||||||
|
if (u.bleeding > 0.0) {
|
||||||
|
float tw = float(textureSize(scene, 0).x);
|
||||||
|
float step = u.oversample / tw; // 1 pixel lógico en UV
|
||||||
|
vec3 ycc = rgb_to_ycc(base);
|
||||||
|
vec3 ycc_l2 = rgb_to_ycc(texture(scene, uv - vec2(2.0*step, 0.0)).rgb);
|
||||||
|
vec3 ycc_l1 = rgb_to_ycc(texture(scene, uv - vec2(1.0*step, 0.0)).rgb);
|
||||||
|
vec3 ycc_r1 = rgb_to_ycc(texture(scene, uv + vec2(1.0*step, 0.0)).rgb);
|
||||||
|
vec3 ycc_r2 = rgb_to_ycc(texture(scene, uv + vec2(2.0*step, 0.0)).rgb);
|
||||||
|
ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0 + ycc.yz*2.0 + ycc_r1.yz*2.0 + ycc_r2.yz) / 8.0;
|
||||||
|
colour = mix(base, ycc_to_rgb(ycc), u.bleeding);
|
||||||
|
} else {
|
||||||
|
colour = base;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aberración cromática (drift animado con time para efecto NTSC real)
|
||||||
|
float ca = u.chroma_strength * 0.005 * (1.0 + 0.15 * sin(u.time * 7.3));
|
||||||
|
colour.r = texture(scene, uv + vec2(ca, 0.0)).r;
|
||||||
|
colour.b = texture(scene, uv - vec2(ca, 0.0)).b;
|
||||||
|
|
||||||
|
// Corrección gamma (linealizar antes de scanlines, codificar después)
|
||||||
|
if (u.gamma_strength > 0.0) {
|
||||||
|
vec3 lin = pow(colour, vec3(2.4));
|
||||||
|
colour = mix(colour, lin, u.gamma_strength);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scanlines — 1 pixel físico oscuro por fila lógica.
|
||||||
|
// Modelo sustractivo: las filas de scanline se oscurecen, las demás no cambian.
|
||||||
|
// Esto evita el efecto de sobrebrillo en contenido con colores vivos.
|
||||||
|
if (u.scanline_strength > 0.0) {
|
||||||
|
float ps = max(1.0, round(u.pixel_scale));
|
||||||
|
float frac_in_row = fract(uv.y * u.screen_height);
|
||||||
|
float row_pos = floor(frac_in_row * ps);
|
||||||
|
float is_dark = step(ps - 1.0, row_pos);
|
||||||
|
float scan = mix(1.0, 0.0, is_dark);
|
||||||
|
colour *= mix(1.0, scan, u.scanline_strength);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (u.gamma_strength > 0.0) {
|
||||||
|
vec3 enc = pow(colour, vec3(1.0 / 2.2));
|
||||||
|
colour = mix(colour, enc, u.gamma_strength);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Viñeta
|
||||||
|
vec2 d = uv - 0.5;
|
||||||
|
float vignette = 1.0 - dot(d, d) * u.vignette_strength;
|
||||||
|
colour *= clamp(vignette, 0.0, 1.0);
|
||||||
|
|
||||||
|
// Máscara de fósforo RGB — después de scanlines (orden original):
|
||||||
|
// filas brillantes saturadas → máscara invisible, filas oscuras → RGB visible.
|
||||||
|
if (u.mask_strength > 0.0) {
|
||||||
|
float whichMask = fract(gl_FragCoord.x * 0.3333333);
|
||||||
|
vec3 mask = vec3(0.80);
|
||||||
|
if (whichMask < 0.3333333)
|
||||||
|
mask.x = 1.0;
|
||||||
|
else if (whichMask < 0.6666666)
|
||||||
|
mask.y = 1.0;
|
||||||
|
else
|
||||||
|
mask.z = 1.0;
|
||||||
|
colour = mix(colour, colour * mask, u.mask_strength);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parpadeo de fósforo CRT (~50 Hz)
|
||||||
|
if (u.flicker > 0.0) {
|
||||||
|
float flicker_wave = sin(u.time * 100.0) * 0.5 + 0.5;
|
||||||
|
colour *= 1.0 - u.flicker * 0.04 * flicker_wave;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_color = vec4(colour, 1.0);
|
||||||
|
}
|
||||||
24
data/shaders/postfx.vert
Normal file
24
data/shaders/postfx.vert
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Vulkan GLSL vertex shader — postfx full-screen triangle
|
||||||
|
// Used for SDL3 GPU API (SPIR-V path, Win/Linux).
|
||||||
|
// Compile: glslc postfx.vert -o postfx.vert.spv
|
||||||
|
// xxd -i postfx.vert.spv > ../../source/core/rendering/sdl3gpu/postfx_vert_spv.h
|
||||||
|
|
||||||
|
layout(location = 0) out vec2 v_uv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Full-screen triangle (no vertex buffer needed)
|
||||||
|
const vec2 positions[3] = vec2[3](
|
||||||
|
vec2(-1.0, -1.0),
|
||||||
|
vec2( 3.0, -1.0),
|
||||||
|
vec2(-1.0, 3.0)
|
||||||
|
);
|
||||||
|
const vec2 uvs[3] = vec2[3](
|
||||||
|
vec2(0.0, 1.0),
|
||||||
|
vec2(2.0, 1.0),
|
||||||
|
vec2(0.0,-1.0)
|
||||||
|
);
|
||||||
|
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
|
||||||
|
v_uv = uvs[gl_VertexIndex];
|
||||||
|
}
|
||||||
15
data/shaders/upscale.frag
Normal file
15
data/shaders/upscale.frag
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Vulkan GLSL fragment shader — Nearest-neighbour upscale pass
|
||||||
|
// Used as the first render pass when supersampling is active.
|
||||||
|
// Compile: glslc upscale.frag -o upscale.frag.spv
|
||||||
|
// xxd -i upscale.frag.spv > ../../source/core/rendering/sdl3gpu/upscale_frag_spv.h
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 v_uv;
|
||||||
|
layout(location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
layout(set = 2, binding = 0) uniform sampler2D scene;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
out_color = texture(scene, v_uv);
|
||||||
|
}
|
||||||
2232
gamecontrollerdb.txt
Normal file
2232
gamecontrollerdb.txt
Normal file
File diff suppressed because it is too large
Load Diff
212
source/core/audio/audio.cpp
Normal file
212
source/core/audio/audio.cpp
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
#include "core/audio/audio.hpp"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h> // Para SDL_GetError, SDL_Init
|
||||||
|
|
||||||
|
#include <algorithm> // Para clamp
|
||||||
|
#include <iostream> // Para std::cout
|
||||||
|
|
||||||
|
// Implementación de stb_vorbis (debe estar ANTES de incluir jail_audio.hpp).
|
||||||
|
// clang-format off
|
||||||
|
#undef STB_VORBIS_HEADER_ONLY
|
||||||
|
#include "external/stb_vorbis.c"
|
||||||
|
// stb_vorbis.c filtra les macros L, C i R (i PLAYBACK_*) al TU. Les netegem
|
||||||
|
// perquè xocarien amb noms de paràmetres de plantilla en altres headers.
|
||||||
|
#undef L
|
||||||
|
#undef C
|
||||||
|
#undef R
|
||||||
|
#undef PLAYBACK_MONO
|
||||||
|
#undef PLAYBACK_LEFT
|
||||||
|
#undef PLAYBACK_RIGHT
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
#include "core/audio/audio_adapter.hpp" // Para AudioResource::getMusic/getSound
|
||||||
|
#include "core/audio/jail_audio.hpp" // Para JA_*
|
||||||
|
#include "game/options.hpp" // Para Options::audio
|
||||||
|
|
||||||
|
// Singleton
|
||||||
|
Audio* Audio::instance = nullptr;
|
||||||
|
|
||||||
|
// Inicializa la instancia única del singleton
|
||||||
|
void Audio::init() { Audio::instance = new Audio(); }
|
||||||
|
|
||||||
|
// Libera la instancia
|
||||||
|
void Audio::destroy() {
|
||||||
|
delete Audio::instance;
|
||||||
|
Audio::instance = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtiene la instancia
|
||||||
|
auto Audio::get() -> Audio* { return Audio::instance; }
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
Audio::Audio() { initSDLAudio(); }
|
||||||
|
|
||||||
|
// Destructor
|
||||||
|
Audio::~Audio() {
|
||||||
|
JA_Quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Método principal
|
||||||
|
void Audio::update() {
|
||||||
|
JA_Update();
|
||||||
|
|
||||||
|
// Sincronizar estado: detectar cuando la música se para (ej. fade-out completado)
|
||||||
|
if (instance && instance->music_.state == MusicState::PLAYING && JA_GetMusicState() != JA_MUSIC_PLAYING) {
|
||||||
|
instance->music_.state = MusicState::STOPPED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reproduce la música por nombre (con crossfade opcional)
|
||||||
|
void Audio::playMusic(const std::string& name, const int loop, const int crossfade_ms) {
|
||||||
|
bool new_loop = (loop != 0);
|
||||||
|
|
||||||
|
// Si ya está sonando exactamente la misma pista y mismo modo loop, no hacemos nada
|
||||||
|
if (music_.state == MusicState::PLAYING && music_.name == name && music_.loop == new_loop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!music_enabled_) return;
|
||||||
|
|
||||||
|
auto* resource = AudioResource::getMusic(name);
|
||||||
|
if (resource == nullptr) return;
|
||||||
|
|
||||||
|
if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) {
|
||||||
|
JA_CrossfadeMusic(resource, crossfade_ms, loop);
|
||||||
|
} else {
|
||||||
|
if (music_.state == MusicState::PLAYING) {
|
||||||
|
JA_StopMusic();
|
||||||
|
}
|
||||||
|
JA_PlayMusic(resource, loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
music_.name = name;
|
||||||
|
music_.loop = new_loop;
|
||||||
|
music_.state = MusicState::PLAYING;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reproduce la música por puntero (con crossfade opcional)
|
||||||
|
void Audio::playMusic(JA_Music_t* music, const int loop, const int crossfade_ms) {
|
||||||
|
if (!music_enabled_ || music == nullptr) return;
|
||||||
|
|
||||||
|
if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) {
|
||||||
|
JA_CrossfadeMusic(music, crossfade_ms, loop);
|
||||||
|
} else {
|
||||||
|
if (music_.state == MusicState::PLAYING) {
|
||||||
|
JA_StopMusic();
|
||||||
|
}
|
||||||
|
JA_PlayMusic(music, loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
music_.name.clear(); // nom desconegut quan es passa per punter
|
||||||
|
music_.loop = (loop != 0);
|
||||||
|
music_.state = MusicState::PLAYING;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pausa la música
|
||||||
|
void Audio::pauseMusic() {
|
||||||
|
if (music_enabled_ && music_.state == MusicState::PLAYING) {
|
||||||
|
JA_PauseMusic();
|
||||||
|
music_.state = MusicState::PAUSED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continua la música pausada
|
||||||
|
void Audio::resumeMusic() {
|
||||||
|
if (music_enabled_ && music_.state == MusicState::PAUSED) {
|
||||||
|
JA_ResumeMusic();
|
||||||
|
music_.state = MusicState::PLAYING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detiene la música
|
||||||
|
void Audio::stopMusic() {
|
||||||
|
if (music_enabled_) {
|
||||||
|
JA_StopMusic();
|
||||||
|
music_.state = MusicState::STOPPED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reproduce un sonido por nombre
|
||||||
|
void Audio::playSound(const std::string& name, Group group) const {
|
||||||
|
if (sound_enabled_) {
|
||||||
|
JA_PlaySound(AudioResource::getSound(name), 0, static_cast<int>(group));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reproduce un sonido por puntero directo
|
||||||
|
void Audio::playSound(JA_Sound_t* sound, Group group) const {
|
||||||
|
if (sound_enabled_ && sound != nullptr) {
|
||||||
|
JA_PlaySound(sound, 0, static_cast<int>(group));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detiene todos los sonidos
|
||||||
|
void Audio::stopAllSounds() const {
|
||||||
|
if (sound_enabled_) {
|
||||||
|
JA_StopChannel(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Realiza un fundido de salida de la música
|
||||||
|
void Audio::fadeOutMusic(int milliseconds) const {
|
||||||
|
if (music_enabled_ && getRealMusicState() == MusicState::PLAYING) {
|
||||||
|
JA_FadeOutMusic(milliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consulta directamente el estado real de la música en jailaudio
|
||||||
|
auto Audio::getRealMusicState() -> MusicState {
|
||||||
|
JA_Music_state ja_state = JA_GetMusicState();
|
||||||
|
switch (ja_state) {
|
||||||
|
case JA_MUSIC_PLAYING:
|
||||||
|
return MusicState::PLAYING;
|
||||||
|
case JA_MUSIC_PAUSED:
|
||||||
|
return MusicState::PAUSED;
|
||||||
|
case JA_MUSIC_STOPPED:
|
||||||
|
case JA_MUSIC_INVALID:
|
||||||
|
case JA_MUSIC_DISABLED:
|
||||||
|
default:
|
||||||
|
return MusicState::STOPPED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Establece el volumen de los sonidos (float 0.0..1.0)
|
||||||
|
void Audio::setSoundVolume(float sound_volume, Group group) const {
|
||||||
|
if (sound_enabled_) {
|
||||||
|
sound_volume = std::clamp(sound_volume, MIN_VOLUME, MAX_VOLUME);
|
||||||
|
const float CONVERTED_VOLUME = sound_volume * Options::audio.volume;
|
||||||
|
JA_SetSoundVolume(CONVERTED_VOLUME, static_cast<int>(group));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Establece el volumen de la música (float 0.0..1.0)
|
||||||
|
void Audio::setMusicVolume(float music_volume) const {
|
||||||
|
if (music_enabled_) {
|
||||||
|
music_volume = std::clamp(music_volume, MIN_VOLUME, MAX_VOLUME);
|
||||||
|
const float CONVERTED_VOLUME = music_volume * Options::audio.volume;
|
||||||
|
JA_SetMusicVolume(CONVERTED_VOLUME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aplica la configuración
|
||||||
|
void Audio::applySettings() {
|
||||||
|
enable(Options::audio.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Establecer estado general
|
||||||
|
void Audio::enable(bool value) {
|
||||||
|
enabled_ = value;
|
||||||
|
|
||||||
|
setSoundVolume(enabled_ ? Options::audio.sound.volume : MIN_VOLUME);
|
||||||
|
setMusicVolume(enabled_ ? Options::audio.music.volume : MIN_VOLUME);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inicializa SDL Audio
|
||||||
|
void Audio::initSDLAudio() {
|
||||||
|
if (!SDL_Init(SDL_INIT_AUDIO)) {
|
||||||
|
std::cout << "SDL_AUDIO could not initialize! SDL Error: " << SDL_GetError() << '\n';
|
||||||
|
} else {
|
||||||
|
JA_Init(FREQUENCY, SDL_AUDIO_S16LE, 2);
|
||||||
|
enable(Options::audio.enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
114
source/core/audio/audio.hpp
Normal file
114
source/core/audio/audio.hpp
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint> // Para int8_t, uint8_t
|
||||||
|
#include <string> // Para string
|
||||||
|
#include <utility> // Para move
|
||||||
|
|
||||||
|
// --- 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 {
|
||||||
|
public:
|
||||||
|
// --- Enums ---
|
||||||
|
enum class Group : std::int8_t {
|
||||||
|
ALL = -1, // Todos los grupos
|
||||||
|
GAME = 0, // Sonidos del juego
|
||||||
|
INTERFACE = 1 // Sonidos de la interfaz
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class MusicState : std::uint8_t {
|
||||||
|
PLAYING, // Reproduciendo música
|
||||||
|
PAUSED, // Música pausada
|
||||||
|
STOPPED, // Música detenida
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Constantes ---
|
||||||
|
static constexpr float MAX_VOLUME = 1.0F; // Volumen máximo (float 0..1)
|
||||||
|
static constexpr float MIN_VOLUME = 0.0F; // Volumen mínimo (float 0..1)
|
||||||
|
static constexpr float VOLUME_STEP = 0.05F; // Pas estàndard per a UI (5%)
|
||||||
|
static constexpr int FREQUENCY = 48000; // Frecuencia de audio
|
||||||
|
static constexpr int DEFAULT_CROSSFADE_MS = 1500; // Duració del crossfade per defecte (ms)
|
||||||
|
|
||||||
|
// --- Singleton ---
|
||||||
|
static void init(); // Inicializa el objeto Audio
|
||||||
|
static void destroy(); // Libera el objeto Audio
|
||||||
|
static auto get() -> Audio*; // Obtiene el puntero al objeto Audio
|
||||||
|
Audio(const Audio&) = delete; // Evitar copia
|
||||||
|
auto operator=(const Audio&) -> Audio& = delete; // Evitar asignación
|
||||||
|
|
||||||
|
static void update(); // Actualización del sistema de audio
|
||||||
|
|
||||||
|
// --- Control de música ---
|
||||||
|
void playMusic(const std::string& name, int loop = -1, int crossfade_ms = 0); // Reproducir música por nombre (con crossfade opcional)
|
||||||
|
void playMusic(struct JA_Music_t* music, int loop = -1, int crossfade_ms = 0); // Reproducir música por puntero (con crossfade opcional)
|
||||||
|
void pauseMusic(); // Pausar reproducción de música
|
||||||
|
void resumeMusic(); // Continua la música pausada
|
||||||
|
void stopMusic(); // Detener completamente la música
|
||||||
|
void fadeOutMusic(int milliseconds) const; // Fundido de salida de la música
|
||||||
|
|
||||||
|
// --- Control de sonidos ---
|
||||||
|
void playSound(const std::string& name, Group group = Group::GAME) const; // Reproducir sonido puntual por nombre
|
||||||
|
void playSound(struct JA_Sound_t* sound, Group group = Group::GAME) const; // Reproducir sonido puntual por puntero
|
||||||
|
void stopAllSounds() const; // Detener todos los sonidos
|
||||||
|
|
||||||
|
// --- Control de volumen (API interna: float 0.0..1.0) ---
|
||||||
|
void setSoundVolume(float volume, Group group = Group::ALL) const; // Ajustar volumen de efectos
|
||||||
|
void setMusicVolume(float volume) const; // Ajustar volumen de música
|
||||||
|
|
||||||
|
// --- Helpers de conversió per a la capa de presentació ---
|
||||||
|
// UI (menús, notificacions) manega enters 0..100; internament viu float 0..1.
|
||||||
|
static constexpr auto toPercent(float volume) -> int {
|
||||||
|
return static_cast<int>(volume * 100.0F + 0.5F);
|
||||||
|
}
|
||||||
|
static constexpr auto fromPercent(int percent) -> float {
|
||||||
|
return static_cast<float>(percent) / 100.0F;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Configuración general ---
|
||||||
|
void enable(bool value); // Establecer estado general
|
||||||
|
void toggleEnabled() { enabled_ = !enabled_; } // Alternar estado general
|
||||||
|
void applySettings(); // Aplica la configuración
|
||||||
|
|
||||||
|
// --- Configuración de sonidos ---
|
||||||
|
void enableSound() { sound_enabled_ = true; } // Habilitar sonidos
|
||||||
|
void disableSound() { sound_enabled_ = false; } // Deshabilitar sonidos
|
||||||
|
void enableSound(bool value) { sound_enabled_ = value; } // Establecer estado de sonidos
|
||||||
|
void toggleSound() { sound_enabled_ = !sound_enabled_; } // Alternar estado de sonidos
|
||||||
|
|
||||||
|
// --- Configuración de música ---
|
||||||
|
void enableMusic() { music_enabled_ = true; } // Habilitar música
|
||||||
|
void disableMusic() { music_enabled_ = false; } // Deshabilitar 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
|
||||||
|
|
||||||
|
// --- Consultas de estado ---
|
||||||
|
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; }
|
||||||
|
[[nodiscard]] auto isSoundEnabled() const -> bool { return sound_enabled_; }
|
||||||
|
[[nodiscard]] auto isMusicEnabled() const -> bool { return music_enabled_; }
|
||||||
|
[[nodiscard]] auto getMusicState() const -> MusicState { return music_.state; }
|
||||||
|
[[nodiscard]] static auto getRealMusicState() -> MusicState;
|
||||||
|
[[nodiscard]] auto getCurrentMusicName() const -> const std::string& { return music_.name; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// --- Tipos anidados ---
|
||||||
|
struct Music {
|
||||||
|
MusicState state{MusicState::STOPPED}; // Estado actual de la música
|
||||||
|
std::string name; // Última pista de música reproducida
|
||||||
|
bool loop{false}; // Indica si se reproduce en bucle
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Métodos ---
|
||||||
|
Audio(); // Constructor privado
|
||||||
|
~Audio(); // Destructor privado
|
||||||
|
void initSDLAudio(); // Inicializa SDL Audio
|
||||||
|
|
||||||
|
// --- Variables miembro ---
|
||||||
|
static Audio* instance; // Instancia única de Audio
|
||||||
|
|
||||||
|
Music music_; // Estado de la música
|
||||||
|
bool enabled_{true}; // Estado general del audio
|
||||||
|
bool sound_enabled_{true}; // Estado de los efectos de sonido
|
||||||
|
bool music_enabled_{true}; // Estado de la música
|
||||||
|
};
|
||||||
13
source/core/audio/audio_adapter.cpp
Normal file
13
source/core/audio/audio_adapter.cpp
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#include "core/audio/audio_adapter.hpp"
|
||||||
|
|
||||||
|
#include "core/resources/resource.h"
|
||||||
|
|
||||||
|
namespace AudioResource {
|
||||||
|
JA_Music_t* getMusic(const std::string& name) {
|
||||||
|
return Resource::get()->getMusic(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
JA_Sound_t* getSound(const std::string& name) {
|
||||||
|
return Resource::get()->getSound(name);
|
||||||
|
}
|
||||||
|
} // namespace AudioResource
|
||||||
17
source/core/audio/audio_adapter.hpp
Normal file
17
source/core/audio/audio_adapter.hpp
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// --- Audio Resource Adapter ---
|
||||||
|
// Aquest fitxer exposa una interfície comuna a Audio per obtenir JA_Music_t* /
|
||||||
|
// JA_Sound_t* per nom. Cada projecte la implementa en audio_adapter.cpp
|
||||||
|
// delegant al seu singleton de recursos (Resource::get(), Resource::Cache::get(),
|
||||||
|
// etc.). Això permet que audio.hpp/audio.cpp siguin idèntics entre projectes.
|
||||||
|
|
||||||
|
#include <string> // Para string
|
||||||
|
|
||||||
|
struct JA_Music_t;
|
||||||
|
struct JA_Sound_t;
|
||||||
|
|
||||||
|
namespace AudioResource {
|
||||||
|
JA_Music_t* getMusic(const std::string& name);
|
||||||
|
JA_Sound_t* getSound(const std::string& name);
|
||||||
|
} // namespace AudioResource
|
||||||
679
source/core/audio/jail_audio.hpp
Normal file
679
source/core/audio/jail_audio.hpp
Normal file
@@ -0,0 +1,679 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// --- Includes ---
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <stdint.h> // Para uint32_t, uint8_t
|
||||||
|
#include <stdio.h> // Para NULL, fseek, fclose, fopen, fread, ftell, FILE, SEEK_END, SEEK_SET
|
||||||
|
#include <stdlib.h> // Para free, malloc
|
||||||
|
|
||||||
|
#include <iostream> // Para std::cout
|
||||||
|
#include <memory> // Para std::unique_ptr
|
||||||
|
#include <string> // Para std::string
|
||||||
|
#include <vector> // Para std::vector
|
||||||
|
|
||||||
|
#define STB_VORBIS_HEADER_ONLY
|
||||||
|
#include "external/stb_vorbis.c" // Para stb_vorbis_open_memory i streaming
|
||||||
|
|
||||||
|
// Deleter stateless per a buffers reservats amb `SDL_malloc` / `SDL_LoadWAV*`.
|
||||||
|
// Compatible amb `std::unique_ptr<Uint8[], SDLFreeDeleter>` — zero size
|
||||||
|
// overhead gràcies a EBO, igual que un unique_ptr amb default_delete.
|
||||||
|
struct SDLFreeDeleter {
|
||||||
|
void operator()(Uint8* p) const noexcept {
|
||||||
|
if (p) SDL_free(p);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Public Enums ---
|
||||||
|
enum JA_Channel_state {
|
||||||
|
JA_CHANNEL_INVALID,
|
||||||
|
JA_CHANNEL_FREE,
|
||||||
|
JA_CHANNEL_PLAYING,
|
||||||
|
JA_CHANNEL_PAUSED,
|
||||||
|
JA_SOUND_DISABLED,
|
||||||
|
};
|
||||||
|
enum JA_Music_state {
|
||||||
|
JA_MUSIC_INVALID,
|
||||||
|
JA_MUSIC_PLAYING,
|
||||||
|
JA_MUSIC_PAUSED,
|
||||||
|
JA_MUSIC_STOPPED,
|
||||||
|
JA_MUSIC_DISABLED,
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Struct Definitions ---
|
||||||
|
#define JA_MAX_SIMULTANEOUS_CHANNELS 20
|
||||||
|
#define JA_MAX_GROUPS 2
|
||||||
|
|
||||||
|
struct JA_Sound_t {
|
||||||
|
SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
|
||||||
|
Uint32 length{0};
|
||||||
|
// Buffer descomprimit (PCM) propietat del sound. Reservat per SDL_LoadWAV
|
||||||
|
// via SDL_malloc; el deleter `SDLFreeDeleter` allibera amb SDL_free.
|
||||||
|
std::unique_ptr<Uint8[], SDLFreeDeleter> buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct JA_Channel_t {
|
||||||
|
JA_Sound_t* sound{nullptr};
|
||||||
|
int pos{0};
|
||||||
|
int times{0};
|
||||||
|
int group{0};
|
||||||
|
SDL_AudioStream* stream{nullptr};
|
||||||
|
JA_Channel_state state{JA_CHANNEL_FREE};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct JA_Music_t {
|
||||||
|
SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
|
||||||
|
|
||||||
|
// OGG comprimit en memòria. Propietat nostra; es copia des del buffer
|
||||||
|
// d'entrada una sola vegada en JA_LoadMusic i es descomprimix en chunks
|
||||||
|
// per streaming. Com que stb_vorbis guarda un punter persistent al
|
||||||
|
// `.data()` d'aquest vector, no el podem resize'jar un cop establert
|
||||||
|
// (una reallocation invalidaria el punter que el decoder conserva).
|
||||||
|
std::vector<Uint8> ogg_data;
|
||||||
|
stb_vorbis* vorbis{nullptr}; // handle del decoder, viu tot el cicle del JA_Music_t
|
||||||
|
|
||||||
|
std::string filename;
|
||||||
|
|
||||||
|
int times{0}; // loops restants (-1 = infinit, 0 = un sol play)
|
||||||
|
SDL_AudioStream* stream{nullptr};
|
||||||
|
JA_Music_state state{JA_MUSIC_INVALID};
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Internal Global State (inline, C++17) ---
|
||||||
|
|
||||||
|
inline JA_Music_t* current_music{nullptr};
|
||||||
|
inline JA_Channel_t channels[JA_MAX_SIMULTANEOUS_CHANNELS];
|
||||||
|
|
||||||
|
inline SDL_AudioSpec JA_audioSpec{SDL_AUDIO_S16, 2, 48000};
|
||||||
|
inline float JA_musicVolume{1.0f};
|
||||||
|
inline float JA_soundVolume[JA_MAX_GROUPS];
|
||||||
|
inline bool JA_musicEnabled{true};
|
||||||
|
inline bool JA_soundEnabled{true};
|
||||||
|
inline SDL_AudioDeviceID sdlAudioDevice{0};
|
||||||
|
|
||||||
|
// --- Crossfade / Fade State ---
|
||||||
|
struct JA_FadeState {
|
||||||
|
bool active{false};
|
||||||
|
Uint64 start_time{0};
|
||||||
|
int duration_ms{0};
|
||||||
|
float initial_volume{0.0f};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct JA_OutgoingMusic {
|
||||||
|
SDL_AudioStream* stream{nullptr};
|
||||||
|
JA_FadeState fade;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline JA_OutgoingMusic outgoing_music;
|
||||||
|
inline JA_FadeState incoming_fade;
|
||||||
|
|
||||||
|
// --- Forward Declarations ---
|
||||||
|
inline void JA_StopMusic();
|
||||||
|
inline void JA_StopChannel(const int channel);
|
||||||
|
inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int loop = 0, const int group = 0);
|
||||||
|
inline void JA_CrossfadeMusic(JA_Music_t* music, int crossfade_ms, int loop = -1);
|
||||||
|
|
||||||
|
// --- Music streaming internals ---
|
||||||
|
// Bytes-per-sample per canal (sempre s16)
|
||||||
|
static constexpr int JA_MUSIC_BYTES_PER_SAMPLE = 2;
|
||||||
|
// Quants shorts decodifiquem per crida a get_samples_short_interleaved.
|
||||||
|
// 8192 shorts = 4096 samples/channel en estèreo ≈ 85ms de so a 48kHz.
|
||||||
|
static constexpr int JA_MUSIC_CHUNK_SHORTS = 8192;
|
||||||
|
// Umbral d'audio per davant del cursor de reproducció. Mantenim ≥ 0.5 s a
|
||||||
|
// l'SDL_AudioStream per absorbir jitter de frame i evitar underruns.
|
||||||
|
static constexpr float JA_MUSIC_LOW_WATER_SECONDS = 0.5f;
|
||||||
|
|
||||||
|
// Decodifica un chunk del vorbis i el volca a l'stream. Retorna samples
|
||||||
|
// decodificats per canal (0 = EOF de l'stream vorbis).
|
||||||
|
inline int JA_FeedMusicChunk(JA_Music_t* music) {
|
||||||
|
if (!music || !music->vorbis || !music->stream) return 0;
|
||||||
|
|
||||||
|
short chunk[JA_MUSIC_CHUNK_SHORTS];
|
||||||
|
const int num_channels = music->spec.channels;
|
||||||
|
const int samples_per_channel = stb_vorbis_get_samples_short_interleaved(
|
||||||
|
music->vorbis,
|
||||||
|
num_channels,
|
||||||
|
chunk,
|
||||||
|
JA_MUSIC_CHUNK_SHORTS);
|
||||||
|
if (samples_per_channel <= 0) return 0;
|
||||||
|
|
||||||
|
const int bytes = samples_per_channel * num_channels * JA_MUSIC_BYTES_PER_SAMPLE;
|
||||||
|
SDL_PutAudioStreamData(music->stream, chunk, bytes);
|
||||||
|
return samples_per_channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reompli l'stream fins que tinga ≥ JA_MUSIC_LOW_WATER_SECONDS bufferats.
|
||||||
|
// En arribar a EOF del vorbis, aplica el loop (times) o deixa drenar.
|
||||||
|
inline void JA_PumpMusic(JA_Music_t* music) {
|
||||||
|
if (!music || !music->vorbis || !music->stream) return;
|
||||||
|
|
||||||
|
const int bytes_per_second = music->spec.freq * music->spec.channels * JA_MUSIC_BYTES_PER_SAMPLE;
|
||||||
|
const int low_water_bytes = static_cast<int>(JA_MUSIC_LOW_WATER_SECONDS * static_cast<float>(bytes_per_second));
|
||||||
|
|
||||||
|
while (SDL_GetAudioStreamAvailable(music->stream) < low_water_bytes) {
|
||||||
|
const int decoded = JA_FeedMusicChunk(music);
|
||||||
|
if (decoded > 0) continue;
|
||||||
|
|
||||||
|
// EOF: si queden loops, rebobinar; si no, tallar i deixar drenar.
|
||||||
|
if (music->times != 0) {
|
||||||
|
stb_vorbis_seek_start(music->vorbis);
|
||||||
|
if (music->times > 0) music->times--;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-carrega `duration_ms` de so dins l'stream actual abans que l'stream
|
||||||
|
// siga robat per outgoing_music (crossfade o fade-out). Imprescindible amb
|
||||||
|
// streaming: l'stream robat no es pot re-alimentar perquè perd la referència
|
||||||
|
// al seu vorbis decoder. No aplica loop — si el vorbis s'esgota abans, parem.
|
||||||
|
inline void JA_PreFillOutgoing(JA_Music_t* music, int duration_ms) {
|
||||||
|
if (!music || !music->vorbis || !music->stream) return;
|
||||||
|
|
||||||
|
const int bytes_per_second = music->spec.freq * music->spec.channels * JA_MUSIC_BYTES_PER_SAMPLE;
|
||||||
|
const int needed_bytes = static_cast<int>((static_cast<int64_t>(duration_ms) * bytes_per_second) / 1000);
|
||||||
|
|
||||||
|
while (SDL_GetAudioStreamAvailable(music->stream) < needed_bytes) {
|
||||||
|
const int decoded = JA_FeedMusicChunk(music);
|
||||||
|
if (decoded <= 0) break; // EOF: deixem drenar el que hi haja
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Core Functions ---
|
||||||
|
|
||||||
|
inline void JA_Update() {
|
||||||
|
// --- Outgoing music fade-out (crossfade o fade-out a silencio) ---
|
||||||
|
if (outgoing_music.stream && outgoing_music.fade.active) {
|
||||||
|
Uint64 now = SDL_GetTicks();
|
||||||
|
Uint64 elapsed = now - outgoing_music.fade.start_time;
|
||||||
|
if (elapsed >= (Uint64)outgoing_music.fade.duration_ms) {
|
||||||
|
SDL_DestroyAudioStream(outgoing_music.stream);
|
||||||
|
outgoing_music.stream = nullptr;
|
||||||
|
outgoing_music.fade.active = false;
|
||||||
|
} else {
|
||||||
|
float percent = (float)elapsed / (float)outgoing_music.fade.duration_ms;
|
||||||
|
SDL_SetAudioStreamGain(outgoing_music.stream, outgoing_music.fade.initial_volume * (1.0f - percent));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Current music ---
|
||||||
|
if (JA_musicEnabled && current_music && current_music->state == JA_MUSIC_PLAYING) {
|
||||||
|
// Fade-in (parte de un crossfade)
|
||||||
|
if (incoming_fade.active) {
|
||||||
|
Uint64 now = SDL_GetTicks();
|
||||||
|
Uint64 elapsed = now - incoming_fade.start_time;
|
||||||
|
if (elapsed >= (Uint64)incoming_fade.duration_ms) {
|
||||||
|
incoming_fade.active = false;
|
||||||
|
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
|
||||||
|
} else {
|
||||||
|
float percent = (float)elapsed / (float)incoming_fade.duration_ms;
|
||||||
|
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume * percent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Streaming: rellenem l'stream fins al low-water-mark i parem si el
|
||||||
|
// vorbis s'ha esgotat i no queden loops.
|
||||||
|
JA_PumpMusic(current_music);
|
||||||
|
if (current_music->times == 0 && SDL_GetAudioStreamAvailable(current_music->stream) == 0) {
|
||||||
|
JA_StopMusic();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Sound channels ---
|
||||||
|
if (JA_soundEnabled) {
|
||||||
|
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i)
|
||||||
|
if (channels[i].state == JA_CHANNEL_PLAYING) {
|
||||||
|
if (channels[i].times != 0) {
|
||||||
|
if ((Uint32)SDL_GetAudioStreamAvailable(channels[i].stream) < (channels[i].sound->length / 2)) {
|
||||||
|
SDL_PutAudioStreamData(channels[i].stream, channels[i].sound->buffer.get(), channels[i].sound->length);
|
||||||
|
if (channels[i].times > 0) channels[i].times--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (SDL_GetAudioStreamAvailable(channels[i].stream) == 0) JA_StopChannel(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void JA_Init(const int freq, const SDL_AudioFormat format, const int num_channels) {
|
||||||
|
JA_audioSpec = {format, num_channels, freq};
|
||||||
|
if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice);
|
||||||
|
sdlAudioDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &JA_audioSpec);
|
||||||
|
if (sdlAudioDevice == 0) std::cout << "Failed to initialize SDL audio!" << '\n';
|
||||||
|
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i) channels[i].state = JA_CHANNEL_FREE;
|
||||||
|
for (int i = 0; i < JA_MAX_GROUPS; ++i) JA_soundVolume[i] = 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void JA_Quit() {
|
||||||
|
if (outgoing_music.stream) {
|
||||||
|
SDL_DestroyAudioStream(outgoing_music.stream);
|
||||||
|
outgoing_music.stream = nullptr;
|
||||||
|
}
|
||||||
|
if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice);
|
||||||
|
sdlAudioDevice = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Music Functions ---
|
||||||
|
|
||||||
|
inline JA_Music_t* JA_LoadMusic(const Uint8* buffer, Uint32 length) {
|
||||||
|
if (!buffer || length == 0) return nullptr;
|
||||||
|
|
||||||
|
// Allocem el JA_Music_t primer per aprofitar el seu `std::vector<Uint8>`
|
||||||
|
// com a propietari del OGG comprimit. stb_vorbis guarda un punter
|
||||||
|
// persistent al buffer; com que ací no el resize'jem, el .data() és
|
||||||
|
// estable durant tot el cicle de vida del music.
|
||||||
|
auto* music = new JA_Music_t();
|
||||||
|
music->ogg_data.assign(buffer, buffer + length);
|
||||||
|
|
||||||
|
int error = 0;
|
||||||
|
music->vorbis = stb_vorbis_open_memory(music->ogg_data.data(),
|
||||||
|
static_cast<int>(length),
|
||||||
|
&error,
|
||||||
|
nullptr);
|
||||||
|
if (!music->vorbis) {
|
||||||
|
std::cout << "JA_LoadMusic: stb_vorbis_open_memory failed (error " << error << ")" << '\n';
|
||||||
|
delete music;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stb_vorbis_info info = stb_vorbis_get_info(music->vorbis);
|
||||||
|
music->spec.channels = info.channels;
|
||||||
|
music->spec.freq = static_cast<int>(info.sample_rate);
|
||||||
|
music->spec.format = SDL_AUDIO_S16;
|
||||||
|
music->state = JA_MUSIC_STOPPED;
|
||||||
|
|
||||||
|
return music;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overload amb filename — els callers l'usen per poder comparar la música
|
||||||
|
// en curs amb JA_GetMusicFilename() i no rearrancar-la si ja és la mateixa.
|
||||||
|
inline JA_Music_t* JA_LoadMusic(Uint8* buffer, Uint32 length, const char* filename) {
|
||||||
|
JA_Music_t* music = JA_LoadMusic(static_cast<const Uint8*>(buffer), length);
|
||||||
|
if (music && filename) music->filename = filename;
|
||||||
|
return music;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline JA_Music_t* JA_LoadMusic(const char* filename) {
|
||||||
|
// Carreguem primer el arxiu en memòria i després el descomprimim.
|
||||||
|
FILE* f = fopen(filename, "rb");
|
||||||
|
if (!f) return nullptr;
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
long fsize = ftell(f);
|
||||||
|
fseek(f, 0, SEEK_SET);
|
||||||
|
auto* buffer = static_cast<Uint8*>(malloc(fsize + 1));
|
||||||
|
if (!buffer) {
|
||||||
|
fclose(f);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (fread(buffer, fsize, 1, f) != 1) {
|
||||||
|
fclose(f);
|
||||||
|
free(buffer);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
JA_Music_t* music = JA_LoadMusic(static_cast<const Uint8*>(buffer), static_cast<Uint32>(fsize));
|
||||||
|
if (music) {
|
||||||
|
music->filename = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(buffer);
|
||||||
|
|
||||||
|
return music;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void JA_PlayMusic(JA_Music_t* music, const int loop = -1) {
|
||||||
|
if (!JA_musicEnabled || !music || !music->vorbis) return;
|
||||||
|
|
||||||
|
JA_StopMusic();
|
||||||
|
|
||||||
|
current_music = music;
|
||||||
|
current_music->state = JA_MUSIC_PLAYING;
|
||||||
|
current_music->times = loop;
|
||||||
|
|
||||||
|
// Rebobinem l'stream de vorbis al principi. Cobreix tant play-per-primera-
|
||||||
|
// vegada com replays/canvis de track que tornen a la mateixa pista.
|
||||||
|
stb_vorbis_seek_start(current_music->vorbis);
|
||||||
|
|
||||||
|
current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &JA_audioSpec);
|
||||||
|
if (!current_music->stream) {
|
||||||
|
std::cout << "Failed to create audio stream!" << '\n';
|
||||||
|
current_music->state = JA_MUSIC_STOPPED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
|
||||||
|
|
||||||
|
// Pre-cargem el buffer abans de bindejar per evitar un underrun inicial.
|
||||||
|
JA_PumpMusic(current_music);
|
||||||
|
|
||||||
|
if (!SDL_BindAudioStream(sdlAudioDevice, current_music->stream)) {
|
||||||
|
std::cout << "[ERROR] SDL_BindAudioStream failed!" << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const char* JA_GetMusicFilename(const JA_Music_t* music = nullptr) {
|
||||||
|
if (!music) music = current_music;
|
||||||
|
if (!music || music->filename.empty()) return nullptr;
|
||||||
|
return music->filename.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void JA_PauseMusic() {
|
||||||
|
if (!JA_musicEnabled) return;
|
||||||
|
if (!current_music || current_music->state != JA_MUSIC_PLAYING) return;
|
||||||
|
|
||||||
|
current_music->state = JA_MUSIC_PAUSED;
|
||||||
|
SDL_UnbindAudioStream(current_music->stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void JA_ResumeMusic() {
|
||||||
|
if (!JA_musicEnabled) return;
|
||||||
|
if (!current_music || current_music->state != JA_MUSIC_PAUSED) return;
|
||||||
|
|
||||||
|
current_music->state = JA_MUSIC_PLAYING;
|
||||||
|
SDL_BindAudioStream(sdlAudioDevice, current_music->stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void JA_StopMusic() {
|
||||||
|
// Limpiar outgoing crossfade si existe
|
||||||
|
if (outgoing_music.stream) {
|
||||||
|
SDL_DestroyAudioStream(outgoing_music.stream);
|
||||||
|
outgoing_music.stream = nullptr;
|
||||||
|
outgoing_music.fade.active = false;
|
||||||
|
}
|
||||||
|
incoming_fade.active = false;
|
||||||
|
|
||||||
|
if (!current_music || current_music->state == JA_MUSIC_INVALID || current_music->state == JA_MUSIC_STOPPED) return;
|
||||||
|
|
||||||
|
current_music->state = JA_MUSIC_STOPPED;
|
||||||
|
if (current_music->stream) {
|
||||||
|
SDL_DestroyAudioStream(current_music->stream);
|
||||||
|
current_music->stream = nullptr;
|
||||||
|
}
|
||||||
|
// Deixem el handle de vorbis viu — es tanca en JA_DeleteMusic.
|
||||||
|
// Rebobinem perquè un futur JA_PlayMusic comence des del principi.
|
||||||
|
if (current_music->vorbis) {
|
||||||
|
stb_vorbis_seek_start(current_music->vorbis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void JA_FadeOutMusic(const int milliseconds) {
|
||||||
|
if (!JA_musicEnabled) return;
|
||||||
|
if (!current_music || current_music->state != JA_MUSIC_PLAYING) return;
|
||||||
|
|
||||||
|
// Destruir outgoing anterior si existe
|
||||||
|
if (outgoing_music.stream) {
|
||||||
|
SDL_DestroyAudioStream(outgoing_music.stream);
|
||||||
|
outgoing_music.stream = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-omplim l'stream amb `milliseconds` de so: un cop robat, ja no
|
||||||
|
// tindrà accés al vorbis decoder i només podrà drenar el que tinga.
|
||||||
|
JA_PreFillOutgoing(current_music, milliseconds);
|
||||||
|
|
||||||
|
// Robar el stream del current_music al outgoing
|
||||||
|
outgoing_music.stream = current_music->stream;
|
||||||
|
outgoing_music.fade = {true, SDL_GetTicks(), milliseconds, JA_musicVolume};
|
||||||
|
|
||||||
|
// Dejar current_music sin stream (ya lo tiene outgoing)
|
||||||
|
current_music->stream = nullptr;
|
||||||
|
current_music->state = JA_MUSIC_STOPPED;
|
||||||
|
if (current_music->vorbis) stb_vorbis_seek_start(current_music->vorbis);
|
||||||
|
incoming_fade.active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void JA_CrossfadeMusic(JA_Music_t* music, const int crossfade_ms, const int loop) {
|
||||||
|
if (!JA_musicEnabled || !music || !music->vorbis) return;
|
||||||
|
|
||||||
|
// Destruir outgoing anterior si existe (crossfade durante crossfade)
|
||||||
|
if (outgoing_music.stream) {
|
||||||
|
SDL_DestroyAudioStream(outgoing_music.stream);
|
||||||
|
outgoing_music.stream = nullptr;
|
||||||
|
outgoing_music.fade.active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Robar el stream de la musica actual al outgoing para el fade-out.
|
||||||
|
// Pre-omplim amb `crossfade_ms` de so perquè no es quede en silenci
|
||||||
|
// abans d'acabar el fade (l'stream robat ja no pot alimentar-se).
|
||||||
|
if (current_music && current_music->state == JA_MUSIC_PLAYING && current_music->stream) {
|
||||||
|
JA_PreFillOutgoing(current_music, crossfade_ms);
|
||||||
|
outgoing_music.stream = current_music->stream;
|
||||||
|
outgoing_music.fade = {true, SDL_GetTicks(), crossfade_ms, JA_musicVolume};
|
||||||
|
current_music->stream = nullptr;
|
||||||
|
current_music->state = JA_MUSIC_STOPPED;
|
||||||
|
if (current_music->vorbis) stb_vorbis_seek_start(current_music->vorbis);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iniciar la nueva pista con gain=0 (el fade-in la sube gradualmente)
|
||||||
|
current_music = music;
|
||||||
|
current_music->state = JA_MUSIC_PLAYING;
|
||||||
|
current_music->times = loop;
|
||||||
|
|
||||||
|
stb_vorbis_seek_start(current_music->vorbis);
|
||||||
|
current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &JA_audioSpec);
|
||||||
|
if (!current_music->stream) {
|
||||||
|
std::cout << "Failed to create audio stream for crossfade!" << '\n';
|
||||||
|
current_music->state = JA_MUSIC_STOPPED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SDL_SetAudioStreamGain(current_music->stream, 0.0f);
|
||||||
|
JA_PumpMusic(current_music); // pre-carrega abans de bindejar
|
||||||
|
SDL_BindAudioStream(sdlAudioDevice, current_music->stream);
|
||||||
|
|
||||||
|
// Configurar fade-in
|
||||||
|
incoming_fade = {true, SDL_GetTicks(), crossfade_ms, 0.0f};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline JA_Music_state JA_GetMusicState() {
|
||||||
|
if (!JA_musicEnabled) return JA_MUSIC_DISABLED;
|
||||||
|
if (!current_music) return JA_MUSIC_INVALID;
|
||||||
|
|
||||||
|
return current_music->state;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void JA_DeleteMusic(JA_Music_t* music) {
|
||||||
|
if (!music) return;
|
||||||
|
if (current_music == music) {
|
||||||
|
JA_StopMusic();
|
||||||
|
current_music = nullptr;
|
||||||
|
}
|
||||||
|
if (music->stream) SDL_DestroyAudioStream(music->stream);
|
||||||
|
if (music->vorbis) stb_vorbis_close(music->vorbis);
|
||||||
|
// ogg_data (std::vector) i filename (std::string) s'alliberen sols
|
||||||
|
// al destructor de JA_Music_t.
|
||||||
|
delete music;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float JA_SetMusicVolume(float volume) {
|
||||||
|
JA_musicVolume = SDL_clamp(volume, 0.0f, 1.0f);
|
||||||
|
if (current_music && current_music->stream) {
|
||||||
|
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
|
||||||
|
}
|
||||||
|
return JA_musicVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void JA_SetMusicPosition(float /*value*/) {
|
||||||
|
// No implementat amb el backend de streaming.
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float JA_GetMusicPosition() {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void JA_EnableMusic(const bool value) {
|
||||||
|
if (!value && current_music && (current_music->state == JA_MUSIC_PLAYING)) JA_StopMusic();
|
||||||
|
JA_musicEnabled = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Sound Functions ---
|
||||||
|
|
||||||
|
inline JA_Sound_t* JA_LoadSound(uint8_t* buffer, uint32_t size) {
|
||||||
|
auto sound = std::make_unique<JA_Sound_t>();
|
||||||
|
Uint8* raw = nullptr;
|
||||||
|
if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), 1, &sound->spec, &raw, &sound->length)) {
|
||||||
|
std::cout << "Failed to load WAV from memory: " << SDL_GetError() << '\n';
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer
|
||||||
|
return sound.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline JA_Sound_t* JA_LoadSound(const char* filename) {
|
||||||
|
auto sound = std::make_unique<JA_Sound_t>();
|
||||||
|
Uint8* raw = nullptr;
|
||||||
|
if (!SDL_LoadWAV(filename, &sound->spec, &raw, &sound->length)) {
|
||||||
|
std::cout << "Failed to load WAV file: " << SDL_GetError() << '\n';
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer
|
||||||
|
return sound.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int JA_PlaySound(JA_Sound_t* sound, const int loop = 0, const int group = 0) {
|
||||||
|
if (!JA_soundEnabled || !sound) return -1;
|
||||||
|
|
||||||
|
int channel = 0;
|
||||||
|
while (channel < JA_MAX_SIMULTANEOUS_CHANNELS && channels[channel].state != JA_CHANNEL_FREE) { channel++; }
|
||||||
|
if (channel == JA_MAX_SIMULTANEOUS_CHANNELS) {
|
||||||
|
// No hay canal libre, reemplazamos el primero
|
||||||
|
channel = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JA_PlaySoundOnChannel(sound, channel, loop, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int loop, const int group) {
|
||||||
|
if (!JA_soundEnabled || !sound) return -1;
|
||||||
|
if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return -1;
|
||||||
|
|
||||||
|
JA_StopChannel(channel);
|
||||||
|
|
||||||
|
channels[channel].sound = sound;
|
||||||
|
channels[channel].times = loop;
|
||||||
|
channels[channel].pos = 0;
|
||||||
|
channels[channel].group = group;
|
||||||
|
channels[channel].state = JA_CHANNEL_PLAYING;
|
||||||
|
channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec);
|
||||||
|
|
||||||
|
if (!channels[channel].stream) {
|
||||||
|
std::cout << "Failed to create audio stream for sound!" << '\n';
|
||||||
|
channels[channel].state = JA_CHANNEL_FREE;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer.get(), channels[channel].sound->length);
|
||||||
|
SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume[group]);
|
||||||
|
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
|
||||||
|
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void JA_DeleteSound(JA_Sound_t* sound) {
|
||||||
|
if (!sound) return;
|
||||||
|
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
|
||||||
|
if (channels[i].sound == sound) JA_StopChannel(i);
|
||||||
|
}
|
||||||
|
// buffer es destrueix automàticament via RAII (SDLFreeDeleter).
|
||||||
|
delete sound;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void JA_PauseChannel(const int channel) {
|
||||||
|
if (!JA_soundEnabled) return;
|
||||||
|
|
||||||
|
if (channel == -1) {
|
||||||
|
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
|
||||||
|
if (channels[i].state == JA_CHANNEL_PLAYING) {
|
||||||
|
channels[i].state = JA_CHANNEL_PAUSED;
|
||||||
|
SDL_UnbindAudioStream(channels[i].stream);
|
||||||
|
}
|
||||||
|
} else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) {
|
||||||
|
if (channels[channel].state == JA_CHANNEL_PLAYING) {
|
||||||
|
channels[channel].state = JA_CHANNEL_PAUSED;
|
||||||
|
SDL_UnbindAudioStream(channels[channel].stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void JA_ResumeChannel(const int channel) {
|
||||||
|
if (!JA_soundEnabled) return;
|
||||||
|
|
||||||
|
if (channel == -1) {
|
||||||
|
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
|
||||||
|
if (channels[i].state == JA_CHANNEL_PAUSED) {
|
||||||
|
channels[i].state = JA_CHANNEL_PLAYING;
|
||||||
|
SDL_BindAudioStream(sdlAudioDevice, channels[i].stream);
|
||||||
|
}
|
||||||
|
} else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) {
|
||||||
|
if (channels[channel].state == JA_CHANNEL_PAUSED) {
|
||||||
|
channels[channel].state = JA_CHANNEL_PLAYING;
|
||||||
|
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void JA_StopChannel(const int channel) {
|
||||||
|
if (channel == -1) {
|
||||||
|
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
|
||||||
|
if (channels[i].state != JA_CHANNEL_FREE) {
|
||||||
|
if (channels[i].stream) SDL_DestroyAudioStream(channels[i].stream);
|
||||||
|
channels[i].stream = nullptr;
|
||||||
|
channels[i].state = JA_CHANNEL_FREE;
|
||||||
|
channels[i].pos = 0;
|
||||||
|
channels[i].sound = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) {
|
||||||
|
if (channels[channel].state != JA_CHANNEL_FREE) {
|
||||||
|
if (channels[channel].stream) SDL_DestroyAudioStream(channels[channel].stream);
|
||||||
|
channels[channel].stream = nullptr;
|
||||||
|
channels[channel].state = JA_CHANNEL_FREE;
|
||||||
|
channels[channel].pos = 0;
|
||||||
|
channels[channel].sound = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline JA_Channel_state JA_GetChannelState(const int channel) {
|
||||||
|
if (!JA_soundEnabled) return JA_SOUND_DISABLED;
|
||||||
|
if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return JA_CHANNEL_INVALID;
|
||||||
|
|
||||||
|
return channels[channel].state;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float JA_SetSoundVolume(float volume, const int group = -1) {
|
||||||
|
const float v = SDL_clamp(volume, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
if (group == -1) {
|
||||||
|
for (int i = 0; i < JA_MAX_GROUPS; ++i) {
|
||||||
|
JA_soundVolume[i] = v;
|
||||||
|
}
|
||||||
|
} else if (group >= 0 && group < JA_MAX_GROUPS) {
|
||||||
|
JA_soundVolume[group] = v;
|
||||||
|
} else {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aplicar volum als canals actius.
|
||||||
|
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
|
||||||
|
if ((channels[i].state == JA_CHANNEL_PLAYING) || (channels[i].state == JA_CHANNEL_PAUSED)) {
|
||||||
|
if (group == -1 || channels[i].group == group) {
|
||||||
|
if (channels[i].stream) {
|
||||||
|
SDL_SetAudioStreamGain(channels[i].stream, JA_soundVolume[channels[i].group]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void JA_EnableSound(const bool value) {
|
||||||
|
if (!value) {
|
||||||
|
JA_StopChannel(-1);
|
||||||
|
}
|
||||||
|
JA_soundEnabled = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float JA_SetVolume(float volume) {
|
||||||
|
float v = JA_SetMusicVolume(volume);
|
||||||
|
JA_SetSoundVolume(v, -1);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
42
source/core/input/global_inputs.cpp
Normal file
42
source/core/input/global_inputs.cpp
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#include "core/input/global_inputs.hpp"
|
||||||
|
|
||||||
|
#include "core/input/input.h"
|
||||||
|
#include "core/rendering/screen.h"
|
||||||
|
|
||||||
|
namespace GlobalInputs {
|
||||||
|
|
||||||
|
auto handle() -> bool {
|
||||||
|
if (Screen::get() == nullptr || Input::get() == nullptr) { return false; }
|
||||||
|
|
||||||
|
if (Input::get()->checkInput(input_window_fullscreen, REPEAT_FALSE)) {
|
||||||
|
Screen::get()->toggleVideoMode();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (Input::get()->checkInput(input_window_dec_size, REPEAT_FALSE)) {
|
||||||
|
Screen::get()->decWindowZoom();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (Input::get()->checkInput(input_window_inc_size, REPEAT_FALSE)) {
|
||||||
|
Screen::get()->incWindowZoom();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (Input::get()->checkInput(input_prev_preset, REPEAT_FALSE)) {
|
||||||
|
Screen::get()->prevPreset();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (Input::get()->checkInput(input_next_preset, REPEAT_FALSE)) {
|
||||||
|
Screen::get()->nextPreset();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (Input::get()->checkInput(input_toggle_shader, REPEAT_FALSE)) {
|
||||||
|
Screen::get()->toggleShaderEnabled();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (Input::get()->checkInput(input_toggle_shader_type, REPEAT_FALSE)) {
|
||||||
|
Screen::get()->toggleActiveShader();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace GlobalInputs
|
||||||
10
source/core/input/global_inputs.hpp
Normal file
10
source/core/input/global_inputs.hpp
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace GlobalInputs {
|
||||||
|
// Gestiona els atalls globals disponibles en qualsevol escena: zoom de
|
||||||
|
// finestra (F1/F2), fullscreen (F3), presets de shader (F8/F9), toggle
|
||||||
|
// shader (F10) i tipus de shader POSTFX↔CRTPI (F11). Retorna true si ha
|
||||||
|
// consumit alguna tecla (per si la capa cridant vol suprimir-la del
|
||||||
|
// processament específic de l'escena).
|
||||||
|
auto handle() -> bool;
|
||||||
|
} // namespace GlobalInputs
|
||||||
@@ -1,14 +1,69 @@
|
|||||||
#include "input.h"
|
#include "core/input/input.h"
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <iostream> // for basic_ostream, operator<<, cout, basi...
|
#include <algorithm> // for any_of
|
||||||
|
#include <iostream> // for basic_ostream, operator<<, cout, basi...
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instancia única
|
||||||
|
Input *Input::instance = nullptr;
|
||||||
|
|
||||||
|
// Singleton API
|
||||||
|
void Input::init(const std::string &gameControllerDbPath) {
|
||||||
|
Input::instance = new Input(gameControllerDbPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Input::destroy() {
|
||||||
|
delete Input::instance;
|
||||||
|
Input::instance = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Input::get() -> Input * {
|
||||||
|
return Input::instance;
|
||||||
|
}
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Input::Input(std::string file) {
|
Input::Input(const std::string &file)
|
||||||
// Fichero gamecontrollerdb.txt
|
: numGamepads(0),
|
||||||
dbPath = file;
|
dbPath(file),
|
||||||
|
verbose(true),
|
||||||
|
disabledUntil(d_notDisabled),
|
||||||
|
enabled(true) {
|
||||||
// Inicializa las variables
|
// Inicializa las variables
|
||||||
keyBindings_t kb;
|
keyBindings_t kb;
|
||||||
kb.scancode = 0;
|
kb.scancode = 0;
|
||||||
@@ -19,9 +74,19 @@ Input::Input(std::string file) {
|
|||||||
gcb.button = SDL_GAMEPAD_BUTTON_INVALID;
|
gcb.button = SDL_GAMEPAD_BUTTON_INVALID;
|
||||||
gcb.active = false;
|
gcb.active = false;
|
||||||
gameControllerBindings.resize(input_number_of_inputs, gcb);
|
gameControllerBindings.resize(input_number_of_inputs, gcb);
|
||||||
|
}
|
||||||
|
|
||||||
verbose = true;
|
// Destructor
|
||||||
enabled = true;
|
Input::~Input() {
|
||||||
|
for (auto *pad : connectedControllers) {
|
||||||
|
if (pad != nullptr) {
|
||||||
|
SDL_CloseGamepad(pad);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connectedControllers.clear();
|
||||||
|
connectedControllerIds.clear();
|
||||||
|
controllerNames.clear();
|
||||||
|
numGamepads = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualiza el estado del objeto
|
// Actualiza el estado del objeto
|
||||||
@@ -82,7 +147,7 @@ bool Input::checkInput(Uint8 input, bool repeat, int device, int index) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gameControllerFound())
|
if (gameControllerFound() && index >= 0 && index < (int)connectedControllers.size())
|
||||||
if ((device == INPUT_USE_GAMECONTROLLER) || (device == INPUT_USE_ANY)) {
|
if ((device == INPUT_USE_GAMECONTROLLER) || (device == INPUT_USE_ANY)) {
|
||||||
if (repeat) {
|
if (repeat) {
|
||||||
if (SDL_GetGamepadButton(connectedControllers[index], gameControllerBindings[input].button)) {
|
if (SDL_GetGamepadButton(connectedControllers[index], gameControllerBindings[input].button)) {
|
||||||
@@ -128,7 +193,7 @@ bool Input::checkAnyInput(int device, int index) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gameControllerFound()) {
|
if (gameControllerFound() && index >= 0 && index < (int)connectedControllers.size()) {
|
||||||
if (device == INPUT_USE_GAMECONTROLLER || device == INPUT_USE_ANY) {
|
if (device == INPUT_USE_GAMECONTROLLER || device == INPUT_USE_ANY) {
|
||||||
for (int i = 0; i < (int)gameControllerBindings.size(); ++i) {
|
for (int i = 0; i < (int)gameControllerBindings.size(); ++i) {
|
||||||
if (SDL_GetGamepadButton(connectedControllers[index], gameControllerBindings[i].button)) {
|
if (SDL_GetGamepadButton(connectedControllers[index], gameControllerBindings[i].button)) {
|
||||||
@@ -141,8 +206,40 @@ bool Input::checkAnyInput(int device, int index) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Busca si hay un mando conectado
|
// Construye el nombre visible de un mando.
|
||||||
|
// Recorta des del primer '(' o '[' (per a evitar coses tipus
|
||||||
|
// "Retroid Controller (vendor: 1001) ...") i talla a 25 caràcters.
|
||||||
|
std::string Input::buildControllerName(SDL_Gamepad *pad, int padIndex) {
|
||||||
|
(void)padIndex;
|
||||||
|
const char *padName = SDL_GetGamepadName(pad);
|
||||||
|
std::string name = padName ? padName : "Unknown";
|
||||||
|
const auto pos = name.find_first_of("([");
|
||||||
|
if (pos != std::string::npos) {
|
||||||
|
name.erase(pos);
|
||||||
|
}
|
||||||
|
while (!name.empty() && name.back() == ' ') {
|
||||||
|
name.pop_back();
|
||||||
|
}
|
||||||
|
if (name.size() > 25) {
|
||||||
|
name.resize(25);
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Busca si hay un mando conectado. Cierra y limpia el estado previo para
|
||||||
|
// que la función sea idempotente si se invoca más de una vez.
|
||||||
bool Input::discoverGameController() {
|
bool Input::discoverGameController() {
|
||||||
|
// Cierra los mandos ya abiertos y limpia los vectores paralelos
|
||||||
|
for (auto *pad : connectedControllers) {
|
||||||
|
if (pad != nullptr) {
|
||||||
|
SDL_CloseGamepad(pad);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connectedControllers.clear();
|
||||||
|
connectedControllerIds.clear();
|
||||||
|
controllerNames.clear();
|
||||||
|
numGamepads = 0;
|
||||||
|
|
||||||
bool found = false;
|
bool found = false;
|
||||||
|
|
||||||
if (SDL_WasInit(SDL_INIT_GAMEPAD) != SDL_INIT_GAMEPAD) {
|
if (SDL_WasInit(SDL_INIT_GAMEPAD) != SDL_INIT_GAMEPAD) {
|
||||||
@@ -157,42 +254,39 @@ bool Input::discoverGameController() {
|
|||||||
|
|
||||||
int nJoysticks = 0;
|
int nJoysticks = 0;
|
||||||
SDL_JoystickID *joysticks = SDL_GetJoysticks(&nJoysticks);
|
SDL_JoystickID *joysticks = SDL_GetJoysticks(&nJoysticks);
|
||||||
numGamepads = 0;
|
|
||||||
|
|
||||||
if (joysticks) {
|
if (joysticks) {
|
||||||
// Cuenta el numero de mandos
|
int gamepadCount = 0;
|
||||||
for (int i = 0; i < nJoysticks; ++i) {
|
for (int i = 0; i < nJoysticks; ++i) {
|
||||||
if (SDL_IsGamepad(joysticks[i])) {
|
if (SDL_IsGamepad(joysticks[i])) {
|
||||||
numGamepads++;
|
gamepadCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
std::cout << "\nChecking for game controllers...\n";
|
std::cout << "\nChecking for game controllers...\n";
|
||||||
std::cout << nJoysticks << " joysticks found, " << numGamepads << " are gamepads\n";
|
std::cout << nJoysticks << " joysticks found, " << gamepadCount << " are gamepads\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (numGamepads > 0) {
|
if (gamepadCount > 0) {
|
||||||
found = true;
|
found = true;
|
||||||
int padIndex = 0;
|
int padIndex = 0;
|
||||||
|
|
||||||
for (int i = 0; i < nJoysticks; i++) {
|
for (int i = 0; i < nJoysticks; i++) {
|
||||||
if (!SDL_IsGamepad(joysticks[i])) continue;
|
if (!SDL_IsGamepad(joysticks[i])) continue;
|
||||||
|
|
||||||
// Abre el mando y lo añade a la lista
|
installWebStandardMapping(joysticks[i]);
|
||||||
SDL_Gamepad *pad = SDL_OpenGamepad(joysticks[i]);
|
SDL_Gamepad *pad = SDL_OpenGamepad(joysticks[i]);
|
||||||
if (pad != nullptr) {
|
if (pad != nullptr) {
|
||||||
|
const std::string name = buildControllerName(pad, padIndex);
|
||||||
connectedControllers.push_back(pad);
|
connectedControllers.push_back(pad);
|
||||||
const std::string separator(" #");
|
connectedControllerIds.push_back(joysticks[i]);
|
||||||
const char *padName = SDL_GetGamepadName(pad);
|
controllerNames.push_back(name);
|
||||||
std::string name = padName ? padName : "Unknown";
|
numGamepads++;
|
||||||
name.resize(25);
|
padIndex++;
|
||||||
name = name + separator + std::to_string(padIndex);
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
std::cout << name << std::endl;
|
std::cout << name << std::endl;
|
||||||
}
|
}
|
||||||
controllerNames.push_back(name);
|
|
||||||
padIndex++;
|
|
||||||
} else {
|
} else {
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
std::cout << "SDL_GetError() = " << SDL_GetError() << std::endl;
|
std::cout << "SDL_GetError() = " << SDL_GetError() << std::endl;
|
||||||
@@ -209,6 +303,65 @@ bool Input::discoverGameController() {
|
|||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Procesa un evento SDL_EVENT_GAMEPAD_ADDED
|
||||||
|
bool Input::handleGamepadAdded(SDL_JoystickID jid, std::string &outName) {
|
||||||
|
if (!SDL_IsGamepad(jid)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si el mando ya está registrado no hace nada (ej. evento retroactivo tras el scan inicial)
|
||||||
|
if (std::any_of(connectedControllerIds.begin(), connectedControllerIds.end(), [jid](SDL_JoystickID existing) { return existing == jid; })) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
installWebStandardMapping(jid);
|
||||||
|
SDL_Gamepad *pad = SDL_OpenGamepad(jid);
|
||||||
|
if (pad == nullptr) {
|
||||||
|
if (verbose) {
|
||||||
|
std::cout << "Failed to open gamepad " << jid << ": " << SDL_GetError() << std::endl;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int padIndex = (int)connectedControllers.size();
|
||||||
|
const std::string name = buildControllerName(pad, padIndex);
|
||||||
|
connectedControllers.push_back(pad);
|
||||||
|
connectedControllerIds.push_back(jid);
|
||||||
|
controllerNames.push_back(name);
|
||||||
|
numGamepads++;
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
std::cout << "Gamepad connected: " << name << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
outName = name;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Procesa un evento SDL_EVENT_GAMEPAD_REMOVED
|
||||||
|
bool Input::handleGamepadRemoved(SDL_JoystickID jid, std::string &outName) {
|
||||||
|
for (size_t i = 0; i < connectedControllerIds.size(); ++i) {
|
||||||
|
if (connectedControllerIds[i] != jid) continue;
|
||||||
|
|
||||||
|
outName = controllerNames[i];
|
||||||
|
if (connectedControllers[i] != nullptr) {
|
||||||
|
SDL_CloseGamepad(connectedControllers[i]);
|
||||||
|
}
|
||||||
|
connectedControllers.erase(connectedControllers.begin() + i);
|
||||||
|
connectedControllerIds.erase(connectedControllerIds.begin() + i);
|
||||||
|
controllerNames.erase(controllerNames.begin() + i);
|
||||||
|
numGamepads--;
|
||||||
|
if (numGamepads < 0) numGamepads = 0;
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
std::cout << "Gamepad disconnected: " << outName << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Comprueba si hay algun mando conectado
|
// Comprueba si hay algun mando conectado
|
||||||
bool Input::gameControllerFound() {
|
bool Input::gameControllerFound() {
|
||||||
if (numGamepads > 0) {
|
if (numGamepads > 0) {
|
||||||
@@ -34,6 +34,12 @@ enum inputs_e {
|
|||||||
input_window_inc_size,
|
input_window_inc_size,
|
||||||
input_window_dec_size,
|
input_window_dec_size,
|
||||||
|
|
||||||
|
// GPU / shaders (hotkeys provisionales hasta que haya menú de opciones)
|
||||||
|
input_prev_preset,
|
||||||
|
input_next_preset,
|
||||||
|
input_toggle_shader,
|
||||||
|
input_toggle_shader_type,
|
||||||
|
|
||||||
// Input obligatorio
|
// Input obligatorio
|
||||||
input_number_of_inputs
|
input_number_of_inputs
|
||||||
};
|
};
|
||||||
@@ -57,7 +63,8 @@ class Input {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Objetos y punteros
|
// Objetos y punteros
|
||||||
std::vector<SDL_Gamepad *> connectedControllers; // Vector con todos los mandos conectados
|
std::vector<SDL_Gamepad *> connectedControllers; // Vector con todos los mandos conectados
|
||||||
|
std::vector<SDL_JoystickID> connectedControllerIds; // Instance IDs paralelos para mapear eventos
|
||||||
|
|
||||||
// Variables
|
// Variables
|
||||||
std::vector<keyBindings_t> keyBindings; // Vector con las teclas asociadas a los inputs predefinidos
|
std::vector<keyBindings_t> keyBindings; // Vector con las teclas asociadas a los inputs predefinidos
|
||||||
@@ -69,9 +76,23 @@ class Input {
|
|||||||
i_disable_e disabledUntil; // Tiempo que esta deshabilitado
|
i_disable_e disabledUntil; // Tiempo que esta deshabilitado
|
||||||
bool enabled; // Indica si está habilitado
|
bool enabled; // Indica si está habilitado
|
||||||
|
|
||||||
|
// Construye el nombre visible de un mando (name truncado + sufijo #N)
|
||||||
|
std::string buildControllerName(SDL_Gamepad *pad, int padIndex);
|
||||||
|
|
||||||
|
// Constructor privado (usar Input::init)
|
||||||
|
explicit Input(const std::string &file);
|
||||||
|
|
||||||
|
// Instancia única
|
||||||
|
static Input *instance;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Constructor
|
// Singleton API
|
||||||
Input(std::string file);
|
static void init(const std::string &gameControllerDbPath); // Crea la instancia
|
||||||
|
static void destroy(); // Libera la instancia
|
||||||
|
static auto get() -> Input *; // Obtiene el puntero a la instancia
|
||||||
|
|
||||||
|
// Destructor
|
||||||
|
~Input();
|
||||||
|
|
||||||
// Actualiza el estado del objeto
|
// Actualiza el estado del objeto
|
||||||
void update();
|
void update();
|
||||||
@@ -91,6 +112,14 @@ class Input {
|
|||||||
// Busca si hay un mando conectado
|
// Busca si hay un mando conectado
|
||||||
bool discoverGameController();
|
bool discoverGameController();
|
||||||
|
|
||||||
|
// Procesa un evento SDL_EVENT_GAMEPAD_ADDED. Devuelve true si el mando se ha añadido
|
||||||
|
// (no estaba ya registrado) y escribe el nombre visible en outName.
|
||||||
|
bool handleGamepadAdded(SDL_JoystickID jid, std::string &outName);
|
||||||
|
|
||||||
|
// Procesa un evento SDL_EVENT_GAMEPAD_REMOVED. Devuelve true si se ha encontrado y
|
||||||
|
// eliminado, y escribe el nombre visible en outName.
|
||||||
|
bool handleGamepadRemoved(SDL_JoystickID jid, std::string &outName);
|
||||||
|
|
||||||
// Comprueba si hay algun mando conectado
|
// Comprueba si hay algun mando conectado
|
||||||
bool gameControllerFound();
|
bool gameControllerFound();
|
||||||
|
|
||||||
35
source/core/input/mouse.cpp
Normal file
35
source/core/input/mouse.cpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#include "core/input/mouse.hpp"
|
||||||
|
|
||||||
|
namespace Mouse {
|
||||||
|
Uint32 cursorHideTime = 3000; // Tiempo en milisegundos para ocultar el cursor por inactividad
|
||||||
|
Uint32 lastMouseMoveTime = 0; // Última vez que el ratón se movió
|
||||||
|
bool cursorVisible = true; // Estado del cursor
|
||||||
|
|
||||||
|
void handleEvent(const SDL_Event &event, bool fullscreen) {
|
||||||
|
if (event.type == SDL_EVENT_MOUSE_MOTION) {
|
||||||
|
lastMouseMoveTime = SDL_GetTicks();
|
||||||
|
if (!cursorVisible && !fullscreen) {
|
||||||
|
SDL_ShowCursor();
|
||||||
|
cursorVisible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateCursorVisibility(bool fullscreen) {
|
||||||
|
// En pantalla completa el cursor siempre está oculto
|
||||||
|
if (fullscreen) {
|
||||||
|
if (cursorVisible) {
|
||||||
|
SDL_HideCursor();
|
||||||
|
cursorVisible = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// En modo ventana, lo oculta tras el periodo de inactividad
|
||||||
|
const Uint32 currentTime = SDL_GetTicks();
|
||||||
|
if (cursorVisible && (currentTime - lastMouseMoveTime > cursorHideTime)) {
|
||||||
|
SDL_HideCursor();
|
||||||
|
cursorVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace Mouse
|
||||||
18
source/core/input/mouse.hpp
Normal file
18
source/core/input/mouse.hpp
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
namespace Mouse {
|
||||||
|
extern Uint32 cursorHideTime; // Tiempo en milisegundos para ocultar el cursor por inactividad
|
||||||
|
extern Uint32 lastMouseMoveTime; // Última vez que el ratón se movió
|
||||||
|
extern bool cursorVisible; // Estado del cursor
|
||||||
|
|
||||||
|
// Procesa un evento de ratón. En pantalla completa ignora el movimiento
|
||||||
|
// para no volver a mostrar el cursor.
|
||||||
|
void handleEvent(const SDL_Event &event, bool fullscreen);
|
||||||
|
|
||||||
|
// Actualiza la visibilidad del cursor. En modo ventana lo oculta
|
||||||
|
// después de cursorHideTime ms sin movimiento. En pantalla completa
|
||||||
|
// lo mantiene siempre oculto.
|
||||||
|
void updateCursorVisibility(bool fullscreen);
|
||||||
|
} // namespace Mouse
|
||||||
85
source/core/locale/lang.cpp
Normal file
85
source/core/locale/lang.cpp
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#include "core/locale/lang.h"
|
||||||
|
|
||||||
|
#include <fstream> // for basic_ifstream, basic_istream, ifstream
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "core/resources/asset.h" // for Asset
|
||||||
|
#include "core/resources/resource_helper.h"
|
||||||
|
|
||||||
|
// Instancia única
|
||||||
|
Lang *Lang::instance = nullptr;
|
||||||
|
|
||||||
|
// Singleton API
|
||||||
|
void Lang::init() {
|
||||||
|
Lang::instance = new Lang();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lang::destroy() {
|
||||||
|
delete Lang::instance;
|
||||||
|
Lang::instance = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Lang::get() -> Lang * {
|
||||||
|
return Lang::instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
Lang::Lang() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destructor
|
||||||
|
Lang::~Lang() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inicializa los textos del juego en el idioma seleccionado
|
||||||
|
bool Lang::setLang(Uint8 lang) {
|
||||||
|
std::string file;
|
||||||
|
|
||||||
|
switch (lang) {
|
||||||
|
case es_ES:
|
||||||
|
file = Asset::get()->get("es_ES.txt");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case en_UK:
|
||||||
|
file = Asset::get()->get("en_UK.txt");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ba_BA:
|
||||||
|
file = Asset::get()->get("ba_BA.txt");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
file = Asset::get()->get("en_UK.txt");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_TEXT_STRINGS; i++)
|
||||||
|
mTextStrings[i] = "";
|
||||||
|
|
||||||
|
// Lee el fichero via ResourceHelper (pack o filesystem)
|
||||||
|
auto bytes = ResourceHelper::loadFile(file);
|
||||||
|
if (bytes.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string content(reinterpret_cast<const char *>(bytes.data()), bytes.size());
|
||||||
|
std::stringstream ss(content);
|
||||||
|
std::string line;
|
||||||
|
int index = 0;
|
||||||
|
while (std::getline(ss, line)) {
|
||||||
|
// Almacena solo las lineas que no empiezan por # o no esten vacias
|
||||||
|
const bool test1 = line.substr(0, 1) != "#";
|
||||||
|
const bool test2 = !line.empty();
|
||||||
|
if (test1 && test2) {
|
||||||
|
mTextStrings[index] = line;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtiene la cadena de texto del indice
|
||||||
|
std::string Lang::getText(int index) {
|
||||||
|
return mTextStrings[index];
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <string> // for string, basic_string
|
#include <string> // for string, basic_string
|
||||||
class Asset;
|
|
||||||
|
|
||||||
// Códigos de idioma
|
// Códigos de idioma
|
||||||
constexpr int es_ES = 0;
|
constexpr int es_ES = 0;
|
||||||
@@ -17,12 +16,19 @@ constexpr int MAX_TEXT_STRINGS = 100;
|
|||||||
// Clase Lang
|
// Clase Lang
|
||||||
class Lang {
|
class Lang {
|
||||||
private:
|
private:
|
||||||
Asset *mAsset; // Objeto que gestiona todos los ficheros de recursos
|
|
||||||
std::string mTextStrings[MAX_TEXT_STRINGS]; // Vector con los textos
|
std::string mTextStrings[MAX_TEXT_STRINGS]; // Vector con los textos
|
||||||
|
|
||||||
|
// Constructor privado (usar Lang::init)
|
||||||
|
Lang();
|
||||||
|
|
||||||
|
// Instancia única
|
||||||
|
static Lang *instance;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Constructor
|
// Singleton API
|
||||||
Lang(Asset *mAsset);
|
static void init(); // Crea la instancia
|
||||||
|
static void destroy(); // Libera la instancia
|
||||||
|
static auto get() -> Lang *; // Obtiene el puntero a la instancia
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
~Lang();
|
~Lang();
|
||||||
@@ -1,170 +1,144 @@
|
|||||||
#include "animatedsprite.h"
|
#include "core/rendering/animatedsprite.h"
|
||||||
|
|
||||||
#include <fstream> // for basic_ostream, operator<<, basic_istream, basic...
|
#include <fstream> // for basic_ostream, operator<<, basic_istream, basic...
|
||||||
#include <iostream> // for cout
|
#include <iostream> // for cout
|
||||||
#include <sstream> // for basic_stringstream
|
#include <sstream> // for basic_stringstream
|
||||||
|
|
||||||
#include "texture.h" // for Texture
|
#include "core/rendering/texture.h" // for Texture
|
||||||
|
|
||||||
// Carga la animación desde un fichero
|
// Parser compartido: lee un istream con el formato .ani
|
||||||
animatedSprite_t loadAnimationFromFile(Texture *texture, std::string filePath, bool verbose) {
|
static animatedSprite_t parseAnimationStream(std::istream &file, Texture *texture, const std::string &filename, bool verbose) {
|
||||||
// Inicializa variables
|
|
||||||
animatedSprite_t as;
|
animatedSprite_t as;
|
||||||
as.texture = texture;
|
as.texture = texture;
|
||||||
int framesPerRow = 0;
|
int framesPerRow = 0;
|
||||||
int frameWidth = 0;
|
int frameWidth = 0;
|
||||||
int frameHeight = 0;
|
int frameHeight = 0;
|
||||||
int maxTiles = 0;
|
int maxTiles = 0;
|
||||||
|
|
||||||
const std::string filename = filePath.substr(filePath.find_last_of("\\/") + 1);
|
|
||||||
std::ifstream file(filePath);
|
|
||||||
std::string line;
|
std::string line;
|
||||||
|
|
||||||
// El fichero se puede abrir
|
if (verbose) {
|
||||||
if (file.good()) {
|
std::cout << "Animation loaded: " << filename << std::endl;
|
||||||
// Procesa el fichero linea a linea
|
}
|
||||||
if (verbose) {
|
while (std::getline(file, line)) {
|
||||||
std::cout << "Animation loaded: " << filename << std::endl;
|
if (line == "[animation]") {
|
||||||
}
|
animation_t buffer;
|
||||||
while (std::getline(file, line)) {
|
buffer.speed = 0;
|
||||||
// Si la linea contiene el texto [animation] se realiza el proceso de carga de una animación
|
buffer.loop = -1;
|
||||||
if (line == "[animation]") {
|
buffer.counter = 0;
|
||||||
animation_t buffer;
|
buffer.currentFrame = 0;
|
||||||
buffer.counter = 0;
|
buffer.completed = false;
|
||||||
buffer.currentFrame = 0;
|
|
||||||
buffer.completed = false;
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
std::getline(file, line);
|
std::getline(file, line);
|
||||||
|
|
||||||
// Encuentra la posición del caracter '='
|
|
||||||
int pos = line.find("=");
|
|
||||||
|
|
||||||
// Procesa las dos subcadenas
|
|
||||||
if (pos != (int)line.npos) {
|
|
||||||
if (line.substr(0, pos) == "name") {
|
|
||||||
buffer.name = line.substr(pos + 1, line.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (line.substr(0, pos) == "speed") {
|
|
||||||
buffer.speed = std::stoi(line.substr(pos + 1, line.length()));
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (line.substr(0, pos) == "loop") {
|
|
||||||
buffer.loop = std::stoi(line.substr(pos + 1, line.length()));
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (line.substr(0, pos) == "frames") {
|
|
||||||
// Se introducen los valores separados por comas en un vector
|
|
||||||
std::stringstream ss(line.substr(pos + 1, line.length()));
|
|
||||||
std::string tmp;
|
|
||||||
SDL_Rect rect = {0, 0, frameWidth, frameHeight};
|
|
||||||
while (getline(ss, tmp, ',')) {
|
|
||||||
// Comprueba que el tile no sea mayor que el maximo indice permitido
|
|
||||||
const int numTile = std::stoi(tmp) > maxTiles ? 0 : std::stoi(tmp);
|
|
||||||
rect.x = (numTile % framesPerRow) * frameWidth;
|
|
||||||
rect.y = (numTile / framesPerRow) * frameHeight;
|
|
||||||
buffer.frames.push_back(rect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (line != "[/animation]");
|
|
||||||
|
|
||||||
// Añade la animación al vector de animaciones
|
|
||||||
as.animations.push_back(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// En caso contrario se parsea el fichero para buscar las variables y los valores
|
|
||||||
else {
|
|
||||||
// Encuentra la posición del caracter '='
|
|
||||||
int pos = line.find("=");
|
int pos = line.find("=");
|
||||||
|
|
||||||
// Procesa las dos subcadenas
|
|
||||||
if (pos != (int)line.npos) {
|
if (pos != (int)line.npos) {
|
||||||
if (line.substr(0, pos) == "framesPerRow") {
|
if (line.substr(0, pos) == "name") {
|
||||||
framesPerRow = std::stoi(line.substr(pos + 1, line.length()));
|
buffer.name = line.substr(pos + 1, line.length());
|
||||||
}
|
} else if (line.substr(0, pos) == "speed") {
|
||||||
|
buffer.speed = std::stoi(line.substr(pos + 1, line.length()));
|
||||||
else if (line.substr(0, pos) == "frameWidth") {
|
} else if (line.substr(0, pos) == "loop") {
|
||||||
frameWidth = std::stoi(line.substr(pos + 1, line.length()));
|
buffer.loop = std::stoi(line.substr(pos + 1, line.length()));
|
||||||
}
|
} else if (line.substr(0, pos) == "frames") {
|
||||||
|
std::stringstream ss(line.substr(pos + 1, line.length()));
|
||||||
else if (line.substr(0, pos) == "frameHeight") {
|
std::string tmp;
|
||||||
frameHeight = std::stoi(line.substr(pos + 1, line.length()));
|
SDL_Rect rect = {0, 0, frameWidth, frameHeight};
|
||||||
}
|
while (getline(ss, tmp, ',')) {
|
||||||
|
const int numTile = std::stoi(tmp) > maxTiles ? 0 : std::stoi(tmp);
|
||||||
else {
|
rect.x = (numTile % framesPerRow) * frameWidth;
|
||||||
|
rect.y = (numTile / framesPerRow) * frameHeight;
|
||||||
|
buffer.frames.push_back(rect);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
|
std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} while (line != "[/animation]");
|
||||||
|
|
||||||
// Normaliza valores
|
as.animations.push_back(buffer);
|
||||||
if (framesPerRow == 0 && frameWidth > 0) {
|
} else {
|
||||||
framesPerRow = texture->getWidth() / frameWidth;
|
int pos = line.find("=");
|
||||||
}
|
if (pos != (int)line.npos) {
|
||||||
|
if (line.substr(0, pos) == "framesPerRow") {
|
||||||
|
framesPerRow = std::stoi(line.substr(pos + 1, line.length()));
|
||||||
|
} else if (line.substr(0, pos) == "frameWidth") {
|
||||||
|
frameWidth = std::stoi(line.substr(pos + 1, line.length()));
|
||||||
|
} else if (line.substr(0, pos) == "frameHeight") {
|
||||||
|
frameHeight = std::stoi(line.substr(pos + 1, line.length()));
|
||||||
|
} else {
|
||||||
|
std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
if (maxTiles == 0 && frameWidth > 0 && frameHeight > 0) {
|
if (framesPerRow == 0 && frameWidth > 0) {
|
||||||
const int w = texture->getWidth() / frameWidth;
|
framesPerRow = texture->getWidth() / frameWidth;
|
||||||
const int h = texture->getHeight() / frameHeight;
|
}
|
||||||
maxTiles = w * h;
|
if (maxTiles == 0 && frameWidth > 0 && frameHeight > 0) {
|
||||||
}
|
const int w = texture->getWidth() / frameWidth;
|
||||||
|
const int h = texture->getHeight() / frameHeight;
|
||||||
|
maxTiles = w * h;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cierra el fichero
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
// El fichero no se puede abrir
|
|
||||||
else {
|
|
||||||
if (verbose) {
|
|
||||||
std::cout << "Warning: Unable to open " << filename.c_str() << " file" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return as;
|
return as;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Carga la animación desde un fichero
|
||||||
|
animatedSprite_t loadAnimationFromFile(Texture *texture, const std::string &filePath, bool verbose) {
|
||||||
|
const std::string filename = filePath.substr(filePath.find_last_of("\\/") + 1);
|
||||||
|
std::ifstream file(filePath);
|
||||||
|
if (!file.good()) {
|
||||||
|
if (verbose) {
|
||||||
|
std::cout << "Warning: Unable to open " << filename.c_str() << " file" << std::endl;
|
||||||
|
}
|
||||||
|
animatedSprite_t as;
|
||||||
|
as.texture = texture;
|
||||||
|
return as;
|
||||||
|
}
|
||||||
|
return parseAnimationStream(file, texture, filename, verbose);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carga la animación desde bytes en memoria
|
||||||
|
animatedSprite_t loadAnimationFromMemory(Texture *texture, const std::vector<uint8_t> &bytes, const std::string &nameForLogs, bool verbose) {
|
||||||
|
if (bytes.empty()) {
|
||||||
|
animatedSprite_t as;
|
||||||
|
as.texture = texture;
|
||||||
|
return as;
|
||||||
|
}
|
||||||
|
std::string content(reinterpret_cast<const char *>(bytes.data()), bytes.size());
|
||||||
|
std::stringstream ss(content);
|
||||||
|
return parseAnimationStream(ss, texture, nameForLogs, verbose);
|
||||||
|
}
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
AnimatedSprite::AnimatedSprite(Texture *texture, SDL_Renderer *renderer, std::string file, std::vector<std::string> *buffer) {
|
AnimatedSprite::AnimatedSprite(Texture *texture, SDL_Renderer *renderer, const std::string &file, std::vector<std::string> *buffer)
|
||||||
|
: currentAnimation(0) {
|
||||||
// Copia los punteros
|
// Copia los punteros
|
||||||
setTexture(texture);
|
setTexture(texture);
|
||||||
setRenderer(renderer);
|
setRenderer(renderer);
|
||||||
|
|
||||||
// Carga las animaciones
|
// Carga las animaciones
|
||||||
if (file != "") {
|
if (!file.empty()) {
|
||||||
animatedSprite_t as = loadAnimationFromFile(texture, file);
|
animatedSprite_t as = loadAnimationFromFile(texture, file);
|
||||||
|
|
||||||
// Copia los datos de las animaciones
|
// Copia los datos de las animaciones
|
||||||
for (auto animation : as.animations) {
|
animation.insert(animation.end(), as.animations.begin(), as.animations.end());
|
||||||
this->animation.push_back(animation);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (buffer) {
|
else if (buffer) {
|
||||||
loadFromVector(buffer);
|
loadFromVector(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inicializa variables
|
|
||||||
currentAnimation = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
AnimatedSprite::AnimatedSprite(SDL_Renderer *renderer, animatedSprite_t *animation) {
|
AnimatedSprite::AnimatedSprite(SDL_Renderer *renderer, animatedSprite_t *animation)
|
||||||
|
: currentAnimation(0) {
|
||||||
// Copia los punteros
|
// Copia los punteros
|
||||||
setTexture(animation->texture);
|
setTexture(animation->texture);
|
||||||
setRenderer(renderer);
|
setRenderer(renderer);
|
||||||
|
|
||||||
// Inicializa variables
|
|
||||||
currentAnimation = 0;
|
|
||||||
|
|
||||||
// Copia los datos de las animaciones
|
// Copia los datos de las animaciones
|
||||||
for (auto a : animation->animations) {
|
this->animation.insert(this->animation.end(), animation->animations.begin(), animation->animations.end());
|
||||||
this->animation.push_back(a);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
@@ -176,10 +150,10 @@ AnimatedSprite::~AnimatedSprite() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Obtiene el indice de la animación a partir del nombre
|
// Obtiene el indice de la animación a partir del nombre
|
||||||
int AnimatedSprite::getIndex(std::string name) {
|
int AnimatedSprite::getIndex(const std::string &name) {
|
||||||
int index = -1;
|
int index = -1;
|
||||||
|
|
||||||
for (auto a : animation) {
|
for (const auto &a : animation) {
|
||||||
index++;
|
index++;
|
||||||
if (a.name == name) {
|
if (a.name == name) {
|
||||||
return index;
|
return index;
|
||||||
@@ -242,12 +216,12 @@ void AnimatedSprite::setCurrentFrame(int num) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Establece el valor del contador
|
// Establece el valor del contador
|
||||||
void AnimatedSprite::setAnimationCounter(std::string name, int num) {
|
void AnimatedSprite::setAnimationCounter(const std::string &name, int num) {
|
||||||
animation[getIndex(name)].counter = num;
|
animation[getIndex(name)].counter = num;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Establece la velocidad de una animación
|
// Establece la velocidad de una animación
|
||||||
void AnimatedSprite::setAnimationSpeed(std::string name, int speed) {
|
void AnimatedSprite::setAnimationSpeed(const std::string &name, int speed) {
|
||||||
animation[getIndex(name)].counter = speed;
|
animation[getIndex(name)].counter = speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,7 +231,7 @@ void AnimatedSprite::setAnimationSpeed(int index, int speed) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Establece si la animación se reproduce en bucle
|
// Establece si la animación se reproduce en bucle
|
||||||
void AnimatedSprite::setAnimationLoop(std::string name, int loop) {
|
void AnimatedSprite::setAnimationLoop(const std::string &name, int loop) {
|
||||||
animation[getIndex(name)].loop = loop;
|
animation[getIndex(name)].loop = loop;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,7 +241,7 @@ void AnimatedSprite::setAnimationLoop(int index, int loop) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Establece el valor de la variable
|
// Establece el valor de la variable
|
||||||
void AnimatedSprite::setAnimationCompleted(std::string name, bool value) {
|
void AnimatedSprite::setAnimationCompleted(const std::string &name, bool value) {
|
||||||
animation[getIndex(name)].completed = value;
|
animation[getIndex(name)].completed = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +256,7 @@ bool AnimatedSprite::animationIsCompleted() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Devuelve el rectangulo de una animación y frame concreto
|
// Devuelve el rectangulo de una animación y frame concreto
|
||||||
SDL_Rect AnimatedSprite::getAnimationClip(std::string name, Uint8 index) {
|
SDL_Rect AnimatedSprite::getAnimationClip(const std::string &name, Uint8 index) {
|
||||||
return animation[getIndex(name)].frames[index];
|
return animation[getIndex(name)].frames[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,6 +286,8 @@ bool AnimatedSprite::loadFromVector(std::vector<std::string> *source) {
|
|||||||
// Si la linea contiene el texto [animation] se realiza el proceso de carga de una animación
|
// Si la linea contiene el texto [animation] se realiza el proceso de carga de una animación
|
||||||
if (line == "[animation]") {
|
if (line == "[animation]") {
|
||||||
animation_t buffer;
|
animation_t buffer;
|
||||||
|
buffer.speed = 0;
|
||||||
|
buffer.loop = -1;
|
||||||
buffer.counter = 0;
|
buffer.counter = 0;
|
||||||
buffer.currentFrame = 0;
|
buffer.currentFrame = 0;
|
||||||
buffer.completed = false;
|
buffer.completed = false;
|
||||||
@@ -411,7 +387,7 @@ bool AnimatedSprite::loadFromVector(std::vector<std::string> *source) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Establece la animacion actual
|
// Establece la animacion actual
|
||||||
void AnimatedSprite::setCurrentAnimation(std::string name) {
|
void AnimatedSprite::setCurrentAnimation(const std::string &name) {
|
||||||
const int newAnimation = getIndex(name);
|
const int newAnimation = getIndex(name);
|
||||||
if (currentAnimation != newAnimation) {
|
if (currentAnimation != newAnimation) {
|
||||||
currentAnimation = newAnimation;
|
currentAnimation = newAnimation;
|
||||||
@@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <string> // for string, basic_string
|
#include <string> // for string, basic_string
|
||||||
#include <vector> // for vector
|
#include <vector> // for vector
|
||||||
|
|
||||||
#include "movingsprite.h" // for MovingSprite
|
#include "core/rendering/movingsprite.h" // for MovingSprite
|
||||||
class Texture;
|
class Texture;
|
||||||
|
|
||||||
struct animation_t {
|
struct animation_t {
|
||||||
@@ -24,7 +25,10 @@ struct animatedSprite_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Carga la animación desde un fichero
|
// Carga la animación desde un fichero
|
||||||
animatedSprite_t loadAnimationFromFile(Texture *texture, std::string filePath, bool verbose = false);
|
animatedSprite_t loadAnimationFromFile(Texture *texture, const std::string &filePath, bool verbose = false);
|
||||||
|
|
||||||
|
// Carga la animación desde bytes en memoria
|
||||||
|
animatedSprite_t loadAnimationFromMemory(Texture *texture, const std::vector<uint8_t> &bytes, const std::string &nameForLogs = "", bool verbose = false);
|
||||||
|
|
||||||
class AnimatedSprite : public MovingSprite {
|
class AnimatedSprite : public MovingSprite {
|
||||||
private:
|
private:
|
||||||
@@ -34,11 +38,11 @@ class AnimatedSprite : public MovingSprite {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
// Constructor
|
// Constructor
|
||||||
AnimatedSprite(Texture *texture = nullptr, SDL_Renderer *renderer = nullptr, std::string file = "", std::vector<std::string> *buffer = nullptr);
|
explicit AnimatedSprite(Texture *texture = nullptr, SDL_Renderer *renderer = nullptr, const std::string &file = "", std::vector<std::string> *buffer = nullptr);
|
||||||
AnimatedSprite(SDL_Renderer *renderer, animatedSprite_t *animation);
|
AnimatedSprite(SDL_Renderer *renderer, animatedSprite_t *animation);
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
~AnimatedSprite();
|
~AnimatedSprite() override;
|
||||||
|
|
||||||
// Calcula el frame correspondiente a la animación actual
|
// Calcula el frame correspondiente a la animación actual
|
||||||
void animate();
|
void animate();
|
||||||
@@ -50,39 +54,39 @@ class AnimatedSprite : public MovingSprite {
|
|||||||
void setCurrentFrame(int num);
|
void setCurrentFrame(int num);
|
||||||
|
|
||||||
// Establece el valor del contador
|
// Establece el valor del contador
|
||||||
void setAnimationCounter(std::string name, int num);
|
void setAnimationCounter(const std::string &name, int num);
|
||||||
|
|
||||||
// Establece la velocidad de una animación
|
// Establece la velocidad de una animación
|
||||||
void setAnimationSpeed(std::string name, int speed);
|
void setAnimationSpeed(const std::string &name, int speed);
|
||||||
void setAnimationSpeed(int index, int speed);
|
void setAnimationSpeed(int index, int speed);
|
||||||
|
|
||||||
// Establece el frame al que vuelve la animación al finalizar
|
// Establece el frame al que vuelve la animación al finalizar
|
||||||
void setAnimationLoop(std::string name, int loop);
|
void setAnimationLoop(const std::string &name, int loop);
|
||||||
void setAnimationLoop(int index, int loop);
|
void setAnimationLoop(int index, int loop);
|
||||||
|
|
||||||
// Establece el valor de la variable
|
// Establece el valor de la variable
|
||||||
void setAnimationCompleted(std::string name, bool value);
|
void setAnimationCompleted(const std::string &name, bool value);
|
||||||
void setAnimationCompleted(int index, bool value);
|
void setAnimationCompleted(int index, bool value);
|
||||||
|
|
||||||
// Comprueba si ha terminado la animación
|
// Comprueba si ha terminado la animación
|
||||||
bool animationIsCompleted();
|
bool animationIsCompleted();
|
||||||
|
|
||||||
// Devuelve el rectangulo de una animación y frame concreto
|
// Devuelve el rectangulo de una animación y frame concreto
|
||||||
SDL_Rect getAnimationClip(std::string name = "default", Uint8 index = 0);
|
SDL_Rect getAnimationClip(const std::string &name = "default", Uint8 index = 0);
|
||||||
SDL_Rect getAnimationClip(int indexA = 0, Uint8 indexF = 0);
|
SDL_Rect getAnimationClip(int indexA = 0, Uint8 indexF = 0);
|
||||||
|
|
||||||
// Obtiene el indice de la animación a partir del nombre
|
// Obtiene el indice de la animación a partir del nombre
|
||||||
int getIndex(std::string name);
|
int getIndex(const std::string &name);
|
||||||
|
|
||||||
// Carga la animación desde un vector
|
// Carga la animación desde un vector
|
||||||
bool loadFromVector(std::vector<std::string> *source);
|
bool loadFromVector(std::vector<std::string> *source);
|
||||||
|
|
||||||
// Establece la animacion actual
|
// Establece la animacion actual
|
||||||
void setCurrentAnimation(std::string name = "default");
|
void setCurrentAnimation(const std::string &name = "default");
|
||||||
void setCurrentAnimation(int index = 0);
|
void setCurrentAnimation(int index = 0);
|
||||||
|
|
||||||
// Actualiza las variables del objeto
|
// Actualiza las variables del objeto
|
||||||
void update();
|
void update() override;
|
||||||
|
|
||||||
// OLD - Establece el rectangulo para un frame de una animación
|
// OLD - Establece el rectangulo para un frame de una animación
|
||||||
void setAnimationFrames(Uint8 index_animation, Uint8 index_frame, int x, int y, int w, int h);
|
void setAnimationFrames(Uint8 index_animation, Uint8 index_frame, int x, int y, int w, int h);
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
#include "fade.h"
|
#include "core/rendering/fade.h"
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <stdlib.h> // for rand
|
#include <stdlib.h> // for rand
|
||||||
|
|
||||||
#include <iostream> // for char_traits, basic_ostream, operator<<
|
#include <iostream> // for char_traits, basic_ostream, operator<<
|
||||||
|
|
||||||
#include "const.h" // for GAMECANVAS_HEIGHT, GAMECANVAS_WIDTH
|
#include "game/defaults.hpp" // for GAMECANVAS_HEIGHT, GAMECANVAS_WIDTH
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Fade::Fade(SDL_Renderer *renderer) {
|
Fade::Fade(SDL_Renderer *renderer)
|
||||||
mRenderer = renderer;
|
: mRenderer(renderer) {
|
||||||
|
|
||||||
mBackbuffer = SDL_CreateTexture(mRenderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT);
|
mBackbuffer = SDL_CreateTexture(mRenderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT);
|
||||||
if (mBackbuffer != nullptr) {
|
if (mBackbuffer != nullptr) {
|
||||||
SDL_SetTextureScaleMode(mBackbuffer, SDL_SCALEMODE_NEAREST);
|
SDL_SetTextureScaleMode(mBackbuffer, SDL_SCALEMODE_NEAREST);
|
||||||
@@ -35,6 +34,12 @@ void Fade::init(Uint8 r, Uint8 g, Uint8 b) {
|
|||||||
mR = r;
|
mR = r;
|
||||||
mG = g;
|
mG = g;
|
||||||
mB = b;
|
mB = b;
|
||||||
|
mROriginal = r;
|
||||||
|
mGOriginal = g;
|
||||||
|
mBOriginal = b;
|
||||||
|
mLastSquareTicks = 0;
|
||||||
|
mSquaresDrawn = 0;
|
||||||
|
mFullscreenDone = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pinta una transición en pantalla
|
// Pinta una transición en pantalla
|
||||||
@@ -42,30 +47,34 @@ void Fade::render() {
|
|||||||
if (mEnabled && !mFinished) {
|
if (mEnabled && !mFinished) {
|
||||||
switch (mFadeType) {
|
switch (mFadeType) {
|
||||||
case FADE_FULLSCREEN: {
|
case FADE_FULLSCREEN: {
|
||||||
SDL_FRect fRect1 = {0, 0, (float)GAMECANVAS_WIDTH, (float)GAMECANVAS_HEIGHT};
|
if (!mFullscreenDone) {
|
||||||
|
SDL_FRect fRect1 = {0, 0, (float)GAMECANVAS_WIDTH, (float)GAMECANVAS_HEIGHT};
|
||||||
|
|
||||||
for (int i = 0; i < 256; i += 4) {
|
int alpha = mCounter * 4;
|
||||||
// Dibujamos sobre el renderizador
|
if (alpha >= 255) {
|
||||||
SDL_SetRenderTarget(mRenderer, nullptr);
|
mFullscreenDone = true;
|
||||||
|
|
||||||
// Copia el backbuffer con la imagen que había al renderizador
|
// Deja todos los buffers del mismo color
|
||||||
SDL_RenderTexture(mRenderer, mBackbuffer, nullptr, nullptr);
|
SDL_SetRenderTarget(mRenderer, mBackbuffer);
|
||||||
|
SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, 255);
|
||||||
|
SDL_RenderClear(mRenderer);
|
||||||
|
|
||||||
SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, i);
|
SDL_SetRenderTarget(mRenderer, nullptr);
|
||||||
SDL_RenderFillRect(mRenderer, &fRect1);
|
SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, 255);
|
||||||
|
SDL_RenderClear(mRenderer);
|
||||||
|
|
||||||
// Vuelca el renderizador en pantalla
|
mFinished = true;
|
||||||
SDL_RenderPresent(mRenderer);
|
} else {
|
||||||
|
// Dibujamos sobre el renderizador
|
||||||
|
SDL_SetRenderTarget(mRenderer, nullptr);
|
||||||
|
|
||||||
|
// Copia el backbuffer con la imagen que había al renderizador
|
||||||
|
SDL_RenderTexture(mRenderer, mBackbuffer, nullptr, nullptr);
|
||||||
|
|
||||||
|
SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, alpha);
|
||||||
|
SDL_RenderFillRect(mRenderer, &fRect1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deja todos los buffers del mismo color
|
|
||||||
SDL_SetRenderTarget(mRenderer, mBackbuffer);
|
|
||||||
SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, 255);
|
|
||||||
SDL_RenderClear(mRenderer);
|
|
||||||
|
|
||||||
SDL_SetRenderTarget(mRenderer, nullptr);
|
|
||||||
SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, 255);
|
|
||||||
SDL_RenderClear(mRenderer);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,14 +98,17 @@ void Fade::render() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case FADE_RANDOM_SQUARE: {
|
case FADE_RANDOM_SQUARE: {
|
||||||
SDL_FRect fRs = {0, 0, 32, 32};
|
Uint32 now = SDL_GetTicks();
|
||||||
|
if (mSquaresDrawn < 50 && now - mLastSquareTicks >= 100) {
|
||||||
|
mLastSquareTicks = now;
|
||||||
|
|
||||||
|
SDL_FRect fRs = {0, 0, 32, 32};
|
||||||
|
|
||||||
for (Uint16 i = 0; i < 50; i++) {
|
|
||||||
// Crea un color al azar
|
// Crea un color al azar
|
||||||
mR = 255 * (rand() % 2);
|
Uint8 r = 255 * (rand() % 2);
|
||||||
mG = 255 * (rand() % 2);
|
Uint8 g = 255 * (rand() % 2);
|
||||||
mB = 255 * (rand() % 2);
|
Uint8 b = 255 * (rand() % 2);
|
||||||
SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, 64);
|
SDL_SetRenderDrawColor(mRenderer, r, g, b, 64);
|
||||||
|
|
||||||
// Dibujamos sobre el backbuffer
|
// Dibujamos sobre el backbuffer
|
||||||
SDL_SetRenderTarget(mRenderer, mBackbuffer);
|
SDL_SetRenderTarget(mRenderer, mBackbuffer);
|
||||||
@@ -108,12 +120,14 @@ void Fade::render() {
|
|||||||
// Volvemos a usar el renderizador de forma normal
|
// Volvemos a usar el renderizador de forma normal
|
||||||
SDL_SetRenderTarget(mRenderer, nullptr);
|
SDL_SetRenderTarget(mRenderer, nullptr);
|
||||||
|
|
||||||
// Copiamos el backbuffer al renderizador
|
mSquaresDrawn++;
|
||||||
SDL_RenderTexture(mRenderer, mBackbuffer, nullptr, nullptr);
|
}
|
||||||
|
|
||||||
// Volcamos el renderizador en pantalla
|
// Copiamos el backbuffer al renderizador
|
||||||
SDL_RenderPresent(mRenderer);
|
SDL_RenderTexture(mRenderer, mBackbuffer, nullptr, nullptr);
|
||||||
SDL_Delay(100);
|
|
||||||
|
if (mSquaresDrawn >= 50) {
|
||||||
|
mFinished = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -140,6 +154,12 @@ void Fade::activateFade() {
|
|||||||
mEnabled = true;
|
mEnabled = true;
|
||||||
mFinished = false;
|
mFinished = false;
|
||||||
mCounter = 0;
|
mCounter = 0;
|
||||||
|
mSquaresDrawn = 0;
|
||||||
|
mLastSquareTicks = 0;
|
||||||
|
mFullscreenDone = false;
|
||||||
|
mR = mROriginal;
|
||||||
|
mG = mGOriginal;
|
||||||
|
mB = mBOriginal;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comprueba si está activo
|
// Comprueba si está activo
|
||||||
54
source/core/rendering/fade.h
Normal file
54
source/core/rendering/fade.h
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
// Tipos de fundido
|
||||||
|
constexpr int FADE_FULLSCREEN = 0;
|
||||||
|
constexpr int FADE_CENTER = 1;
|
||||||
|
constexpr int FADE_RANDOM_SQUARE = 2;
|
||||||
|
|
||||||
|
// Clase Fade
|
||||||
|
class Fade {
|
||||||
|
private:
|
||||||
|
SDL_Renderer *mRenderer = nullptr; // El renderizador de la ventana
|
||||||
|
SDL_Texture *mBackbuffer = nullptr; // Textura para usar como backbuffer
|
||||||
|
Uint8 mFadeType = FADE_FULLSCREEN; // Tipo de fade a realizar
|
||||||
|
Uint16 mCounter = 0; // Contador interno
|
||||||
|
bool mEnabled = false; // Indica si el fade está activo
|
||||||
|
bool mFinished = false; // Indica si ha terminado la transición
|
||||||
|
Uint8 mR = 0, mG = 0, mB = 0; // Colores para el fade
|
||||||
|
Uint8 mROriginal = 0, mGOriginal = 0, mBOriginal = 0; // Colores originales para FADE_RANDOM_SQUARE
|
||||||
|
Uint32 mLastSquareTicks = 0; // Ticks del último cuadrado dibujado (FADE_RANDOM_SQUARE)
|
||||||
|
Uint16 mSquaresDrawn = 0; // Número de cuadrados dibujados (FADE_RANDOM_SQUARE)
|
||||||
|
bool mFullscreenDone = false; // Indica si el fade fullscreen ha terminado la fase de fundido
|
||||||
|
SDL_Rect mRect1{}; // Rectangulo usado para crear los efectos de transición
|
||||||
|
SDL_Rect mRect2{}; // Rectangulo usado para crear los efectos de transición
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Constructor
|
||||||
|
explicit Fade(SDL_Renderer *renderer);
|
||||||
|
|
||||||
|
// Destructor
|
||||||
|
~Fade();
|
||||||
|
|
||||||
|
// Inicializa las variables
|
||||||
|
void init(Uint8 r, Uint8 g, Uint8 b);
|
||||||
|
|
||||||
|
// Pinta una transición en pantalla
|
||||||
|
void render();
|
||||||
|
|
||||||
|
// Actualiza las variables internas
|
||||||
|
void update();
|
||||||
|
|
||||||
|
// Activa el fade
|
||||||
|
void activateFade();
|
||||||
|
|
||||||
|
// Comprueba si ha terminado la transicion
|
||||||
|
bool hasEnded();
|
||||||
|
|
||||||
|
// Comprueba si está activo
|
||||||
|
bool isEnabled();
|
||||||
|
|
||||||
|
// Establece el tipo de fade
|
||||||
|
void setFadeType(Uint8 fadeType);
|
||||||
|
};
|
||||||
@@ -1,55 +1,28 @@
|
|||||||
#include "movingsprite.h"
|
#include "core/rendering/movingsprite.h"
|
||||||
|
|
||||||
#include "texture.h" // for Texture
|
#include "core/rendering/texture.h" // for Texture
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
MovingSprite::MovingSprite(float x, float y, int w, int h, float velx, float vely, float accelx, float accely, Texture *texture, SDL_Renderer *renderer) {
|
MovingSprite::MovingSprite(float x, float y, int w, int h, float velx, float vely, float accelx, float accely, Texture *texture, SDL_Renderer *renderer)
|
||||||
// Copia los punteros
|
: Sprite(0, 0, w, h, texture, renderer),
|
||||||
this->texture = texture;
|
x(x),
|
||||||
this->renderer = renderer;
|
y(y),
|
||||||
|
xPrev(x),
|
||||||
// Establece el alto y el ancho del sprite
|
yPrev(y),
|
||||||
this->w = w;
|
vx(velx),
|
||||||
this->h = h;
|
vy(vely),
|
||||||
|
ax(accelx),
|
||||||
// Establece la posición X,Y del sprite
|
ay(accely),
|
||||||
this->x = x;
|
zoomW(1),
|
||||||
this->y = y;
|
zoomH(1),
|
||||||
xPrev = x;
|
angle(0.0),
|
||||||
yPrev = y;
|
rotateEnabled(false),
|
||||||
|
rotateSpeed(0),
|
||||||
// Establece la velocidad X,Y del sprite
|
rotateAmount(0.0),
|
||||||
vx = velx;
|
counter(0),
|
||||||
vy = vely;
|
center(nullptr),
|
||||||
|
currentFlip(SDL_FLIP_NONE) {
|
||||||
// Establece la aceleración X,Y del sprite
|
}
|
||||||
ax = accelx;
|
|
||||||
ay = accely;
|
|
||||||
|
|
||||||
// Establece el zoom W,H del sprite
|
|
||||||
zoomW = 1;
|
|
||||||
zoomH = 1;
|
|
||||||
|
|
||||||
// Establece el angulo con el que se dibujará
|
|
||||||
angle = (double)0;
|
|
||||||
|
|
||||||
// Establece los valores de rotacion
|
|
||||||
rotateEnabled = false;
|
|
||||||
rotateSpeed = 0;
|
|
||||||
rotateAmount = (double)0;
|
|
||||||
|
|
||||||
// Contador interno
|
|
||||||
counter = 0;
|
|
||||||
|
|
||||||
// Establece el rectangulo de donde coger la imagen
|
|
||||||
spriteClip = {0, 0, w, h};
|
|
||||||
|
|
||||||
// Establece el centro de rotación
|
|
||||||
center = nullptr;
|
|
||||||
|
|
||||||
// Establece el tipo de volteado
|
|
||||||
currentFlip = SDL_FLIP_NONE;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Reinicia todas las variables
|
// Reinicia todas las variables
|
||||||
void MovingSprite::clear() {
|
void MovingSprite::clear() {
|
||||||
@@ -97,11 +70,13 @@ void MovingSprite::render() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Obtiene el valor de la variable
|
// Obtiene el valor de la variable
|
||||||
|
// cppcheck-suppress duplInheritedMember
|
||||||
float MovingSprite::getPosX() {
|
float MovingSprite::getPosX() {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtiene el valor de la variable
|
// Obtiene el valor de la variable
|
||||||
|
// cppcheck-suppress duplInheritedMember
|
||||||
float MovingSprite::getPosY() {
|
float MovingSprite::getPosY() {
|
||||||
return y;
|
return y;
|
||||||
}
|
}
|
||||||
@@ -2,14 +2,16 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include "sprite.h" // for Sprite
|
#include "core/rendering/sprite.h" // for Sprite
|
||||||
class Texture;
|
class Texture;
|
||||||
|
|
||||||
// Clase MovingSprite. Añade posicion y velocidad en punto flotante
|
// Clase MovingSprite. Añade posicion y velocidad en punto flotante
|
||||||
class MovingSprite : public Sprite {
|
class MovingSprite : public Sprite {
|
||||||
protected:
|
protected:
|
||||||
float x; // Posición en el eje X
|
// cppcheck-suppress duplInheritedMember
|
||||||
float y; // Posición en el eje Y
|
float x; // Posición en el eje X (sub-pixel; Sprite::x es int)
|
||||||
|
// cppcheck-suppress duplInheritedMember
|
||||||
|
float y; // Posición en el eje Y (sub-pixel; Sprite::y es int)
|
||||||
|
|
||||||
float xPrev; // Posición anterior en el eje X
|
float xPrev; // Posición anterior en el eje X
|
||||||
float yPrev; // Posición anterior en el eje Y
|
float yPrev; // Posición anterior en el eje Y
|
||||||
@@ -33,7 +35,7 @@ class MovingSprite : public Sprite {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
// Constructor
|
// Constructor
|
||||||
MovingSprite(float x = 0, float y = 0, int w = 0, int h = 0, float velx = 0, float vely = 0, float accelx = 0, float accely = 0, Texture *texture = nullptr, SDL_Renderer *renderer = nullptr);
|
explicit MovingSprite(float x = 0, float y = 0, int w = 0, int h = 0, float velx = 0, float vely = 0, float accelx = 0, float accely = 0, Texture *texture = nullptr, SDL_Renderer *renderer = nullptr);
|
||||||
|
|
||||||
// Mueve el sprite
|
// Mueve el sprite
|
||||||
void move();
|
void move();
|
||||||
@@ -42,18 +44,20 @@ class MovingSprite : public Sprite {
|
|||||||
void rotate();
|
void rotate();
|
||||||
|
|
||||||
// Actualiza las variables internas del objeto
|
// Actualiza las variables internas del objeto
|
||||||
void update();
|
virtual void update();
|
||||||
|
|
||||||
// Reinicia todas las variables
|
// Reinicia todas las variables
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
// Muestra el sprite por pantalla
|
// Muestra el sprite por pantalla
|
||||||
void render();
|
void render() override;
|
||||||
|
|
||||||
// Obten el valor de la variable
|
// Obten el valor de la variable
|
||||||
|
// cppcheck-suppress duplInheritedMember
|
||||||
float getPosX();
|
float getPosX();
|
||||||
|
|
||||||
// Obten el valor de la variable
|
// Obten el valor de la variable
|
||||||
|
// cppcheck-suppress duplInheritedMember
|
||||||
float getPosY();
|
float getPosY();
|
||||||
|
|
||||||
// Obten el valor de la variable
|
// Obten el valor de la variable
|
||||||
@@ -84,7 +88,7 @@ class MovingSprite : public Sprite {
|
|||||||
Uint16 getRotateSpeed();
|
Uint16 getRotateSpeed();
|
||||||
|
|
||||||
// Establece la posición y el tamaño del objeto
|
// Establece la posición y el tamaño del objeto
|
||||||
void setRect(SDL_Rect rect);
|
void setRect(SDL_Rect rect) override;
|
||||||
|
|
||||||
// Establece el valor de la variable
|
// Establece el valor de la variable
|
||||||
void setPosX(float value);
|
void setPosX(float value);
|
||||||
@@ -144,7 +148,7 @@ class MovingSprite : public Sprite {
|
|||||||
SDL_FlipMode getFlip();
|
SDL_FlipMode getFlip();
|
||||||
|
|
||||||
// Devuelve el rectangulo donde está el sprite
|
// Devuelve el rectangulo donde está el sprite
|
||||||
SDL_Rect getRect();
|
SDL_Rect getRect() override;
|
||||||
|
|
||||||
// Deshace el último movimiento
|
// Deshace el último movimiento
|
||||||
void undoMove();
|
void undoMove();
|
||||||
722
source/core/rendering/screen.cpp
Normal file
722
source/core/rendering/screen.cpp
Normal file
@@ -0,0 +1,722 @@
|
|||||||
|
#include "core/rendering/screen.h"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <algorithm> // for max, min
|
||||||
|
#include <cstring> // for memcpy
|
||||||
|
#include <iostream> // for basic_ostream, operator<<, cout, endl
|
||||||
|
#include <string> // for basic_string, char_traits, string
|
||||||
|
|
||||||
|
#include "core/input/mouse.hpp" // for Mouse::cursorVisible, Mouse::lastMouseMoveTime
|
||||||
|
#include "core/rendering/text.h" // for Text, TXT_CENTER, TXT_COLOR, TXT_STROKE
|
||||||
|
#include "core/resources/resource.h"
|
||||||
|
#include "game/defaults.hpp" // for GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT
|
||||||
|
#include "game/options.hpp" // for Options::video, Options::settings
|
||||||
|
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp" // for Rendering::SDL3GPUShader
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
#include <emscripten.h>
|
||||||
|
#include <emscripten/html5.h>
|
||||||
|
|
||||||
|
// --- Fix per a fullscreen/resize en Emscripten ---
|
||||||
|
//
|
||||||
|
// SDL3 + Emscripten no emet de forma fiable SDL_EVENT_WINDOW_LEAVE_FULLSCREEN
|
||||||
|
// (libsdl-org/SDL#13300) ni SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED /
|
||||||
|
// SDL_EVENT_DISPLAY_ORIENTATION (libsdl-org/SDL#11389). Quan l'usuari ix de
|
||||||
|
// fullscreen amb Esc o rota el mòbil, el canvas HTML torna al tamany correcte
|
||||||
|
// però l'estat intern de SDL creu que segueix en fullscreen amb la resolució
|
||||||
|
// anterior i el viewport queda desencuadrat.
|
||||||
|
//
|
||||||
|
// Solució: registrar callbacks natius d'Emscripten, diferir la feina un tick
|
||||||
|
// del event loop (el canvas encara no està estable en el moment del callback)
|
||||||
|
// i cridar setVideoMode() amb el flag de fullscreen actualitzat. La crida
|
||||||
|
// interna a SDL_SetWindowFullscreen(false) és la peça que realment fa eixir
|
||||||
|
// SDL del seu estat intern de fullscreen — sense això res més funciona.
|
||||||
|
namespace {
|
||||||
|
Screen *g_screen_instance = nullptr;
|
||||||
|
|
||||||
|
void deferredCanvasResize(void * /*userData*/) {
|
||||||
|
if (g_screen_instance) {
|
||||||
|
g_screen_instance->handleCanvasResized();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EM_BOOL onEmFullscreenChange(int /*eventType*/, const EmscriptenFullscreenChangeEvent *event, void * /*userData*/) {
|
||||||
|
if (g_screen_instance && event) {
|
||||||
|
g_screen_instance->syncFullscreenFlagFromBrowser(event->isFullscreen != 0);
|
||||||
|
}
|
||||||
|
emscripten_async_call(deferredCanvasResize, nullptr, 0);
|
||||||
|
return EM_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
EM_BOOL onEmOrientationChange(int /*eventType*/, const EmscriptenOrientationChangeEvent * /*event*/, void * /*userData*/) {
|
||||||
|
emscripten_async_call(deferredCanvasResize, nullptr, 0);
|
||||||
|
return EM_FALSE;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
#endif // __EMSCRIPTEN__
|
||||||
|
|
||||||
|
// Instancia única
|
||||||
|
Screen *Screen::instance = nullptr;
|
||||||
|
|
||||||
|
// Singleton API
|
||||||
|
void Screen::init(SDL_Window *window, SDL_Renderer *renderer) {
|
||||||
|
Screen::instance = new Screen(window, renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::destroy() {
|
||||||
|
delete Screen::instance;
|
||||||
|
Screen::instance = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Screen::get() -> Screen * {
|
||||||
|
return Screen::instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
Screen::Screen(SDL_Window *window, SDL_Renderer *renderer)
|
||||||
|
: borderColor{0x00, 0x00, 0x00} {
|
||||||
|
// Inicializa variables
|
||||||
|
this->window = window;
|
||||||
|
this->renderer = renderer;
|
||||||
|
|
||||||
|
gameCanvasWidth = GAMECANVAS_WIDTH;
|
||||||
|
gameCanvasHeight = GAMECANVAS_HEIGHT;
|
||||||
|
|
||||||
|
// Establece el modo de video (fullscreen/ventana + logical presentation)
|
||||||
|
// ANTES de crear la textura — SDL3 GPU necesita la logical presentation
|
||||||
|
// del renderer ya aplicada al swapchain quan es reclama la ventana per a GPU.
|
||||||
|
// Mirror del pattern de jaildoctors_dilemma (que usa exactament 256×192 i
|
||||||
|
// funciona) on `initSDLVideo` configura la presentation abans de crear cap
|
||||||
|
// textura.
|
||||||
|
setVideoMode(Options::video.fullscreen);
|
||||||
|
|
||||||
|
// Força al window manager a completar el resize/posicionat abans de passar
|
||||||
|
// la ventana al dispositiu GPU. Sense açò en Linux/X11 hi ha un race
|
||||||
|
// condition que deixa el swapchain en estat inestable i fa crashear el
|
||||||
|
// driver Vulkan en `SDL_CreateGPUGraphicsPipeline`.
|
||||||
|
SDL_SyncWindow(window);
|
||||||
|
|
||||||
|
// Crea la textura donde se dibujan los graficos del juego.
|
||||||
|
// ARGB8888 per simplificar el readback cap al pipeline SDL3 GPU.
|
||||||
|
gameCanvas = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, gameCanvasWidth, gameCanvasHeight);
|
||||||
|
if (gameCanvas != nullptr) {
|
||||||
|
SDL_SetTextureScaleMode(gameCanvas, Options::video.scale_mode);
|
||||||
|
}
|
||||||
|
if (gameCanvas == nullptr) {
|
||||||
|
if (Options::settings.console) {
|
||||||
|
std::cout << "gameCanvas could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
// Buffer de readback del gameCanvas (lo dimensionamos una vez)
|
||||||
|
pixel_buffer_.resize(static_cast<size_t>(gameCanvasWidth) * static_cast<size_t>(gameCanvasHeight));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Renderiza una vez la textura vacía al renderer abans d'inicialitzar els
|
||||||
|
// shaders: jaildoctors_dilemma ho fa així i evita que el driver Vulkan
|
||||||
|
// crashegi en la creació del pipeline gràfic. `initShaders()` es crida
|
||||||
|
// després des de `Director` amb el swapchain ja estable.
|
||||||
|
SDL_RenderTexture(renderer, gameCanvas, nullptr, nullptr);
|
||||||
|
|
||||||
|
// Estado inicial de las notificaciones. El Text real se enlaza después vía
|
||||||
|
// `initNotifications()` quan `Resource` ja estigui inicialitzat. Dividim
|
||||||
|
// això del constructor perquè `initShaders()` (GPU) ha de cridar-se ABANS
|
||||||
|
// de carregar recursos: si el SDL_Renderer ha fet abans moltes
|
||||||
|
// allocacions (carrega de textures), el driver Vulkan crasheja quan
|
||||||
|
// després es reclama la ventana per al dispositiu GPU.
|
||||||
|
notificationText = nullptr;
|
||||||
|
notificationMessage = "";
|
||||||
|
notificationTextColor = {0xFF, 0xFF, 0xFF};
|
||||||
|
notificationOutlineColor = {0x00, 0x00, 0x00};
|
||||||
|
notificationEndTime = 0;
|
||||||
|
notificationY = 2;
|
||||||
|
|
||||||
|
// Registra callbacks natius d'Emscripten per a fullscreen/orientation
|
||||||
|
registerEmscriptenEventCallbacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enllaça el Text de les notificacions amb el recurs compartit de `Resource`.
|
||||||
|
// S'ha de cridar després de `Resource::init(...)`.
|
||||||
|
void Screen::initNotifications() {
|
||||||
|
notificationText = Resource::get()->getText("8bithud");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destructor
|
||||||
|
Screen::~Screen() {
|
||||||
|
// notificationText es propiedad de Resource — no liberar.
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
shutdownShaders();
|
||||||
|
#endif
|
||||||
|
SDL_DestroyTexture(gameCanvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limpia la pantalla
|
||||||
|
void Screen::clean(color_t color) {
|
||||||
|
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, 0xFF);
|
||||||
|
SDL_RenderClear(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepara para empezar a dibujar en la textura de juego
|
||||||
|
void Screen::start() {
|
||||||
|
SDL_SetRenderTarget(renderer, gameCanvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vuelca el contenido del renderizador en pantalla
|
||||||
|
void Screen::blit() {
|
||||||
|
// Dibuja la notificación activa sobre el gameCanvas antes de presentar
|
||||||
|
SDL_SetRenderTarget(renderer, gameCanvas);
|
||||||
|
renderNotification();
|
||||||
|
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
// Si el backend GPU està viu i accelerat, passem sempre per ell (tant amb
|
||||||
|
// shaders com sense). Seguim el mateix pattern que aee_plus: quan shader
|
||||||
|
// està desactivat, forcem POSTFX + params a zero només per a aquest frame
|
||||||
|
// i restaurem el shader actiu, així CRTPI no aplica les seues scanlines
|
||||||
|
// quan l'usuari ho ha desactivat.
|
||||||
|
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||||
|
SDL_Surface *surface = SDL_RenderReadPixels(renderer, nullptr);
|
||||||
|
if (surface != nullptr) {
|
||||||
|
if (surface->format == SDL_PIXELFORMAT_ARGB8888) {
|
||||||
|
std::memcpy(pixel_buffer_.data(), surface->pixels, pixel_buffer_.size() * sizeof(Uint32));
|
||||||
|
} else {
|
||||||
|
SDL_Surface *converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888);
|
||||||
|
if (converted != nullptr) {
|
||||||
|
std::memcpy(pixel_buffer_.data(), converted->pixels, pixel_buffer_.size() * sizeof(Uint32));
|
||||||
|
SDL_DestroySurface(converted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SDL_DestroySurface(surface);
|
||||||
|
}
|
||||||
|
SDL_SetRenderTarget(renderer, nullptr);
|
||||||
|
|
||||||
|
if (Options::video.shader.enabled) {
|
||||||
|
// Ruta normal: shader amb els seus params.
|
||||||
|
shader_backend_->uploadPixels(pixel_buffer_.data(), gameCanvasWidth, gameCanvasHeight);
|
||||||
|
shader_backend_->render();
|
||||||
|
} else {
|
||||||
|
// Shader off: POSTFX amb params zero (passa-per-aquí). CRTPI no
|
||||||
|
// val perque sempre aplica els seus efectes interns; salvem i
|
||||||
|
// restaurem el shader actiu.
|
||||||
|
const auto PREV_SHADER = shader_backend_->getActiveShader();
|
||||||
|
if (PREV_SHADER != Rendering::ShaderType::POSTFX) {
|
||||||
|
shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
|
||||||
|
}
|
||||||
|
shader_backend_->setPostFXParams(Rendering::PostFXParams{});
|
||||||
|
shader_backend_->uploadPixels(pixel_buffer_.data(), gameCanvasWidth, gameCanvasHeight);
|
||||||
|
shader_backend_->render();
|
||||||
|
if (PREV_SHADER != Rendering::ShaderType::POSTFX) {
|
||||||
|
shader_backend_->setActiveShader(PREV_SHADER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Vuelve a dejar el renderizador en modo normal
|
||||||
|
SDL_SetRenderTarget(renderer, nullptr);
|
||||||
|
|
||||||
|
// Borra el contenido previo
|
||||||
|
SDL_SetRenderDrawColor(renderer, borderColor.r, borderColor.g, borderColor.b, 0xFF);
|
||||||
|
SDL_RenderClear(renderer);
|
||||||
|
|
||||||
|
// Copia la textura de juego en el renderizador en la posición adecuada
|
||||||
|
SDL_FRect fdest = {(float)dest.x, (float)dest.y, (float)dest.w, (float)dest.h};
|
||||||
|
SDL_RenderTexture(renderer, gameCanvas, nullptr, &fdest);
|
||||||
|
|
||||||
|
// Muestra por pantalla el renderizador
|
||||||
|
SDL_RenderPresent(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Video y ventana
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Establece el modo de video
|
||||||
|
void Screen::setVideoMode(bool fullscreen) {
|
||||||
|
applyFullscreen(fullscreen);
|
||||||
|
if (fullscreen) {
|
||||||
|
applyFullscreenLayout();
|
||||||
|
} else {
|
||||||
|
applyWindowedLayout();
|
||||||
|
}
|
||||||
|
applyLogicalPresentation(fullscreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cambia entre pantalla completa y ventana
|
||||||
|
void Screen::toggleVideoMode() {
|
||||||
|
setVideoMode(!Options::video.fullscreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce el zoom de la ventana
|
||||||
|
auto Screen::decWindowZoom() -> bool {
|
||||||
|
if (Options::video.fullscreen) { return false; }
|
||||||
|
const int PREV = Options::window.zoom;
|
||||||
|
Options::window.zoom = std::max(Options::window.zoom - 1, WINDOW_ZOOM_MIN);
|
||||||
|
if (Options::window.zoom == PREV) { return false; }
|
||||||
|
setVideoMode(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aumenta el zoom de la ventana
|
||||||
|
auto Screen::incWindowZoom() -> bool {
|
||||||
|
if (Options::video.fullscreen) { return false; }
|
||||||
|
const int PREV = Options::window.zoom;
|
||||||
|
Options::window.zoom = std::min(Options::window.zoom + 1, WINDOW_ZOOM_MAX);
|
||||||
|
if (Options::window.zoom == PREV) { return false; }
|
||||||
|
setVideoMode(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Establece el zoom de la ventana directamente
|
||||||
|
auto Screen::setWindowZoom(int zoom) -> bool {
|
||||||
|
if (Options::video.fullscreen) { return false; }
|
||||||
|
if (zoom < WINDOW_ZOOM_MIN || zoom > WINDOW_ZOOM_MAX) { return false; }
|
||||||
|
if (zoom == Options::window.zoom) { return false; }
|
||||||
|
Options::window.zoom = zoom;
|
||||||
|
setVideoMode(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Establece el escalado entero
|
||||||
|
void Screen::setIntegerScale(bool enabled) {
|
||||||
|
if (Options::video.integer_scale == enabled) { return; }
|
||||||
|
Options::video.integer_scale = enabled;
|
||||||
|
setVideoMode(Options::video.fullscreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alterna el escalado entero
|
||||||
|
void Screen::toggleIntegerScale() {
|
||||||
|
setIntegerScale(!Options::video.integer_scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Establece el V-Sync
|
||||||
|
void Screen::setVSync(bool enabled) {
|
||||||
|
Options::video.vsync = enabled;
|
||||||
|
SDL_SetRenderVSync(renderer, enabled ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
if (shader_backend_) {
|
||||||
|
shader_backend_->setVSync(enabled);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alterna el V-Sync
|
||||||
|
void Screen::toggleVSync() {
|
||||||
|
setVSync(!Options::video.vsync);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cambia el color del borde
|
||||||
|
void Screen::setBorderColor(color_t color) {
|
||||||
|
borderColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Helpers privados de setVideoMode
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// SDL_SetWindowFullscreen + visibilidad del cursor
|
||||||
|
void Screen::applyFullscreen(bool fullscreen) {
|
||||||
|
SDL_SetWindowFullscreen(window, fullscreen);
|
||||||
|
if (fullscreen) {
|
||||||
|
SDL_HideCursor();
|
||||||
|
Mouse::cursorVisible = false;
|
||||||
|
} else {
|
||||||
|
SDL_ShowCursor();
|
||||||
|
Mouse::cursorVisible = true;
|
||||||
|
Mouse::lastMouseMoveTime = SDL_GetTicks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calcula windowWidth/Height/dest para el modo ventana y aplica SDL_SetWindowSize
|
||||||
|
void Screen::applyWindowedLayout() {
|
||||||
|
windowWidth = gameCanvasWidth;
|
||||||
|
windowHeight = gameCanvasHeight;
|
||||||
|
dest = {0, 0, gameCanvasWidth, gameCanvasHeight};
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
windowWidth *= WASM_RENDER_SCALE;
|
||||||
|
windowHeight *= WASM_RENDER_SCALE;
|
||||||
|
dest.w *= WASM_RENDER_SCALE;
|
||||||
|
dest.h *= WASM_RENDER_SCALE;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Modifica el tamaño de la ventana
|
||||||
|
SDL_SetWindowSize(window, windowWidth * Options::window.zoom, windowHeight * Options::window.zoom);
|
||||||
|
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtiene el tamaño de la ventana en fullscreen y calcula el rect del juego
|
||||||
|
void Screen::applyFullscreenLayout() {
|
||||||
|
SDL_GetWindowSize(window, &windowWidth, &windowHeight);
|
||||||
|
computeFullscreenGameRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calcula el rectángulo dest para fullscreen: integer_scale / aspect ratio
|
||||||
|
void Screen::computeFullscreenGameRect() {
|
||||||
|
if (Options::video.integer_scale) {
|
||||||
|
// Calcula el tamaño de la escala máxima
|
||||||
|
int scale = 0;
|
||||||
|
while (((gameCanvasWidth * (scale + 1)) <= windowWidth) && ((gameCanvasHeight * (scale + 1)) <= windowHeight)) {
|
||||||
|
scale++;
|
||||||
|
}
|
||||||
|
|
||||||
|
dest.w = gameCanvasWidth * scale;
|
||||||
|
dest.h = gameCanvasHeight * scale;
|
||||||
|
dest.x = (windowWidth - dest.w) / 2;
|
||||||
|
dest.y = (windowHeight - dest.h) / 2;
|
||||||
|
} else {
|
||||||
|
// Manté la relació d'aspecte sense escalat enter (letterbox/pillarbox).
|
||||||
|
float ratio = (float)gameCanvasWidth / (float)gameCanvasHeight;
|
||||||
|
if ((windowWidth - gameCanvasWidth) >= (windowHeight - gameCanvasHeight)) {
|
||||||
|
dest.h = windowHeight;
|
||||||
|
dest.w = (int)((windowHeight * ratio) + 0.5f);
|
||||||
|
dest.x = (windowWidth - dest.w) / 2;
|
||||||
|
dest.y = (windowHeight - dest.h) / 2;
|
||||||
|
} else {
|
||||||
|
dest.w = windowWidth;
|
||||||
|
dest.h = (int)((windowWidth / ratio) + 0.5f);
|
||||||
|
dest.x = (windowWidth - dest.w) / 2;
|
||||||
|
dest.y = (windowHeight - dest.h) / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aplica la logical presentation y persiste el estado en options
|
||||||
|
void Screen::applyLogicalPresentation(bool fullscreen) {
|
||||||
|
SDL_SetRenderLogicalPresentation(renderer, windowWidth, windowHeight, SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||||
|
Options::video.fullscreen = fullscreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Notificaciones
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Muestra una notificación en la línea superior durante durationMs
|
||||||
|
void Screen::notify(const std::string &text, color_t textColor, color_t outlineColor, Uint32 durationMs) {
|
||||||
|
notificationMessage = text;
|
||||||
|
notificationTextColor = textColor;
|
||||||
|
notificationOutlineColor = outlineColor;
|
||||||
|
notificationEndTime = SDL_GetTicks() + durationMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limpia la notificación actual
|
||||||
|
void Screen::clearNotification() {
|
||||||
|
notificationEndTime = 0;
|
||||||
|
notificationMessage.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dibuja la notificación activa (si la hay) sobre el gameCanvas
|
||||||
|
void Screen::renderNotification() {
|
||||||
|
if (notificationText == nullptr || SDL_GetTicks() >= notificationEndTime) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
notificationText->writeDX(TXT_CENTER | TXT_COLOR | TXT_STROKE,
|
||||||
|
gameCanvasWidth / 2,
|
||||||
|
notificationY,
|
||||||
|
notificationMessage,
|
||||||
|
1,
|
||||||
|
notificationTextColor,
|
||||||
|
1,
|
||||||
|
notificationOutlineColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Emscripten — fix per a fullscreen/resize (veure el bloc de comentaris al
|
||||||
|
// principi del fitxer i l'anonymous namespace amb els callbacks natius).
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void Screen::handleCanvasResized() {
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
// La crida a SDL_SetWindowFullscreen + SDL_SetRenderLogicalPresentation
|
||||||
|
// que fa setVideoMode és l'única manera de resincronitzar l'estat intern
|
||||||
|
// de SDL amb el canvas HTML real.
|
||||||
|
setVideoMode(Options::video.fullscreen);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::syncFullscreenFlagFromBrowser(bool isFullscreen) {
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
Options::video.fullscreen = isFullscreen;
|
||||||
|
#else
|
||||||
|
(void)isFullscreen;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::registerEmscriptenEventCallbacks() {
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
// IMPORTANT: NO registrem resize callback. En mòbil, fer scroll fa que el
|
||||||
|
// navegador oculti/mostri la barra d'URL, disparant un resize del DOM per
|
||||||
|
// cada scroll. Això portava a cridar setVideoMode per cada scroll, que
|
||||||
|
// re-aplicava la logical presentation i corrompia el viewport intern de SDL.
|
||||||
|
g_screen_instance = this;
|
||||||
|
emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_TRUE, onEmFullscreenChange);
|
||||||
|
emscripten_set_orientationchange_callback(nullptr, EM_TRUE, onEmOrientationChange);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// GPU / shaders (SDL3 GPU post-procesado). En builds con NO_SHADERS (Emscripten)
|
||||||
|
// las operaciones son no-op; la ruta clásica sigue siendo la única disponible.
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
// Aplica al backend el shader actiu + els seus presets PostFX i CrtPi.
|
||||||
|
// Només s'ha de cridar quan `videoShaderEnabled=true` (en cas contrari el
|
||||||
|
// blit() ja força POSTFX+zero params per a desactivar els efectes sense
|
||||||
|
// tocar els paràmetres emmagatzemats).
|
||||||
|
void Screen::applyShaderParams() {
|
||||||
|
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
shader_backend_->setActiveShader(Options::video.shader.current_shader);
|
||||||
|
applyCurrentPostFXPreset();
|
||||||
|
applyCurrentCrtPiPreset();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void Screen::initShaders() {
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
if (!shader_backend_) {
|
||||||
|
shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
|
||||||
|
const std::string FALLBACK_DRIVER = "none";
|
||||||
|
shader_backend_->setPreferredDriver(
|
||||||
|
Options::video.gpu.acceleration ? Options::video.gpu.preferred_driver : FALLBACK_DRIVER);
|
||||||
|
}
|
||||||
|
if (!shader_backend_->isHardwareAccelerated()) {
|
||||||
|
const bool ok = shader_backend_->init(window, gameCanvas, "", "");
|
||||||
|
if (Options::settings.console) {
|
||||||
|
std::cout << "Screen::initShaders: SDL3GPUShader::init() = " << (ok ? "OK" : "FAILED") << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shader_backend_->isHardwareAccelerated()) {
|
||||||
|
shader_backend_->setScaleMode(Options::video.integer_scale);
|
||||||
|
shader_backend_->setVSync(Options::video.vsync);
|
||||||
|
|
||||||
|
// Resol els índexs de preset a partir del nom emmagatzemat al config.
|
||||||
|
// Si el nom no existeix (preset esborrat del YAML), es queda en 0.
|
||||||
|
for (int i = 0; i < static_cast<int>(Options::postfx_presets.size()); ++i) {
|
||||||
|
if (Options::postfx_presets[i].name == Options::video.shader.current_postfx_preset_name) {
|
||||||
|
Options::current_postfx_preset = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < static_cast<int>(Options::crtpi_presets.size()); ++i) {
|
||||||
|
if (Options::crtpi_presets[i].name == Options::video.shader.current_crtpi_preset_name) {
|
||||||
|
Options::current_crtpi_preset = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyShaderParams(); // aplica preset del shader actiu
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::shutdownShaders() {
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
// Només es crida des del destructor de Screen. Els toggles runtime NO la
|
||||||
|
// poden cridar: destruir + recrear el dispositiu SDL3 GPU amb la ventana
|
||||||
|
// ja reclamada és inestable (Vulkan/Radeon crasheja en el següent claim).
|
||||||
|
if (shader_backend_) {
|
||||||
|
shader_backend_->cleanup();
|
||||||
|
shader_backend_.reset();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Screen::isGpuAccelerated() const -> bool {
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
return shader_backend_ && shader_backend_->isHardwareAccelerated();
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::setShaderEnabled(bool enabled) {
|
||||||
|
if (Options::video.shader.enabled == enabled) { return; }
|
||||||
|
Options::video.shader.enabled = enabled;
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
if (enabled) {
|
||||||
|
applyShaderParams(); // restaura preset del shader actiu
|
||||||
|
}
|
||||||
|
// Si enabled=false, blit() forçarà POSTFX+zero per frame — no cal tocar
|
||||||
|
// res ara.
|
||||||
|
#endif
|
||||||
|
const color_t CYAN = {0x00, 0xFF, 0xFF};
|
||||||
|
const color_t BLACK = {0x00, 0x00, 0x00};
|
||||||
|
const Uint32 DUR_MS = 1500;
|
||||||
|
notify(enabled ? "Shader: ON" : "Shader: OFF", CYAN, BLACK, DUR_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::toggleShaderEnabled() {
|
||||||
|
setShaderEnabled(!Options::video.shader.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Screen::isShaderEnabled() const -> bool {
|
||||||
|
return Options::video.shader.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
void Screen::setActiveShader(Rendering::ShaderType type) {
|
||||||
|
Options::video.shader.current_shader = type;
|
||||||
|
if (Options::video.shader.enabled) {
|
||||||
|
applyShaderParams();
|
||||||
|
}
|
||||||
|
const color_t MAGENTA = {0xFF, 0x00, 0xFF};
|
||||||
|
const color_t BLACK = {0x00, 0x00, 0x00};
|
||||||
|
const Uint32 DUR_MS = 1500;
|
||||||
|
notify(type == Rendering::ShaderType::CRTPI ? "Shader: CRTPI" : "Shader: POSTFX", MAGENTA, BLACK, DUR_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Screen::getActiveShader() const -> Rendering::ShaderType {
|
||||||
|
return Options::video.shader.current_shader;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void Screen::toggleActiveShader() {
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
const Rendering::ShaderType NEXT = getActiveShader() == Rendering::ShaderType::POSTFX
|
||||||
|
? Rendering::ShaderType::CRTPI
|
||||||
|
: Rendering::ShaderType::POSTFX;
|
||||||
|
setActiveShader(NEXT);
|
||||||
|
#else
|
||||||
|
Options::video.shader.current_shader = Options::video.shader.current_shader == Rendering::ShaderType::POSTFX
|
||||||
|
? Rendering::ShaderType::CRTPI
|
||||||
|
: Rendering::ShaderType::POSTFX;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Presets de shaders
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void Screen::applyCurrentPostFXPreset() {
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) { return; }
|
||||||
|
if (Options::postfx_presets.empty()) { return; }
|
||||||
|
if (Options::current_postfx_preset < 0 || Options::current_postfx_preset >= static_cast<int>(Options::postfx_presets.size())) {
|
||||||
|
Options::current_postfx_preset = 0;
|
||||||
|
}
|
||||||
|
const auto &PRESET = Options::postfx_presets[Options::current_postfx_preset];
|
||||||
|
Rendering::PostFXParams p;
|
||||||
|
p.vignette = PRESET.vignette;
|
||||||
|
p.scanlines = PRESET.scanlines;
|
||||||
|
p.chroma = PRESET.chroma;
|
||||||
|
p.mask = PRESET.mask;
|
||||||
|
p.gamma = PRESET.gamma;
|
||||||
|
p.curvature = PRESET.curvature;
|
||||||
|
p.bleeding = PRESET.bleeding;
|
||||||
|
p.flicker = PRESET.flicker;
|
||||||
|
shader_backend_->setPostFXParams(p);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::applyCurrentCrtPiPreset() {
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) { return; }
|
||||||
|
if (Options::crtpi_presets.empty()) { return; }
|
||||||
|
if (Options::current_crtpi_preset < 0 || Options::current_crtpi_preset >= static_cast<int>(Options::crtpi_presets.size())) {
|
||||||
|
Options::current_crtpi_preset = 0;
|
||||||
|
}
|
||||||
|
const auto &PRESET = Options::crtpi_presets[Options::current_crtpi_preset];
|
||||||
|
Rendering::CrtPiParams p;
|
||||||
|
p.scanline_weight = PRESET.scanline_weight;
|
||||||
|
p.scanline_gap_brightness = PRESET.scanline_gap_brightness;
|
||||||
|
p.bloom_factor = PRESET.bloom_factor;
|
||||||
|
p.input_gamma = PRESET.input_gamma;
|
||||||
|
p.output_gamma = PRESET.output_gamma;
|
||||||
|
p.mask_brightness = PRESET.mask_brightness;
|
||||||
|
p.curvature_x = PRESET.curvature_x;
|
||||||
|
p.curvature_y = PRESET.curvature_y;
|
||||||
|
p.mask_type = PRESET.mask_type;
|
||||||
|
p.enable_scanlines = PRESET.enable_scanlines;
|
||||||
|
p.enable_multisample = PRESET.enable_multisample;
|
||||||
|
p.enable_gamma = PRESET.enable_gamma;
|
||||||
|
p.enable_curvature = PRESET.enable_curvature;
|
||||||
|
p.enable_sharper = PRESET.enable_sharper;
|
||||||
|
shader_backend_->setCrtPiParams(p);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Screen::getCurrentPresetName() const -> const char * {
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) { return "---"; }
|
||||||
|
if (Options::video.shader.current_shader == Rendering::ShaderType::POSTFX) {
|
||||||
|
if (Options::current_postfx_preset >= 0 && Options::current_postfx_preset < static_cast<int>(Options::postfx_presets.size())) {
|
||||||
|
return Options::postfx_presets[Options::current_postfx_preset].name.c_str();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (Options::current_crtpi_preset >= 0 && Options::current_crtpi_preset < static_cast<int>(Options::crtpi_presets.size())) {
|
||||||
|
return Options::crtpi_presets[Options::current_crtpi_preset].name.c_str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return "---";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Screen::nextPreset() -> bool {
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) { return false; }
|
||||||
|
if (!Options::video.shader.enabled) { return false; }
|
||||||
|
|
||||||
|
if (Options::video.shader.current_shader == Rendering::ShaderType::POSTFX) {
|
||||||
|
if (Options::postfx_presets.empty()) { return false; }
|
||||||
|
const int N = static_cast<int>(Options::postfx_presets.size());
|
||||||
|
Options::current_postfx_preset = (Options::current_postfx_preset + 1) % N;
|
||||||
|
Options::video.shader.current_postfx_preset_name =
|
||||||
|
Options::postfx_presets[Options::current_postfx_preset].name;
|
||||||
|
applyCurrentPostFXPreset();
|
||||||
|
} else {
|
||||||
|
if (Options::crtpi_presets.empty()) { return false; }
|
||||||
|
const int N = static_cast<int>(Options::crtpi_presets.size());
|
||||||
|
Options::current_crtpi_preset = (Options::current_crtpi_preset + 1) % N;
|
||||||
|
Options::video.shader.current_crtpi_preset_name =
|
||||||
|
Options::crtpi_presets[Options::current_crtpi_preset].name;
|
||||||
|
applyCurrentCrtPiPreset();
|
||||||
|
}
|
||||||
|
|
||||||
|
const color_t GREEN = {0x00, 0xFF, 0x80};
|
||||||
|
const color_t BLACK = {0x00, 0x00, 0x00};
|
||||||
|
const Uint32 DUR_MS = 1500;
|
||||||
|
notify(std::string("Preset: ") + getCurrentPresetName(), GREEN, BLACK, DUR_MS);
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Screen::prevPreset() -> bool {
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) { return false; }
|
||||||
|
if (!Options::video.shader.enabled) { return false; }
|
||||||
|
|
||||||
|
if (Options::video.shader.current_shader == Rendering::ShaderType::POSTFX) {
|
||||||
|
if (Options::postfx_presets.empty()) { return false; }
|
||||||
|
const int N = static_cast<int>(Options::postfx_presets.size());
|
||||||
|
Options::current_postfx_preset = (Options::current_postfx_preset - 1 + N) % N;
|
||||||
|
Options::video.shader.current_postfx_preset_name =
|
||||||
|
Options::postfx_presets[Options::current_postfx_preset].name;
|
||||||
|
applyCurrentPostFXPreset();
|
||||||
|
} else {
|
||||||
|
if (Options::crtpi_presets.empty()) { return false; }
|
||||||
|
const int N = static_cast<int>(Options::crtpi_presets.size());
|
||||||
|
Options::current_crtpi_preset = (Options::current_crtpi_preset - 1 + N) % N;
|
||||||
|
Options::video.shader.current_crtpi_preset_name =
|
||||||
|
Options::crtpi_presets[Options::current_crtpi_preset].name;
|
||||||
|
applyCurrentCrtPiPreset();
|
||||||
|
}
|
||||||
|
|
||||||
|
const color_t GREEN = {0x00, 0xFF, 0x80};
|
||||||
|
const color_t BLACK = {0x00, 0x00, 0x00};
|
||||||
|
const Uint32 DUR_MS = 1500;
|
||||||
|
notify(std::string("Preset: ") + getCurrentPresetName(), GREEN, BLACK, DUR_MS);
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
139
source/core/rendering/screen.h
Normal file
139
source/core/rendering/screen.h
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <memory> // for unique_ptr
|
||||||
|
#include <string> // for string
|
||||||
|
#include <vector> // for vector
|
||||||
|
|
||||||
|
#include "utils/utils.h" // for color_t
|
||||||
|
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
#include "core/rendering/shader_backend.hpp" // for Rendering::ShaderType
|
||||||
|
namespace Rendering {
|
||||||
|
class ShaderBackend;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class Text;
|
||||||
|
|
||||||
|
class Screen {
|
||||||
|
public:
|
||||||
|
// Constantes
|
||||||
|
static constexpr int WINDOW_ZOOM_MIN = 1;
|
||||||
|
static constexpr int WINDOW_ZOOM_MAX = 4;
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
// En WASM el tamaño de ventana está fijado a 1x, así que escalamos el
|
||||||
|
// renderizado por 3 aprovechando el modo NEAREST de la textura del juego
|
||||||
|
// para que los píxeles salgan nítidos.
|
||||||
|
static constexpr int WASM_RENDER_SCALE = 3;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Singleton API
|
||||||
|
static void init(SDL_Window *window, SDL_Renderer *renderer); // Crea la instancia
|
||||||
|
static void destroy(); // Libera la instancia
|
||||||
|
static auto get() -> Screen *; // Obtiene el puntero a la instancia
|
||||||
|
|
||||||
|
// Destructor (público por requisitos de `delete` desde destroy())
|
||||||
|
~Screen();
|
||||||
|
|
||||||
|
// Render loop
|
||||||
|
void clean(color_t color = {0x00, 0x00, 0x00}); // Limpia la pantalla
|
||||||
|
void start(); // Prepara para empezar a dibujar en la textura de juego
|
||||||
|
void blit(); // Vuelca el contenido del renderizador en pantalla
|
||||||
|
|
||||||
|
// Video y ventana
|
||||||
|
void setVideoMode(bool fullscreen); // Establece el modo de video
|
||||||
|
void toggleVideoMode(); // Cambia entre pantalla completa y ventana
|
||||||
|
void handleCanvasResized(); // En Emscripten, reaplica setVideoMode tras un cambio del navegador (salida de fullscreen con Esc, rotación). No-op fuera de Emscripten
|
||||||
|
void syncFullscreenFlagFromBrowser(bool isFullscreen); // Sincroniza el flag interno de fullscreen con el estado real del navegador. Debe llamarse antes de diferir handleCanvasResized. No-op fuera de Emscripten
|
||||||
|
void toggleIntegerScale(); // Alterna el escalado entero
|
||||||
|
void setIntegerScale(bool enabled); // Establece el escalado entero
|
||||||
|
void toggleVSync(); // Alterna el V-Sync
|
||||||
|
void setVSync(bool enabled); // Establece el V-Sync
|
||||||
|
auto decWindowZoom() -> bool; // Reduce el zoom de la ventana (devuelve true si cambió)
|
||||||
|
auto incWindowZoom() -> bool; // Aumenta el zoom de la ventana (devuelve true si cambió)
|
||||||
|
auto setWindowZoom(int zoom) -> bool; // Establece el zoom de la ventana (devuelve true si cambió)
|
||||||
|
|
||||||
|
// Borde
|
||||||
|
void setBorderColor(color_t color); // Cambia el color del borde
|
||||||
|
|
||||||
|
// Notificaciones
|
||||||
|
void initNotifications(); // Enllaça el Text de notificacions amb `Resource`. A cridar després de `Resource::init(...)`.
|
||||||
|
void notify(const std::string &text, color_t textColor, color_t outlineColor, Uint32 durationMs); // Muestra una notificación en la línea superior del canvas durante durationMs. Sobrescribe cualquier notificación activa (sin apilación).
|
||||||
|
void clearNotification(); // Limpia la notificación actual
|
||||||
|
|
||||||
|
// GPU / shaders (post-procesado). En builds con NO_SHADERS (Emscripten) son no-op.
|
||||||
|
void initShaders(); // Crea el backend GPU si no existe y lo inicializa
|
||||||
|
void shutdownShaders(); // Libera el backend GPU
|
||||||
|
auto isGpuAccelerated() const -> bool; // true si el backend existe y reporta hardware OK
|
||||||
|
void setShaderEnabled(bool enabled); // Activa o desactiva el post-procesado (persiste)
|
||||||
|
void toggleShaderEnabled(); // Alterna post-procesado
|
||||||
|
auto isShaderEnabled() const -> bool; // Estado actual (lee options)
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
void setActiveShader(Rendering::ShaderType type); // POSTFX o CRTPI
|
||||||
|
auto getActiveShader() const -> Rendering::ShaderType;
|
||||||
|
#endif
|
||||||
|
void toggleActiveShader(); // Alterna POSTFX ↔ CRTPI
|
||||||
|
|
||||||
|
// Presets de shaders (PostFX/CrtPi). Operen sobre el shader actiu.
|
||||||
|
// Retornen false si GPU off / shaders off / llista buida (igual que a aee_plus).
|
||||||
|
auto nextPreset() -> bool;
|
||||||
|
auto prevPreset() -> bool;
|
||||||
|
auto getCurrentPresetName() const -> const char *;
|
||||||
|
void applyCurrentPostFXPreset(); // Escriu el preset PostFX actiu al backend
|
||||||
|
void applyCurrentCrtPiPreset(); // Escriu el preset CrtPi actiu al backend
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Constructor privado (usar Screen::init)
|
||||||
|
Screen(SDL_Window *window, SDL_Renderer *renderer);
|
||||||
|
|
||||||
|
// Instancia única
|
||||||
|
static Screen *instance;
|
||||||
|
|
||||||
|
// Helpers internos de setVideoMode
|
||||||
|
void applyFullscreen(bool fullscreen); // SDL_SetWindowFullscreen + visibilidad del cursor
|
||||||
|
void applyWindowedLayout(); // Calcula windowWidth/Height/dest + SDL_SetWindowSize + SDL_SetWindowPosition
|
||||||
|
void applyFullscreenLayout(); // SDL_GetWindowSize + delegación a computeFullscreenGameRect
|
||||||
|
void computeFullscreenGameRect(); // Calcula dest en fullscreen (integerScale / keepAspect / stretched)
|
||||||
|
void applyLogicalPresentation(bool fullscreen); // SDL_SetRenderLogicalPresentation + persistencia a options
|
||||||
|
|
||||||
|
// Emscripten
|
||||||
|
void registerEmscriptenEventCallbacks(); // Registra los callbacks nativos de Emscripten para fullscreenchange y orientationchange. No-op fuera de Emscripten
|
||||||
|
|
||||||
|
// Notificaciones
|
||||||
|
void renderNotification(); // Dibuja la notificación activa (si la hay) sobre el gameCanvas
|
||||||
|
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
// Aplica els paràmetres actuals del shader al backend segons options
|
||||||
|
// (pass-through si `videoShaderEnabled==false`, preset per defecte si true).
|
||||||
|
void applyShaderParams();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Objetos y punteros
|
||||||
|
SDL_Window *window; // Ventana de la aplicación
|
||||||
|
SDL_Renderer *renderer; // El renderizador de la ventana
|
||||||
|
SDL_Texture *gameCanvas; // Textura para completar la ventana de juego hasta la pantalla completa
|
||||||
|
|
||||||
|
// Variables
|
||||||
|
int windowWidth; // Ancho de la pantalla o ventana
|
||||||
|
int windowHeight; // Alto de la pantalla o ventana
|
||||||
|
int gameCanvasWidth; // Resolución interna del juego. Es el ancho de la textura donde se dibuja el juego
|
||||||
|
int gameCanvasHeight; // Resolución interna del juego. Es el alto de la textura donde se dibuja el juego
|
||||||
|
SDL_Rect dest; // Coordenadas donde se va a dibujar la textura del juego sobre la pantalla o ventana
|
||||||
|
color_t borderColor; // Color del borde añadido a la textura de juego para rellenar la pantalla
|
||||||
|
|
||||||
|
// Notificaciones - una sola activa, sin apilación ni animaciones
|
||||||
|
Text *notificationText; // Fuente 8bithud dedicada a las notificaciones
|
||||||
|
std::string notificationMessage; // Texto a mostrar
|
||||||
|
color_t notificationTextColor; // Color del texto
|
||||||
|
color_t notificationOutlineColor; // Color del outline
|
||||||
|
Uint32 notificationEndTime; // SDL_GetTicks() hasta el cual se muestra
|
||||||
|
int notificationY; // Fila vertical en el canvas virtual
|
||||||
|
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
// GPU / shaders
|
||||||
|
std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend GPU (nullptr si inactivo)
|
||||||
|
std::vector<Uint32> pixel_buffer_; // Buffer de readback del gameCanvas (ARGB8888)
|
||||||
|
#endif
|
||||||
|
};
|
||||||
1329
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp
Normal file
1329
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp
Normal file
File diff suppressed because it is too large
Load Diff
178
source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp
Normal file
178
source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_gpu.h>
|
||||||
|
|
||||||
|
#include "core/rendering/shader_backend.hpp"
|
||||||
|
|
||||||
|
// PostFX uniforms pushed to fragment stage each frame.
|
||||||
|
// Must match the MSL struct and GLSL uniform block layout.
|
||||||
|
// 12 floats = 48 bytes — meets Metal/Vulkan 16-byte alignment requirement.
|
||||||
|
struct PostFXUniforms {
|
||||||
|
float vignette_strength; // 0 = none, ~0.8 = subtle
|
||||||
|
float chroma_strength; // 0 = off, ~0.2 = subtle chromatic aberration
|
||||||
|
float scanline_strength; // 0 = off, 1 = full
|
||||||
|
float screen_height; // logical height in pixels (used by bleeding effect)
|
||||||
|
float mask_strength; // 0 = off, 1 = full phosphor dot mask
|
||||||
|
float gamma_strength; // 0 = off, 1 = full gamma 2.4/2.2 correction
|
||||||
|
float curvature; // 0 = flat, 1 = max barrel distortion
|
||||||
|
float bleeding; // 0 = off, 1 = max NTSC chrominance bleeding
|
||||||
|
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
|
||||||
|
float time; // seconds since SDL init (SDL_GetTicks() / 1000.0f)
|
||||||
|
float oversample; // supersampling factor (1.0 = off, 3.0 = 3×SS)
|
||||||
|
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz — keep struct at 48 bytes (3 × 16)
|
||||||
|
};
|
||||||
|
|
||||||
|
// CrtPi uniforms pushed to fragment stage each frame.
|
||||||
|
// Must match the MSL struct and GLSL uniform block layout.
|
||||||
|
// 14 fields (8 floats + 6 ints) + 2 floats (texture size) = 16 fields = 64 bytes — 4 × 16-byte alignment.
|
||||||
|
struct CrtPiUniforms {
|
||||||
|
// vec4 #0
|
||||||
|
float scanline_weight; // Ajuste gaussiano (default 6.0)
|
||||||
|
float scanline_gap_brightness; // Brillo mínimo entre scanlines (default 0.12)
|
||||||
|
float bloom_factor; // Factor brillo zonas iluminadas (default 3.5)
|
||||||
|
float input_gamma; // Gamma de entrada (default 2.4)
|
||||||
|
// vec4 #1
|
||||||
|
float output_gamma; // Gamma de salida (default 2.2)
|
||||||
|
float mask_brightness; // Brillo sub-píxeles máscara (default 0.80)
|
||||||
|
float curvature_x; // Distorsión barrel X (default 0.05)
|
||||||
|
float curvature_y; // Distorsión barrel Y (default 0.10)
|
||||||
|
// vec4 #2
|
||||||
|
int mask_type; // 0=ninguna, 1=verde/magenta, 2=RGB fósforo
|
||||||
|
int enable_scanlines; // 0 = off, 1 = on
|
||||||
|
int enable_multisample; // 0 = off, 1 = on (antialiasing analítico)
|
||||||
|
int enable_gamma; // 0 = off, 1 = on
|
||||||
|
// vec4 #3
|
||||||
|
int enable_curvature; // 0 = off, 1 = on
|
||||||
|
int enable_sharper; // 0 = off, 1 = on
|
||||||
|
float texture_width; // Ancho del canvas en píxeles (inyectado en render)
|
||||||
|
float texture_height; // Alto del canvas en píxeles (inyectado en render)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Downscale uniforms pushed to the Lanczos downscale fragment stage.
|
||||||
|
// 1 int + 3 floats = 16 bytes — meets Metal/Vulkan alignment.
|
||||||
|
struct DownscaleUniforms {
|
||||||
|
int algorithm; // 0 = Lanczos2 (ventana 2), 1 = Lanczos3 (ventana 3)
|
||||||
|
float pad0;
|
||||||
|
float pad1;
|
||||||
|
float pad2;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Backend de shaders usando SDL3 GPU API (Metal en macOS, Vulkan/SPIR-V en Win/Linux)
|
||||||
|
*
|
||||||
|
* Reemplaza el backend OpenGL para que los shaders PostFX funcionen en macOS.
|
||||||
|
* Pipeline: Surface pixels (CPU) → SDL_GPUTransferBuffer → SDL_GPUTexture (scene)
|
||||||
|
* → PostFX render pass → swapchain → present
|
||||||
|
*/
|
||||||
|
class SDL3GPUShader : public ShaderBackend {
|
||||||
|
public:
|
||||||
|
SDL3GPUShader() = default;
|
||||||
|
~SDL3GPUShader() override;
|
||||||
|
|
||||||
|
auto init(SDL_Window* window,
|
||||||
|
SDL_Texture* texture,
|
||||||
|
const std::string& vertex_source,
|
||||||
|
const std::string& fragment_source) -> bool override;
|
||||||
|
|
||||||
|
void render() override;
|
||||||
|
void setTextureSize(float width, float height) override {}
|
||||||
|
void cleanup() final; // Libera pipeline/texturas pero mantiene el device vivo
|
||||||
|
void destroy(); // Limpieza completa (device + swapchain); llamar solo al cerrar
|
||||||
|
[[nodiscard]] auto isHardwareAccelerated() const -> bool override { return is_initialized_; }
|
||||||
|
[[nodiscard]] auto getDriverName() const -> std::string override { return driver_name_; }
|
||||||
|
|
||||||
|
// Establece el driver GPU preferido (vacío = auto). Debe llamarse antes de init().
|
||||||
|
void setPreferredDriver(const std::string& driver) override { preferred_driver_ = driver; }
|
||||||
|
|
||||||
|
// Sube píxeles ARGB8888 desde CPU; llamado antes de render()
|
||||||
|
void uploadPixels(const Uint32* pixels, int width, int height) override;
|
||||||
|
|
||||||
|
// Actualiza los parámetros de intensidad de los efectos PostFX
|
||||||
|
void setPostFXParams(const PostFXParams& p) override;
|
||||||
|
|
||||||
|
// Activa/desactiva VSync en el swapchain
|
||||||
|
void setVSync(bool vsync) override;
|
||||||
|
|
||||||
|
// Activa/desactiva escalado entero (integer scale)
|
||||||
|
void setScaleMode(bool integer_scale) override;
|
||||||
|
|
||||||
|
// Establece factor de supersampling (1 = off, 3 = 3×SS)
|
||||||
|
void setOversample(int factor) override;
|
||||||
|
|
||||||
|
// Activa/desactiva interpolación LINEAR en el upscale (false = NEAREST)
|
||||||
|
void setLinearUpscale(bool linear) override;
|
||||||
|
|
||||||
|
// Selecciona algoritmo de downscale: 0=bilinear legacy, 1=Lanczos2, 2=Lanczos3
|
||||||
|
void setDownscaleAlgo(int algo) override;
|
||||||
|
|
||||||
|
// Devuelve las dimensiones de la textura de supersampling (0,0 si SS desactivado)
|
||||||
|
[[nodiscard]] auto getSsTextureSize() const -> std::pair<int, int> override;
|
||||||
|
|
||||||
|
// Selecciona el shader de post-procesado activo (POSTFX o CRTPI)
|
||||||
|
void setActiveShader(ShaderType type) override;
|
||||||
|
|
||||||
|
// Actualiza los parámetros del shader CRT-Pi
|
||||||
|
void setCrtPiParams(const CrtPiParams& p) override;
|
||||||
|
|
||||||
|
// Devuelve el shader activo
|
||||||
|
[[nodiscard]] auto getActiveShader() const -> ShaderType override { return active_shader_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
static auto createShaderMSL(SDL_GPUDevice* device,
|
||||||
|
const char* msl_source,
|
||||||
|
const char* entrypoint,
|
||||||
|
SDL_GPUShaderStage stage,
|
||||||
|
Uint32 num_samplers,
|
||||||
|
Uint32 num_uniform_buffers) -> SDL_GPUShader*;
|
||||||
|
|
||||||
|
static auto createShaderSPIRV(SDL_GPUDevice* device,
|
||||||
|
const uint8_t* spv_code,
|
||||||
|
size_t spv_size,
|
||||||
|
const char* entrypoint,
|
||||||
|
SDL_GPUShaderStage stage,
|
||||||
|
Uint32 num_samplers,
|
||||||
|
Uint32 num_uniform_buffers) -> SDL_GPUShader*;
|
||||||
|
|
||||||
|
auto createPipeline() -> bool;
|
||||||
|
auto createCrtPiPipeline() -> bool; // Pipeline dedicado para el shader CrtPi
|
||||||
|
auto reinitTexturesAndBuffer() -> bool; // Recrea scene_texture_ y upload_buffer_
|
||||||
|
auto recreateScaledTexture(int factor) -> bool; // Recrea scaled_texture_ para factor dado
|
||||||
|
static auto calcSsFactor(float zoom) -> int; // Primer múltiplo de 3 >= zoom (mín 3)
|
||||||
|
// Devuelve el mejor present mode disponible: IMMEDIATE > MAILBOX > VSYNC
|
||||||
|
[[nodiscard]] auto bestPresentMode(bool vsync) const -> SDL_GPUPresentMode;
|
||||||
|
|
||||||
|
SDL_Window* window_ = nullptr;
|
||||||
|
SDL_GPUDevice* device_ = nullptr;
|
||||||
|
SDL_GPUGraphicsPipeline* pipeline_ = nullptr; // PostFX pass (→ swapchain o → postfx_texture_)
|
||||||
|
SDL_GPUGraphicsPipeline* crtpi_pipeline_ = nullptr; // CrtPi pass (→ swapchain directo, sin SS)
|
||||||
|
SDL_GPUGraphicsPipeline* postfx_offscreen_pipeline_ = nullptr; // PostFX → postfx_texture_ (B8G8R8A8, solo con Lanczos)
|
||||||
|
SDL_GPUGraphicsPipeline* upscale_pipeline_ = nullptr; // Upscale pass (solo con SS)
|
||||||
|
SDL_GPUGraphicsPipeline* downscale_pipeline_ = nullptr; // Lanczos downscale (solo con SS + algo > 0)
|
||||||
|
SDL_GPUTexture* scene_texture_ = nullptr; // Canvas del juego (game_width_ × game_height_)
|
||||||
|
SDL_GPUTexture* scaled_texture_ = nullptr; // Upscale target (game×factor), solo con SS
|
||||||
|
SDL_GPUTexture* postfx_texture_ = nullptr; // PostFX output a resolución escalada, solo con Lanczos
|
||||||
|
SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
|
||||||
|
SDL_GPUSampler* sampler_ = nullptr; // NEAREST
|
||||||
|
SDL_GPUSampler* linear_sampler_ = nullptr; // LINEAR
|
||||||
|
|
||||||
|
PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_strength = 0.15F, .scanline_strength = 0.7F, .screen_height = 192.0F, .pixel_scale = 1.0F, .oversample = 1.0F};
|
||||||
|
CrtPiUniforms crtpi_uniforms_{.scanline_weight = 6.0F, .scanline_gap_brightness = 0.12F, .bloom_factor = 3.5F, .input_gamma = 2.4F, .output_gamma = 2.2F, .mask_brightness = 0.80F, .curvature_x = 0.05F, .curvature_y = 0.10F, .mask_type = 2, .enable_scanlines = 1, .enable_multisample = 1, .enable_gamma = 1};
|
||||||
|
ShaderType active_shader_ = ShaderType::POSTFX; // Shader de post-procesado activo
|
||||||
|
|
||||||
|
int game_width_ = 0; // Dimensiones originales del canvas
|
||||||
|
int game_height_ = 0;
|
||||||
|
int ss_factor_ = 0; // Factor SS activo (3, 6, 9...) o 0 si SS desactivado
|
||||||
|
int oversample_ = 1; // SS on/off (1 = off, >1 = on)
|
||||||
|
int downscale_algo_ = 1; // 0 = bilinear legacy, 1 = Lanczos2, 2 = Lanczos3
|
||||||
|
std::string driver_name_;
|
||||||
|
std::string preferred_driver_; // Driver preferido; vacío = auto (SDL elige)
|
||||||
|
bool is_initialized_ = false;
|
||||||
|
bool vsync_ = true;
|
||||||
|
bool integer_scale_ = false;
|
||||||
|
bool linear_upscale_ = false; // Upscale NEAREST (false) o LINEAR (true)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
2
source/core/rendering/sdl3gpu/spv/.clang-format
Normal file
2
source/core/rendering/sdl3gpu/spv/.clang-format
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
DisableFormat: true
|
||||||
|
SortIncludes: Never
|
||||||
4
source/core/rendering/sdl3gpu/spv/.clang-tidy
Normal file
4
source/core/rendering/sdl3gpu/spv/.clang-tidy
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# source/core/rendering/sdl3gpu/spv/.clang-tidy
|
||||||
|
Checks: '-*'
|
||||||
|
WarningsAsErrors: ''
|
||||||
|
HeaderFilterRegex: ''
|
||||||
10362
source/core/rendering/sdl3gpu/spv/crtpi_frag_spv.h
Normal file
10362
source/core/rendering/sdl3gpu/spv/crtpi_frag_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
4253
source/core/rendering/sdl3gpu/spv/downscale_frag_spv.h
Normal file
4253
source/core/rendering/sdl3gpu/spv/downscale_frag_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
11717
source/core/rendering/sdl3gpu/spv/postfx_frag_spv.h
Normal file
11717
source/core/rendering/sdl3gpu/spv/postfx_frag_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
1449
source/core/rendering/sdl3gpu/spv/postfx_vert_spv.h
Normal file
1449
source/core/rendering/sdl3gpu/spv/postfx_vert_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
633
source/core/rendering/sdl3gpu/spv/upscale_frag_spv.h
Normal file
633
source/core/rendering/sdl3gpu/spv/upscale_frag_spv.h
Normal file
@@ -0,0 +1,633 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
static const uint8_t kupscale_frag_spv[] = {
|
||||||
|
0x03,
|
||||||
|
0x02,
|
||||||
|
0x23,
|
||||||
|
0x07,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x0b,
|
||||||
|
0x00,
|
||||||
|
0x0d,
|
||||||
|
0x00,
|
||||||
|
0x14,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x11,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0b,
|
||||||
|
0x00,
|
||||||
|
0x06,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x47,
|
||||||
|
0x4c,
|
||||||
|
0x53,
|
||||||
|
0x4c,
|
||||||
|
0x2e,
|
||||||
|
0x73,
|
||||||
|
0x74,
|
||||||
|
0x64,
|
||||||
|
0x2e,
|
||||||
|
0x34,
|
||||||
|
0x35,
|
||||||
|
0x30,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0e,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0f,
|
||||||
|
0x00,
|
||||||
|
0x07,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x6d,
|
||||||
|
0x61,
|
||||||
|
0x69,
|
||||||
|
0x6e,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x09,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x11,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x10,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x07,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0xc2,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0a,
|
||||||
|
0x00,
|
||||||
|
0x47,
|
||||||
|
0x4c,
|
||||||
|
0x5f,
|
||||||
|
0x47,
|
||||||
|
0x4f,
|
||||||
|
0x4f,
|
||||||
|
0x47,
|
||||||
|
0x4c,
|
||||||
|
0x45,
|
||||||
|
0x5f,
|
||||||
|
0x63,
|
||||||
|
0x70,
|
||||||
|
0x70,
|
||||||
|
0x5f,
|
||||||
|
0x73,
|
||||||
|
0x74,
|
||||||
|
0x79,
|
||||||
|
0x6c,
|
||||||
|
0x65,
|
||||||
|
0x5f,
|
||||||
|
0x6c,
|
||||||
|
0x69,
|
||||||
|
0x6e,
|
||||||
|
0x65,
|
||||||
|
0x5f,
|
||||||
|
0x64,
|
||||||
|
0x69,
|
||||||
|
0x72,
|
||||||
|
0x65,
|
||||||
|
0x63,
|
||||||
|
0x74,
|
||||||
|
0x69,
|
||||||
|
0x76,
|
||||||
|
0x65,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x08,
|
||||||
|
0x00,
|
||||||
|
0x47,
|
||||||
|
0x4c,
|
||||||
|
0x5f,
|
||||||
|
0x47,
|
||||||
|
0x4f,
|
||||||
|
0x4f,
|
||||||
|
0x47,
|
||||||
|
0x4c,
|
||||||
|
0x45,
|
||||||
|
0x5f,
|
||||||
|
0x69,
|
||||||
|
0x6e,
|
||||||
|
0x63,
|
||||||
|
0x6c,
|
||||||
|
0x75,
|
||||||
|
0x64,
|
||||||
|
0x65,
|
||||||
|
0x5f,
|
||||||
|
0x64,
|
||||||
|
0x69,
|
||||||
|
0x72,
|
||||||
|
0x65,
|
||||||
|
0x63,
|
||||||
|
0x74,
|
||||||
|
0x69,
|
||||||
|
0x76,
|
||||||
|
0x65,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x6d,
|
||||||
|
0x61,
|
||||||
|
0x69,
|
||||||
|
0x6e,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x09,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x6f,
|
||||||
|
0x75,
|
||||||
|
0x74,
|
||||||
|
0x5f,
|
||||||
|
0x63,
|
||||||
|
0x6f,
|
||||||
|
0x6c,
|
||||||
|
0x6f,
|
||||||
|
0x72,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0d,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x73,
|
||||||
|
0x63,
|
||||||
|
0x65,
|
||||||
|
0x6e,
|
||||||
|
0x65,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x11,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x76,
|
||||||
|
0x5f,
|
||||||
|
0x75,
|
||||||
|
0x76,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x47,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x09,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x1e,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x47,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0d,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x21,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x47,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0d,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x22,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x47,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x11,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x1e,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x13,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x21,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x16,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x06,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x20,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x17,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x07,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x06,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x20,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x08,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x07,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x3b,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x08,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x09,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x19,
|
||||||
|
0x00,
|
||||||
|
0x09,
|
||||||
|
0x00,
|
||||||
|
0x0a,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x06,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x1b,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x0b,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0a,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x20,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0c,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0b,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x3b,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0c,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0d,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x17,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0f,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x06,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x20,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x10,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0f,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x3b,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x10,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x11,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x36,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0xf8,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x3d,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0b,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0e,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0d,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x3d,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0f,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x12,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x11,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x57,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x07,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x13,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0e,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x12,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x3e,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x09,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x13,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0xfd,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x38,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00};
|
||||||
|
static const size_t kupscale_frag_spv_size = 628;
|
||||||
175
source/core/rendering/shader_backend.hpp
Normal file
175
source/core/rendering/shader_backend.hpp
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
/** @brief Identificador del shader de post-procesado activo */
|
||||||
|
enum class ShaderType { POSTFX,
|
||||||
|
CRTPI };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parámetros de intensidad de los efectos PostFX
|
||||||
|
* Definido a nivel de namespace para facilitar el uso desde subclases y screen.cpp
|
||||||
|
*/
|
||||||
|
struct PostFXParams {
|
||||||
|
float vignette = 0.0F; // Intensidad de la viñeta
|
||||||
|
float scanlines = 0.0F; // Intensidad de las scanlines
|
||||||
|
float chroma = 0.0F; // Aberración cromática
|
||||||
|
float mask = 0.0F; // Máscara de fósforo RGB
|
||||||
|
float gamma = 0.0F; // Corrección gamma (blend 0=off, 1=full)
|
||||||
|
float curvature = 0.0F; // Curvatura barrel CRT
|
||||||
|
float bleeding = 0.0F; // Sangrado de color NTSC
|
||||||
|
float flicker = 0.0F; // Parpadeo de fósforo CRT ~50 Hz
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parámetros del shader CRT-Pi (algoritmo de scanlines continuas)
|
||||||
|
* Diferente al PostFX: usa pesos gaussianos por distancia subpixel y bloom.
|
||||||
|
*/
|
||||||
|
struct CrtPiParams {
|
||||||
|
float scanline_weight{6.0F}; // Ajuste gaussiano (mayor = scanlines más estrechas)
|
||||||
|
float scanline_gap_brightness{0.12F}; // Brillo mínimo en las ranuras entre scanlines
|
||||||
|
float bloom_factor{3.5F}; // Factor de brillo para zonas iluminadas
|
||||||
|
float input_gamma{2.4F}; // Gamma de entrada (linealización)
|
||||||
|
float output_gamma{2.2F}; // Gamma de salida (codificación)
|
||||||
|
float mask_brightness{0.80F}; // Sub-píxeles tenues en la máscara de fósforo
|
||||||
|
float curvature_x{0.05F}; // Distorsión barrel eje X
|
||||||
|
float curvature_y{0.10F}; // Distorsión barrel eje Y
|
||||||
|
int mask_type{2}; // 0=ninguna, 1=verde/magenta, 2=RGB fósforo
|
||||||
|
bool enable_scanlines{true}; // Activar efecto de scanlines
|
||||||
|
bool enable_multisample{true}; // Antialiasing analítico de scanlines
|
||||||
|
bool enable_gamma{true}; // Corrección gamma
|
||||||
|
bool enable_curvature{false}; // Distorsión barrel CRT
|
||||||
|
bool enable_sharper{false}; // Submuestreo más nítido (modo SHARPER)
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Interfaz abstracta para backends de renderizado con shaders
|
||||||
|
*
|
||||||
|
* Esta interfaz define el contrato que todos los backends de shaders
|
||||||
|
* deben cumplir (OpenGL, Metal, Vulkan, etc.)
|
||||||
|
*/
|
||||||
|
class ShaderBackend {
|
||||||
|
public:
|
||||||
|
virtual ~ShaderBackend() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Inicializa el backend de shaders
|
||||||
|
* @param window Ventana SDL
|
||||||
|
* @param texture Textura de backbuffer a la que aplicar shaders
|
||||||
|
* @param vertex_source Código fuente del vertex shader
|
||||||
|
* @param fragment_source Código fuente del fragment shader
|
||||||
|
* @return true si la inicialización fue exitosa
|
||||||
|
*/
|
||||||
|
virtual auto init(SDL_Window* window,
|
||||||
|
SDL_Texture* texture,
|
||||||
|
const std::string& vertex_source,
|
||||||
|
const std::string& fragment_source) -> bool = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Renderiza la textura con los shaders aplicados
|
||||||
|
*/
|
||||||
|
virtual void render() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Establece el tamaño de la textura como parámetro del shader
|
||||||
|
* @param width Ancho de la textura
|
||||||
|
* @param height Alto de la textura
|
||||||
|
*/
|
||||||
|
virtual void setTextureSize(float width, float height) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Limpia y libera recursos del backend
|
||||||
|
*/
|
||||||
|
virtual void cleanup() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sube píxeles ARGB8888 desde la CPU al backend de shaders
|
||||||
|
* Usado por SDL3GPUShader para evitar pasar por SDL_Texture
|
||||||
|
*/
|
||||||
|
virtual void uploadPixels(const Uint32* /*pixels*/, int /*width*/, int /*height*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Establece los parámetros de intensidad de los efectos PostFX
|
||||||
|
* @param p Struct con todos los parámetros PostFX
|
||||||
|
*/
|
||||||
|
virtual void setPostFXParams(const PostFXParams& /*p*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Activa o desactiva VSync en el swapchain del GPU device
|
||||||
|
*/
|
||||||
|
virtual void setVSync(bool /*vsync*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Activa o desactiva el escalado entero (integer scale)
|
||||||
|
*/
|
||||||
|
virtual void setScaleMode(bool /*integer_scale*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Establece el factor de supersampling (1 = off, 3 = 3× SS)
|
||||||
|
* Con factor > 1, la textura GPU se crea a game×factor resolución y
|
||||||
|
* las scanlines se hornean en CPU (uploadPixels). El sampler usa LINEAR.
|
||||||
|
*/
|
||||||
|
virtual void setOversample(int /*factor*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Activa/desactiva interpolación LINEAR en el paso de upscale (SS).
|
||||||
|
* Por defecto NEAREST (false). Solo tiene efecto con supersampling activo.
|
||||||
|
*/
|
||||||
|
virtual void setLinearUpscale(bool /*linear*/) {}
|
||||||
|
[[nodiscard]] virtual auto isLinearUpscale() const -> bool { return false; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Selecciona el algoritmo de downscale tras el PostFX (SS activo).
|
||||||
|
* 0 = bilinear legacy (comportamiento actual, sin textura intermedia),
|
||||||
|
* 1 = Lanczos2 (ventana 2, ~25 muestras), 2 = Lanczos3 (ventana 3, ~49 muestras).
|
||||||
|
*/
|
||||||
|
virtual void setDownscaleAlgo(int /*algo*/) {}
|
||||||
|
[[nodiscard]] virtual auto getDownscaleAlgo() const -> int { return 0; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Devuelve las dimensiones de la textura de supersampling.
|
||||||
|
* @return Par (ancho, alto) en píxeles; (0, 0) si SS está desactivado.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual auto getSsTextureSize() const -> std::pair<int, int> { return {0, 0}; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Verifica si el backend está usando aceleración por hardware
|
||||||
|
* @return true si usa aceleración (OpenGL/Metal/Vulkan)
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual auto isHardwareAccelerated() const -> bool = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Nombre del driver GPU activo (p.ej. "vulkan", "metal", "direct3d12")
|
||||||
|
* @return Cadena vacía si no disponible
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual auto getDriverName() const -> std::string { return {}; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Establece el driver GPU preferido antes de init().
|
||||||
|
* Vacío = selección automática de SDL. Implementado en SDL3GPUShader.
|
||||||
|
*/
|
||||||
|
virtual void setPreferredDriver(const std::string& /*driver*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Selecciona el shader de post-procesado activo (POSTFX o CRTPI).
|
||||||
|
* Debe llamarse antes de render(). No recrea pipelines.
|
||||||
|
*/
|
||||||
|
virtual void setActiveShader(ShaderType /*type*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Establece los parámetros del shader CRT-Pi.
|
||||||
|
*/
|
||||||
|
virtual void setCrtPiParams(const CrtPiParams& /*p*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Devuelve el shader de post-procesado activo.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual auto getActiveShader() const -> ShaderType { return ShaderType::POSTFX; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "smartsprite.h"
|
#include "core/rendering/smartsprite.h"
|
||||||
|
|
||||||
#include "movingsprite.h" // for MovingSprite
|
#include "core/rendering/movingsprite.h" // for MovingSprite
|
||||||
class Texture;
|
class Texture;
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
@@ -46,16 +46,6 @@ void SmartSprite::render() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtiene el valor de la variable
|
|
||||||
bool SmartSprite::isEnabled() {
|
|
||||||
return enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Establece el valor de la variable
|
|
||||||
void SmartSprite::setEnabled(bool enabled) {
|
|
||||||
this->enabled = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Obtiene el valor de la variable
|
// Obtiene el valor de la variable
|
||||||
int SmartSprite::getEnabledCounter() {
|
int SmartSprite::getEnabledCounter() {
|
||||||
return enabledCounter;
|
return enabledCounter;
|
||||||
@@ -2,14 +2,13 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include "animatedsprite.h" // for AnimatedSprite
|
#include "core/rendering/animatedsprite.h" // for AnimatedSprite
|
||||||
class Texture;
|
class Texture;
|
||||||
|
|
||||||
// Clase SmartSprite
|
// Clase SmartSprite
|
||||||
class SmartSprite : public AnimatedSprite {
|
class SmartSprite : public AnimatedSprite {
|
||||||
private:
|
private:
|
||||||
// Variables
|
// Variables
|
||||||
bool enabled; // Indica si esta habilitado
|
|
||||||
bool onDestination; // Indica si está en el destino
|
bool onDestination; // Indica si está en el destino
|
||||||
int destX; // Posicion de destino en el eje X
|
int destX; // Posicion de destino en el eje X
|
||||||
int destY; // Posicion de destino en el eje Y
|
int destY; // Posicion de destino en el eje Y
|
||||||
@@ -30,16 +29,10 @@ class SmartSprite : public AnimatedSprite {
|
|||||||
void init();
|
void init();
|
||||||
|
|
||||||
// Actualiza la posición y comprueba si ha llegado a su destino
|
// Actualiza la posición y comprueba si ha llegado a su destino
|
||||||
void update();
|
void update() override;
|
||||||
|
|
||||||
// Pinta el objeto en pantalla
|
// Pinta el objeto en pantalla
|
||||||
void render();
|
void render() override;
|
||||||
|
|
||||||
// Obtiene el valor de la variable
|
|
||||||
bool isEnabled();
|
|
||||||
|
|
||||||
// Establece el valor de la variable
|
|
||||||
void setEnabled(bool enabled);
|
|
||||||
|
|
||||||
// Obtiene el valor de la variable
|
// Obtiene el valor de la variable
|
||||||
int getEnabledCounter();
|
int getEnabledCounter();
|
||||||
@@ -1,50 +1,28 @@
|
|||||||
#include "sprite.h"
|
#include "core/rendering/sprite.h"
|
||||||
|
|
||||||
#include "texture.h" // for Texture
|
#include "core/rendering/texture.h" // for Texture
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Sprite::Sprite(int x, int y, int w, int h, Texture *texture, SDL_Renderer *renderer) {
|
Sprite::Sprite(int x, int y, int w, int h, Texture *texture, SDL_Renderer *renderer)
|
||||||
// Establece la posición X,Y del sprite
|
: x(x),
|
||||||
this->x = x;
|
y(y),
|
||||||
this->y = y;
|
w(w),
|
||||||
|
h(h),
|
||||||
// Establece el alto y el ancho del sprite
|
renderer(renderer),
|
||||||
this->w = w;
|
texture(texture),
|
||||||
this->h = h;
|
spriteClip{0, 0, w, h},
|
||||||
|
enabled(true) {
|
||||||
// Establece el puntero al renderizador de la ventana
|
|
||||||
this->renderer = renderer;
|
|
||||||
|
|
||||||
// Establece la textura donde están los gráficos para el sprite
|
|
||||||
this->texture = texture;
|
|
||||||
|
|
||||||
// Establece el rectangulo de donde coger la imagen
|
|
||||||
spriteClip = {0, 0, w, h};
|
|
||||||
|
|
||||||
// Inicializa variables
|
|
||||||
enabled = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Sprite::Sprite(SDL_Rect rect, Texture *texture, SDL_Renderer *renderer) {
|
Sprite::Sprite(SDL_Rect rect, Texture *texture, SDL_Renderer *renderer)
|
||||||
// Establece la posición X,Y del sprite
|
: x(rect.x),
|
||||||
x = rect.x;
|
y(rect.y),
|
||||||
y = rect.y;
|
w(rect.w),
|
||||||
|
h(rect.h),
|
||||||
// Establece el alto y el ancho del sprite
|
renderer(renderer),
|
||||||
w = rect.w;
|
texture(texture),
|
||||||
h = rect.h;
|
spriteClip{0, 0, rect.w, rect.h},
|
||||||
|
enabled(true) {
|
||||||
// Establece el puntero al renderizador de la ventana
|
|
||||||
this->renderer = renderer;
|
|
||||||
|
|
||||||
// Establece la textura donde están los gráficos para el sprite
|
|
||||||
this->texture = texture;
|
|
||||||
|
|
||||||
// Establece el rectangulo de donde coger la imagen
|
|
||||||
spriteClip = {0, 0, w, h};
|
|
||||||
|
|
||||||
// Inicializa variables
|
|
||||||
enabled = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
@@ -19,14 +19,14 @@ class Sprite {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
// Constructor
|
// Constructor
|
||||||
Sprite(int x = 0, int y = 0, int w = 0, int h = 0, Texture *texture = nullptr, SDL_Renderer *renderer = nullptr);
|
explicit Sprite(int x = 0, int y = 0, int w = 0, int h = 0, Texture *texture = nullptr, SDL_Renderer *renderer = nullptr);
|
||||||
Sprite(SDL_Rect rect, Texture *texture, SDL_Renderer *renderer);
|
Sprite(SDL_Rect rect, Texture *texture, SDL_Renderer *renderer);
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
~Sprite();
|
virtual ~Sprite();
|
||||||
|
|
||||||
// Muestra el sprite por pantalla
|
// Muestra el sprite por pantalla
|
||||||
void render();
|
virtual void render();
|
||||||
|
|
||||||
// Obten el valor de la variable
|
// Obten el valor de la variable
|
||||||
int getPosX();
|
int getPosX();
|
||||||
@@ -77,14 +77,14 @@ class Sprite {
|
|||||||
void setRenderer(SDL_Renderer *renderer);
|
void setRenderer(SDL_Renderer *renderer);
|
||||||
|
|
||||||
// Establece el valor de la variable
|
// Establece el valor de la variable
|
||||||
void setEnabled(bool value);
|
virtual void setEnabled(bool value);
|
||||||
|
|
||||||
// Comprueba si el objeto está habilitado
|
// Comprueba si el objeto está habilitado
|
||||||
bool isEnabled();
|
virtual bool isEnabled();
|
||||||
|
|
||||||
// Devuelve el rectangulo donde está el sprite
|
// Devuelve el rectangulo donde está el sprite
|
||||||
SDL_Rect getRect();
|
virtual SDL_Rect getRect();
|
||||||
|
|
||||||
// Establece los valores de posición y tamaño del sprite
|
// Establece los valores de posición y tamaño del sprite
|
||||||
void setRect(SDL_Rect rect);
|
virtual void setRect(SDL_Rect rect);
|
||||||
};
|
};
|
||||||
@@ -1,79 +1,93 @@
|
|||||||
|
|
||||||
#include "text.h"
|
#include "core/rendering/text.h"
|
||||||
|
|
||||||
#include <fstream> // for char_traits, basic_ostream, basic_ifstream, ope...
|
#include <fstream> // for char_traits, basic_ostream, basic_ifstream, ope...
|
||||||
#include <iostream> // for cout
|
#include <iostream> // for cout
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
#include "sprite.h" // for Sprite
|
#include "core/rendering/sprite.h" // for Sprite
|
||||||
#include "texture.h" // for Texture
|
#include "core/rendering/texture.h" // for Texture
|
||||||
#include "utils.h" // for color_t
|
#include "utils/utils.h" // for color_t
|
||||||
|
|
||||||
|
// Parser compartido: rellena un textFile_t desde cualquier istream
|
||||||
|
static void parseTextFileStream(std::istream &rfile, textFile_t &tf) {
|
||||||
|
std::string buffer;
|
||||||
|
std::getline(rfile, buffer);
|
||||||
|
std::getline(rfile, buffer);
|
||||||
|
tf.boxWidth = std::stoi(buffer);
|
||||||
|
|
||||||
|
std::getline(rfile, buffer);
|
||||||
|
std::getline(rfile, buffer);
|
||||||
|
tf.boxHeight = std::stoi(buffer);
|
||||||
|
|
||||||
|
int index = 32;
|
||||||
|
int line_read = 0;
|
||||||
|
while (std::getline(rfile, buffer)) {
|
||||||
|
if (line_read % 2 == 1) {
|
||||||
|
tf.offset[index++].w = std::stoi(buffer);
|
||||||
|
}
|
||||||
|
buffer.clear();
|
||||||
|
line_read++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void computeTextFileOffsets(textFile_t &tf) {
|
||||||
|
for (int i = 32; i < 128; ++i) {
|
||||||
|
tf.offset[i].x = ((i - 32) % 15) * tf.boxWidth;
|
||||||
|
tf.offset[i].y = ((i - 32) / 15) * tf.boxHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Llena una estructuta textFile_t desde un fichero
|
// Llena una estructuta textFile_t desde un fichero
|
||||||
textFile_t LoadTextFile(std::string file, bool verbose) {
|
textFile_t LoadTextFile(const std::string &file, bool verbose) {
|
||||||
textFile_t tf;
|
textFile_t tf;
|
||||||
|
tf.boxWidth = 0;
|
||||||
// Inicializa a cero el vector con las coordenadas
|
tf.boxHeight = 0;
|
||||||
for (int i = 0; i < 128; ++i) {
|
for (int i = 0; i < 128; ++i) {
|
||||||
tf.offset[i].x = 0;
|
tf.offset[i].x = 0;
|
||||||
tf.offset[i].y = 0;
|
tf.offset[i].y = 0;
|
||||||
tf.offset[i].w = 0;
|
tf.offset[i].w = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Abre el fichero para leer los valores
|
|
||||||
const std::string filename = file.substr(file.find_last_of("\\/") + 1).c_str();
|
const std::string filename = file.substr(file.find_last_of("\\/") + 1).c_str();
|
||||||
std::ifstream rfile(file);
|
std::ifstream rfile(file);
|
||||||
|
|
||||||
if (rfile.is_open() && rfile.good()) {
|
if (rfile.is_open() && rfile.good()) {
|
||||||
std::string buffer;
|
parseTextFileStream(rfile, tf);
|
||||||
|
|
||||||
// Lee los dos primeros valores del fichero
|
|
||||||
std::getline(rfile, buffer);
|
|
||||||
std::getline(rfile, buffer);
|
|
||||||
tf.boxWidth = std::stoi(buffer);
|
|
||||||
|
|
||||||
std::getline(rfile, buffer);
|
|
||||||
std::getline(rfile, buffer);
|
|
||||||
tf.boxHeight = std::stoi(buffer);
|
|
||||||
|
|
||||||
// lee el resto de datos del fichero
|
|
||||||
int index = 32;
|
|
||||||
int line_read = 0;
|
|
||||||
while (std::getline(rfile, buffer)) {
|
|
||||||
// Almacena solo las lineas impares
|
|
||||||
if (line_read % 2 == 1) {
|
|
||||||
tf.offset[index++].w = std::stoi(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limpia el buffer
|
|
||||||
buffer.clear();
|
|
||||||
line_read++;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Cierra el fichero
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
std::cout << "Text loaded: " << filename.c_str() << std::endl;
|
std::cout << "Text loaded: " << filename.c_str() << std::endl;
|
||||||
}
|
}
|
||||||
rfile.close();
|
} else if (verbose) {
|
||||||
|
std::cout << "Warning: Unable to open " << filename.c_str() << " file" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// El fichero no se puede abrir
|
computeTextFileOffsets(tf);
|
||||||
else {
|
return tf;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Llena una estructura textFile_t desde bytes en memoria
|
||||||
|
textFile_t LoadTextFileFromMemory(const std::vector<uint8_t> &bytes, bool verbose) {
|
||||||
|
textFile_t tf;
|
||||||
|
tf.boxWidth = 0;
|
||||||
|
tf.boxHeight = 0;
|
||||||
|
for (int i = 0; i < 128; ++i) {
|
||||||
|
tf.offset[i].x = 0;
|
||||||
|
tf.offset[i].y = 0;
|
||||||
|
tf.offset[i].w = 0;
|
||||||
|
}
|
||||||
|
if (!bytes.empty()) {
|
||||||
|
std::string content(reinterpret_cast<const char *>(bytes.data()), bytes.size());
|
||||||
|
std::stringstream ss(content);
|
||||||
|
parseTextFileStream(ss, tf);
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
std::cout << "Warning: Unable to open " << filename.c_str() << " file" << std::endl;
|
std::cout << "Text loaded from memory" << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
computeTextFileOffsets(tf);
|
||||||
// Establece las coordenadas para cada caracter ascii de la cadena y su ancho
|
|
||||||
for (int i = 32; i < 128; ++i) {
|
|
||||||
tf.offset[i].x = ((i - 32) % 15) * tf.boxWidth;
|
|
||||||
tf.offset[i].y = ((i - 32) / 15) * tf.boxHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tf;
|
return tf;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Text::Text(std::string bitmapFile, std::string textFile, SDL_Renderer *renderer) {
|
Text::Text(const std::string &bitmapFile, const std::string &textFile, SDL_Renderer *renderer) {
|
||||||
// Carga los offsets desde el fichero
|
// Carga los offsets desde el fichero
|
||||||
textFile_t tf = LoadTextFile(textFile);
|
textFile_t tf = LoadTextFile(textFile);
|
||||||
|
|
||||||
@@ -95,7 +109,7 @@ Text::Text(std::string bitmapFile, std::string textFile, SDL_Renderer *renderer)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Text::Text(std::string textFile, Texture *texture, SDL_Renderer *renderer) {
|
Text::Text(const std::string &textFile, Texture *texture, SDL_Renderer *renderer) {
|
||||||
// Carga los offsets desde el fichero
|
// Carga los offsets desde el fichero
|
||||||
textFile_t tf = LoadTextFile(textFile);
|
textFile_t tf = LoadTextFile(textFile);
|
||||||
|
|
||||||
@@ -135,6 +149,24 @@ Text::Text(textFile_t *textFile, Texture *texture, SDL_Renderer *renderer) {
|
|||||||
fixedWidth = false;
|
fixedWidth = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constructor desde bytes
|
||||||
|
Text::Text(const std::vector<uint8_t> &pngBytes, const std::vector<uint8_t> &txtBytes, SDL_Renderer *renderer) {
|
||||||
|
textFile_t tf = LoadTextFileFromMemory(txtBytes);
|
||||||
|
boxHeight = tf.boxHeight;
|
||||||
|
boxWidth = tf.boxWidth;
|
||||||
|
for (int i = 0; i < 128; ++i) {
|
||||||
|
offset[i].x = tf.offset[i].x;
|
||||||
|
offset[i].y = tf.offset[i].y;
|
||||||
|
offset[i].w = tf.offset[i].w;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crea la textura desde bytes (Text es dueño en este overload)
|
||||||
|
texture = new Texture(renderer, pngBytes);
|
||||||
|
sprite = new Sprite({0, 0, boxWidth, boxHeight}, texture, renderer);
|
||||||
|
|
||||||
|
fixedWidth = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
Text::~Text() {
|
Text::~Text() {
|
||||||
delete sprite;
|
delete sprite;
|
||||||
@@ -144,7 +176,7 @@ Text::~Text() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Escribe texto en pantalla
|
// Escribe texto en pantalla
|
||||||
void Text::write(int x, int y, std::string text, int kerning, int lenght) {
|
void Text::write(int x, int y, const std::string &text, int kerning, int lenght) {
|
||||||
int shift = 0;
|
int shift = 0;
|
||||||
|
|
||||||
if (lenght == -1) {
|
if (lenght == -1) {
|
||||||
@@ -164,14 +196,14 @@ void Text::write(int x, int y, std::string text, int kerning, int lenght) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Escribe el texto con colores
|
// Escribe el texto con colores
|
||||||
void Text::writeColored(int x, int y, std::string text, color_t color, int kerning, int lenght) {
|
void Text::writeColored(int x, int y, const std::string &text, color_t color, int kerning, int lenght) {
|
||||||
sprite->getTexture()->setColor(color.r, color.g, color.b);
|
sprite->getTexture()->setColor(color.r, color.g, color.b);
|
||||||
write(x, y, text, kerning, lenght);
|
write(x, y, text, kerning, lenght);
|
||||||
sprite->getTexture()->setColor(255, 255, 255);
|
sprite->getTexture()->setColor(255, 255, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escribe el texto con sombra
|
// Escribe el texto con sombra
|
||||||
void Text::writeShadowed(int x, int y, std::string text, color_t color, Uint8 shadowDistance, int kerning, int lenght) {
|
void Text::writeShadowed(int x, int y, const std::string &text, color_t color, Uint8 shadowDistance, int kerning, int lenght) {
|
||||||
sprite->getTexture()->setColor(color.r, color.g, color.b);
|
sprite->getTexture()->setColor(color.r, color.g, color.b);
|
||||||
write(x + shadowDistance, y + shadowDistance, text, kerning, lenght);
|
write(x + shadowDistance, y + shadowDistance, text, kerning, lenght);
|
||||||
sprite->getTexture()->setColor(255, 255, 255);
|
sprite->getTexture()->setColor(255, 255, 255);
|
||||||
@@ -179,13 +211,13 @@ void Text::writeShadowed(int x, int y, std::string text, color_t color, Uint8 sh
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Escribe el texto centrado en un punto x
|
// Escribe el texto centrado en un punto x
|
||||||
void Text::writeCentered(int x, int y, std::string text, int kerning, int lenght) {
|
void Text::writeCentered(int x, int y, const std::string &text, int kerning, int lenght) {
|
||||||
x -= (Text::lenght(text, kerning) / 2);
|
x -= (Text::lenght(text, kerning) / 2);
|
||||||
write(x, y, text, kerning, lenght);
|
write(x, y, text, kerning, lenght);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escribe texto con extras
|
// Escribe texto con extras
|
||||||
void Text::writeDX(Uint8 flags, int x, int y, std::string text, int kerning, color_t textColor, Uint8 shadowDistance, color_t shadowColor, int lenght) {
|
void Text::writeDX(Uint8 flags, int x, int y, const std::string &text, int kerning, color_t textColor, Uint8 shadowDistance, color_t shadowColor, int lenght) {
|
||||||
const bool centered = ((flags & TXT_CENTER) == TXT_CENTER);
|
const bool centered = ((flags & TXT_CENTER) == TXT_CENTER);
|
||||||
const bool shadowed = ((flags & TXT_SHADOW) == TXT_SHADOW);
|
const bool shadowed = ((flags & TXT_SHADOW) == TXT_SHADOW);
|
||||||
const bool colored = ((flags & TXT_COLOR) == TXT_COLOR);
|
const bool colored = ((flags & TXT_COLOR) == TXT_COLOR);
|
||||||
@@ -217,7 +249,7 @@ void Text::writeDX(Uint8 flags, int x, int y, std::string text, int kerning, col
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Obtiene la longitud en pixels de una cadena
|
// Obtiene la longitud en pixels de una cadena
|
||||||
int Text::lenght(std::string text, int kerning) {
|
int Text::lenght(const std::string &text, int kerning) {
|
||||||
int shift = 0;
|
int shift = 0;
|
||||||
|
|
||||||
for (int i = 0; i < (int)text.length(); ++i)
|
for (int i = 0; i < (int)text.length(); ++i)
|
||||||
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <string> // for string
|
#include <string> // for string
|
||||||
|
#include <vector>
|
||||||
class Sprite;
|
class Sprite;
|
||||||
class Texture;
|
class Texture;
|
||||||
#include "utils.h"
|
#include "utils/utils.h"
|
||||||
|
|
||||||
// Opciones de texto
|
// Opciones de texto
|
||||||
constexpr int TXT_COLOR = 1;
|
constexpr int TXT_COLOR = 1;
|
||||||
@@ -26,7 +28,10 @@ struct textFile_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Llena una estructuta textFile_t desde un fichero
|
// Llena una estructuta textFile_t desde un fichero
|
||||||
textFile_t LoadTextFile(std::string file, bool verbose = false);
|
textFile_t LoadTextFile(const std::string &file, bool verbose = false);
|
||||||
|
|
||||||
|
// Llena una estructura textFile_t desde bytes en memoria
|
||||||
|
textFile_t LoadTextFileFromMemory(const std::vector<uint8_t> &bytes, bool verbose = false);
|
||||||
|
|
||||||
// Clase texto. Pinta texto en pantalla a partir de un bitmap
|
// Clase texto. Pinta texto en pantalla a partir de un bitmap
|
||||||
class Text {
|
class Text {
|
||||||
@@ -43,30 +48,37 @@ class Text {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
// Constructor
|
// Constructor
|
||||||
Text(std::string bitmapFile, std::string textFile, SDL_Renderer *renderer);
|
Text(const std::string &bitmapFile, const std::string &textFile, SDL_Renderer *renderer);
|
||||||
Text(std::string textFile, Texture *texture, SDL_Renderer *renderer);
|
Text(const std::string &textFile, Texture *texture, SDL_Renderer *renderer);
|
||||||
Text(textFile_t *textFile, Texture *texture, SDL_Renderer *renderer);
|
Text(textFile_t *textFile, Texture *texture, SDL_Renderer *renderer);
|
||||||
|
|
||||||
|
// Constructor desde bytes en memoria: comparte ownership del texture (no lo libera)
|
||||||
|
Text(const std::vector<uint8_t> &pngBytes, const std::vector<uint8_t> &txtBytes, SDL_Renderer *renderer);
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
~Text();
|
~Text();
|
||||||
|
|
||||||
|
// No copiable (gestiona memoria dinámica)
|
||||||
|
Text(const Text &) = delete;
|
||||||
|
Text &operator=(const Text &) = delete;
|
||||||
|
|
||||||
// Escribe el texto en pantalla
|
// Escribe el texto en pantalla
|
||||||
void write(int x, int y, std::string text, int kerning = 1, int lenght = -1);
|
void write(int x, int y, const std::string &text, int kerning = 1, int lenght = -1);
|
||||||
|
|
||||||
// Escribe el texto con colores
|
// Escribe el texto con colores
|
||||||
void writeColored(int x, int y, std::string text, color_t color, int kerning = 1, int lenght = -1);
|
void writeColored(int x, int y, const std::string &text, color_t color, int kerning = 1, int lenght = -1);
|
||||||
|
|
||||||
// Escribe el texto con sombra
|
// Escribe el texto con sombra
|
||||||
void writeShadowed(int x, int y, std::string text, color_t color, Uint8 shadowDistance = 1, int kerning = 1, int lenght = -1);
|
void writeShadowed(int x, int y, const std::string &text, color_t color, Uint8 shadowDistance = 1, int kerning = 1, int lenght = -1);
|
||||||
|
|
||||||
// Escribe el texto centrado en un punto x
|
// Escribe el texto centrado en un punto x
|
||||||
void writeCentered(int x, int y, std::string text, int kerning = 1, int lenght = -1);
|
void writeCentered(int x, int y, const std::string &text, int kerning = 1, int lenght = -1);
|
||||||
|
|
||||||
// Escribe texto con extras
|
// Escribe texto con extras
|
||||||
void writeDX(Uint8 flags, int x, int y, std::string text, int kerning = 1, color_t textColor = color_t(255, 255, 255), Uint8 shadowDistance = 1, color_t shadowColor = color_t(0, 0, 0), int lenght = -1);
|
void writeDX(Uint8 flags, int x, int y, const std::string &text, int kerning = 1, color_t textColor = color_t(255, 255, 255), Uint8 shadowDistance = 1, color_t shadowColor = color_t(0, 0, 0), int lenght = -1);
|
||||||
|
|
||||||
// Obtiene la longitud en pixels de una cadena
|
// Obtiene la longitud en pixels de una cadena
|
||||||
int lenght(std::string text, int kerning = 1);
|
int lenght(const std::string &text, int kerning = 1);
|
||||||
|
|
||||||
// Devuelve el valor de la variable
|
// Devuelve el valor de la variable
|
||||||
int getCharacterSize();
|
int getCharacterSize();
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
|
|
||||||
#include "texture.h"
|
#include "core/rendering/texture.h"
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <stdlib.h> // for exit
|
#include <stdlib.h> // for exit
|
||||||
|
|
||||||
#include <iostream> // for basic_ostream, operator<<, cout, endl
|
#include <iostream> // for basic_ostream, operator<<, cout, endl
|
||||||
#define STB_IMAGE_IMPLEMENTATION
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
#include "stb_image.h" // for stbi_failure_reason, stbi_image_free
|
#include "external/stb_image.h" // for stbi_failure_reason, stbi_image_free
|
||||||
|
|
||||||
SDL_ScaleMode Texture::currentScaleMode = SDL_SCALEMODE_NEAREST;
|
SDL_ScaleMode Texture::currentScaleMode = SDL_SCALEMODE_NEAREST;
|
||||||
|
|
||||||
@@ -15,91 +15,97 @@ void Texture::setGlobalScaleMode(SDL_ScaleMode mode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Texture::Texture(SDL_Renderer *renderer, std::string path, bool verbose) {
|
Texture::Texture(SDL_Renderer *renderer, const std::string &path, bool verbose)
|
||||||
// Copia punteros
|
: texture(nullptr),
|
||||||
this->renderer = renderer;
|
renderer(renderer),
|
||||||
this->path = path;
|
width(0),
|
||||||
|
height(0),
|
||||||
// Inicializa
|
path(path) {
|
||||||
texture = nullptr;
|
|
||||||
width = 0;
|
|
||||||
height = 0;
|
|
||||||
|
|
||||||
// Carga el fichero en la textura
|
// Carga el fichero en la textura
|
||||||
if (path != "") {
|
if (!path.empty()) {
|
||||||
loadFromFile(path, renderer, verbose);
|
loadFromFile(path, renderer, verbose);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constructor desde bytes
|
||||||
|
Texture::Texture(SDL_Renderer *renderer, const std::vector<uint8_t> &bytes, bool verbose)
|
||||||
|
: texture(nullptr),
|
||||||
|
renderer(renderer),
|
||||||
|
width(0),
|
||||||
|
height(0),
|
||||||
|
path("") {
|
||||||
|
if (!bytes.empty()) {
|
||||||
|
loadFromMemory(bytes.data(), bytes.size(), renderer, verbose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
Texture::~Texture() {
|
Texture::~Texture() {
|
||||||
// Libera memoria
|
// Libera memoria
|
||||||
unload();
|
unload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper: convierte píxeles RGBA decodificados por stbi en SDL_Texture
|
||||||
|
static SDL_Texture *createTextureFromPixels(SDL_Renderer *renderer, unsigned char *data, int w, int h, int *out_w, int *out_h) {
|
||||||
|
const int pitch = 4 * w;
|
||||||
|
SDL_Surface *loadedSurface = SDL_CreateSurfaceFrom(w, h, SDL_PIXELFORMAT_RGBA32, static_cast<void *>(data), pitch);
|
||||||
|
if (loadedSurface == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
SDL_Texture *newTexture = SDL_CreateTextureFromSurface(renderer, loadedSurface);
|
||||||
|
if (newTexture != nullptr) {
|
||||||
|
*out_w = loadedSurface->w;
|
||||||
|
*out_h = loadedSurface->h;
|
||||||
|
SDL_SetTextureScaleMode(newTexture, Texture::currentScaleMode);
|
||||||
|
}
|
||||||
|
SDL_DestroySurface(loadedSurface);
|
||||||
|
return newTexture;
|
||||||
|
}
|
||||||
|
|
||||||
// Carga una imagen desde un fichero
|
// Carga una imagen desde un fichero
|
||||||
bool Texture::loadFromFile(std::string path, SDL_Renderer *renderer, bool verbose) {
|
bool Texture::loadFromFile(const std::string &path, SDL_Renderer *renderer, bool verbose) {
|
||||||
const std::string filename = path.substr(path.find_last_of("\\/") + 1);
|
const std::string filename = path.substr(path.find_last_of("\\/") + 1);
|
||||||
int req_format = STBI_rgb_alpha;
|
int req_format = STBI_rgb_alpha;
|
||||||
int width, height, orig_format;
|
int w, h, orig_format;
|
||||||
unsigned char *data = stbi_load(path.c_str(), &width, &height, &orig_format, req_format);
|
unsigned char *data = stbi_load(path.c_str(), &w, &h, &orig_format, req_format);
|
||||||
if (data == nullptr) {
|
if (data == nullptr) {
|
||||||
SDL_Log("Loading image failed: %s", stbi_failure_reason());
|
SDL_Log("Loading image failed: %s", stbi_failure_reason());
|
||||||
exit(1);
|
exit(1);
|
||||||
} else {
|
} else if (verbose) {
|
||||||
if (verbose) {
|
std::cout << "Image loaded: " << filename.c_str() << std::endl;
|
||||||
std::cout << "Image loaded: " << filename.c_str() << std::endl;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int pitch;
|
|
||||||
SDL_PixelFormat pixel_format;
|
|
||||||
if (req_format == STBI_rgb) {
|
|
||||||
pitch = 3 * width; // 3 bytes por pixel * pixels per linea
|
|
||||||
pixel_format = SDL_PIXELFORMAT_RGB24;
|
|
||||||
} else { // STBI_rgb_alpha (RGBA)
|
|
||||||
pitch = 4 * width;
|
|
||||||
pixel_format = SDL_PIXELFORMAT_RGBA32;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limpia
|
|
||||||
unload();
|
unload();
|
||||||
|
SDL_Texture *newTexture = createTextureFromPixels(renderer, data, w, h, &this->width, &this->height);
|
||||||
// La textura final
|
if (newTexture == nullptr && verbose) {
|
||||||
SDL_Texture *newTexture = nullptr;
|
std::cout << "Unable to load image " << path.c_str() << std::endl;
|
||||||
|
|
||||||
// Carga la imagen desde una ruta específica
|
|
||||||
SDL_Surface *loadedSurface = SDL_CreateSurfaceFrom(width, height, pixel_format, (void *)data, pitch);
|
|
||||||
if (loadedSurface == nullptr) {
|
|
||||||
if (verbose) {
|
|
||||||
std::cout << "Unable to load image " << path.c_str() << std::endl;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Crea la textura desde los pixels de la surface
|
|
||||||
newTexture = SDL_CreateTextureFromSurface(renderer, loadedSurface);
|
|
||||||
if (newTexture == nullptr) {
|
|
||||||
if (verbose) {
|
|
||||||
std::cout << "Unable to create texture from " << path.c_str() << "! SDL Error: " << SDL_GetError() << std::endl;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Obtiene las dimensiones de la imagen
|
|
||||||
this->width = loadedSurface->w;
|
|
||||||
this->height = loadedSurface->h;
|
|
||||||
|
|
||||||
// Aplica el modo de escalado
|
|
||||||
SDL_SetTextureScaleMode(newTexture, currentScaleMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Elimina la textura cargada
|
|
||||||
SDL_DestroySurface(loadedSurface);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return success
|
|
||||||
stbi_image_free(data);
|
stbi_image_free(data);
|
||||||
texture = newTexture;
|
texture = newTexture;
|
||||||
return texture != nullptr;
|
return texture != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Carga una imagen desde bytes en memoria
|
||||||
|
bool Texture::loadFromMemory(const uint8_t *data, size_t size, SDL_Renderer *renderer, bool verbose) {
|
||||||
|
int w, h, orig_format;
|
||||||
|
unsigned char *pixels = stbi_load_from_memory(data, (int)size, &w, &h, &orig_format, STBI_rgb_alpha);
|
||||||
|
if (pixels == nullptr) {
|
||||||
|
SDL_Log("Loading image from memory failed: %s", stbi_failure_reason());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
unload();
|
||||||
|
SDL_Texture *newTexture = createTextureFromPixels(renderer, pixels, w, h, &this->width, &this->height);
|
||||||
|
if (newTexture == nullptr && verbose) {
|
||||||
|
std::cout << "Unable to create texture from memory" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
stbi_image_free(pixels);
|
||||||
|
texture = newTexture;
|
||||||
|
return texture != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// Crea una textura en blanco
|
// Crea una textura en blanco
|
||||||
bool Texture::createBlank(SDL_Renderer *renderer, int width, int height, SDL_TextureAccess access) {
|
bool Texture::createBlank(SDL_Renderer *renderer, int width, int height, SDL_TextureAccess access) {
|
||||||
// Crea una textura sin inicializar
|
// Crea una textura sin inicializar
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <string> // for basic_string, string
|
#include <string> // for basic_string, string
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class Texture {
|
class Texture {
|
||||||
private:
|
private:
|
||||||
@@ -22,13 +24,19 @@ class Texture {
|
|||||||
static void setGlobalScaleMode(SDL_ScaleMode mode);
|
static void setGlobalScaleMode(SDL_ScaleMode mode);
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Texture(SDL_Renderer *renderer, std::string path = "", bool verbose = false);
|
explicit Texture(SDL_Renderer *renderer, const std::string &path = "", bool verbose = false);
|
||||||
|
|
||||||
|
// Constructor desde bytes (PNG en memoria)
|
||||||
|
Texture(SDL_Renderer *renderer, const std::vector<uint8_t> &bytes, bool verbose = false);
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
~Texture();
|
~Texture();
|
||||||
|
|
||||||
// Carga una imagen desde un fichero
|
// Carga una imagen desde un fichero
|
||||||
bool loadFromFile(std::string path, SDL_Renderer *renderer, bool verbose = false);
|
bool loadFromFile(const std::string &path, SDL_Renderer *renderer, bool verbose = false);
|
||||||
|
|
||||||
|
// Carga una imagen desde bytes en memoria
|
||||||
|
bool loadFromMemory(const uint8_t *data, size_t size, SDL_Renderer *renderer, bool verbose = false);
|
||||||
|
|
||||||
// Crea una textura en blanco
|
// Crea una textura en blanco
|
||||||
bool createBlank(SDL_Renderer *renderer, int width, int height, SDL_TextureAccess = SDL_TEXTUREACCESS_STREAMING);
|
bool createBlank(SDL_Renderer *renderer, int width, int height, SDL_TextureAccess = SDL_TEXTUREACCESS_STREAMING);
|
||||||
@@ -1,25 +1,22 @@
|
|||||||
#include "writer.h"
|
#include "core/rendering/writer.h"
|
||||||
|
|
||||||
#include "text.h" // for Text
|
#include "core/rendering/text.h" // for Text
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Writer::Writer(Text *text) {
|
Writer::Writer(Text *text)
|
||||||
// Copia los punteros
|
: text(text),
|
||||||
this->text = text;
|
posX(0),
|
||||||
|
posY(0),
|
||||||
// Inicializa variables
|
kerning(0),
|
||||||
posX = 0;
|
caption(""),
|
||||||
posY = 0;
|
speed(0),
|
||||||
kerning = 0;
|
writingCounter(0),
|
||||||
caption = "";
|
index(0),
|
||||||
speed = 0;
|
lenght(0),
|
||||||
writingCounter = 0;
|
completed(false),
|
||||||
index = 0;
|
enabled(false),
|
||||||
lenght = 0;
|
enabledCounter(0),
|
||||||
completed = false;
|
finished(false) {
|
||||||
enabled = false;
|
|
||||||
enabledCounter = 0;
|
|
||||||
finished = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualiza el objeto
|
// Actualiza el objeto
|
||||||
@@ -73,7 +70,7 @@ void Writer::setKerning(int value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Establece el valor de la variable
|
// Establece el valor de la variable
|
||||||
void Writer::setCaption(std::string text) {
|
void Writer::setCaption(const std::string &text) {
|
||||||
caption = text;
|
caption = text;
|
||||||
lenght = text.length();
|
lenght = text.length();
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ class Writer {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
// Constructor
|
// Constructor
|
||||||
Writer(Text *text);
|
explicit Writer(Text *text);
|
||||||
|
|
||||||
// Actualiza el objeto
|
// Actualiza el objeto
|
||||||
void update();
|
void update();
|
||||||
@@ -43,7 +43,7 @@ class Writer {
|
|||||||
void setKerning(int value);
|
void setKerning(int value);
|
||||||
|
|
||||||
// Establece el valor de la variable
|
// Establece el valor de la variable
|
||||||
void setCaption(std::string text);
|
void setCaption(const std::string &text);
|
||||||
|
|
||||||
// Establece el valor de la variable
|
// Establece el valor de la variable
|
||||||
void setSpeed(int value);
|
void setSpeed(int value);
|
||||||
@@ -1,19 +1,38 @@
|
|||||||
#include "asset.h"
|
#include "core/resources/asset.h"
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <stddef.h> // for size_t
|
#include <stddef.h> // for size_t
|
||||||
|
|
||||||
#include <iostream> // for basic_ostream, operator<<, cout, endl
|
#include <iostream> // for basic_ostream, operator<<, cout, endl
|
||||||
|
|
||||||
|
#include "core/resources/resource_helper.h"
|
||||||
|
|
||||||
|
// Instancia única
|
||||||
|
Asset *Asset::instance = nullptr;
|
||||||
|
|
||||||
|
// Singleton API
|
||||||
|
void Asset::init(const std::string &executablePath) {
|
||||||
|
Asset::instance = new Asset(executablePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Asset::destroy() {
|
||||||
|
delete Asset::instance;
|
||||||
|
Asset::instance = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Asset::get() -> Asset * {
|
||||||
|
return Asset::instance;
|
||||||
|
}
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Asset::Asset(std::string executablePath) {
|
Asset::Asset(const std::string &executablePath)
|
||||||
this->executablePath = executablePath.substr(0, executablePath.find_last_of("\\/"));
|
: longestName(0),
|
||||||
longestName = 0;
|
executablePath(executablePath.substr(0, executablePath.find_last_of("\\/"))),
|
||||||
verbose = true;
|
verbose(true) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Añade un elemento a la lista
|
// Añade un elemento a la lista
|
||||||
void Asset::add(std::string file, enum assetType type, bool required, bool absolute) {
|
void Asset::add(const std::string &file, enum assetType type, bool required, bool absolute) {
|
||||||
item_t temp;
|
item_t temp;
|
||||||
temp.file = absolute ? file : executablePath + file;
|
temp.file = absolute ? file : executablePath + file;
|
||||||
temp.type = type;
|
temp.type = type;
|
||||||
@@ -25,8 +44,8 @@ void Asset::add(std::string file, enum assetType type, bool required, bool absol
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Devuelve el fichero de un elemento de la lista a partir de una cadena
|
// Devuelve el fichero de un elemento de la lista a partir de una cadena
|
||||||
std::string Asset::get(std::string text) {
|
std::string Asset::get(const std::string &text) {
|
||||||
for (auto f : fileList) {
|
for (const auto &f : fileList) {
|
||||||
const size_t lastIndex = f.file.find_last_of("/") + 1;
|
const size_t lastIndex = f.file.find_last_of("/") + 1;
|
||||||
const std::string file = f.file.substr(lastIndex, std::string::npos);
|
const std::string file = f.file.substr(lastIndex, std::string::npos);
|
||||||
|
|
||||||
@@ -57,7 +76,7 @@ bool Asset::check() {
|
|||||||
// Comprueba si hay ficheros de ese tipo
|
// Comprueba si hay ficheros de ese tipo
|
||||||
bool any = false;
|
bool any = false;
|
||||||
|
|
||||||
for (auto f : fileList) {
|
for (const auto &f : fileList) {
|
||||||
if ((f.required) && (f.type == type)) {
|
if ((f.required) && (f.type == type)) {
|
||||||
any = true;
|
any = true;
|
||||||
}
|
}
|
||||||
@@ -69,7 +88,7 @@ bool Asset::check() {
|
|||||||
std::cout << "\n>> " << getTypeName(type).c_str() << " FILES" << std::endl;
|
std::cout << "\n>> " << getTypeName(type).c_str() << " FILES" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto f : fileList) {
|
for (const auto &f : fileList) {
|
||||||
if ((f.required) && (f.type == type)) {
|
if ((f.required) && (f.type == type)) {
|
||||||
success &= checkFile(f.file);
|
success &= checkFile(f.file);
|
||||||
}
|
}
|
||||||
@@ -92,18 +111,25 @@ bool Asset::check() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Comprueba que existe un fichero
|
// Comprueba que existe un fichero
|
||||||
bool Asset::checkFile(std::string path) {
|
bool Asset::checkFile(const std::string &path) {
|
||||||
bool success = false;
|
bool success = false;
|
||||||
std::string result = "ERROR";
|
std::string result = "ERROR";
|
||||||
|
|
||||||
// Comprueba si existe el fichero
|
// Comprueba si existe el fichero (pack o filesystem)
|
||||||
const std::string filename = path.substr(path.find_last_of("\\/") + 1);
|
const std::string filename = path.substr(path.find_last_of("\\/") + 1);
|
||||||
SDL_IOStream *file = SDL_IOFromFile(path.c_str(), "rb");
|
if (ResourceHelper::shouldUseResourcePack(path)) {
|
||||||
|
auto bytes = ResourceHelper::loadFile(path);
|
||||||
if (file != nullptr) {
|
if (!bytes.empty()) {
|
||||||
result = "OK";
|
result = "OK";
|
||||||
success = true;
|
success = true;
|
||||||
SDL_CloseIO(file);
|
}
|
||||||
|
} else {
|
||||||
|
SDL_IOStream *file = SDL_IOFromFile(path.c_str(), "rb");
|
||||||
|
if (file != nullptr) {
|
||||||
|
result = "OK";
|
||||||
|
success = true;
|
||||||
|
SDL_CloseIO(file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
@@ -18,15 +18,15 @@ enum assetType {
|
|||||||
|
|
||||||
// Clase Asset
|
// Clase Asset
|
||||||
class Asset {
|
class Asset {
|
||||||
private:
|
public:
|
||||||
// Estructura para definir un item
|
// Estructura para definir un item
|
||||||
struct item_t {
|
struct item_t {
|
||||||
std::string file; // Ruta del fichero desde la raiz del directorio
|
std::string file; // Ruta del fichero desde la raiz del directorio
|
||||||
enum assetType type; // Indica el tipo de recurso
|
enum assetType type; // Indica el tipo de recurso
|
||||||
bool required; // Indica si es un fichero que debe de existir
|
bool required; // Indica si es un fichero que debe de existir
|
||||||
// bool absolute; // Indica si la ruta que se ha proporcionado es una ruta absoluta
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
// Variables
|
// Variables
|
||||||
int longestName; // Contiene la longitud del nombre de fichero mas largo
|
int longestName; // Contiene la longitud del nombre de fichero mas largo
|
||||||
std::vector<item_t> fileList; // Listado con todas las rutas a los ficheros
|
std::vector<item_t> fileList; // Listado con todas las rutas a los ficheros
|
||||||
@@ -34,20 +34,31 @@ class Asset {
|
|||||||
bool verbose; // Indica si ha de mostrar información por pantalla
|
bool verbose; // Indica si ha de mostrar información por pantalla
|
||||||
|
|
||||||
// Comprueba que existe un fichero
|
// Comprueba que existe un fichero
|
||||||
bool checkFile(std::string executablePath);
|
bool checkFile(const std::string &executablePath);
|
||||||
|
|
||||||
// Devuelve el nombre del tipo de recurso
|
// Devuelve el nombre del tipo de recurso
|
||||||
std::string getTypeName(int type);
|
std::string getTypeName(int type);
|
||||||
|
|
||||||
|
// Constructor privado (usar Asset::init)
|
||||||
|
explicit Asset(const std::string &path);
|
||||||
|
|
||||||
|
// Instancia única
|
||||||
|
static Asset *instance;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Constructor
|
// Singleton API
|
||||||
Asset(std::string path);
|
static void init(const std::string &executablePath); // Crea la instancia
|
||||||
|
static void destroy(); // Libera la instancia
|
||||||
|
static auto get() -> Asset *; // Obtiene el puntero a la instancia
|
||||||
|
|
||||||
// Añade un elemento a la lista
|
// Añade un elemento a la lista
|
||||||
void add(std::string file, enum assetType type, bool required = true, bool absolute = false);
|
void add(const std::string &file, enum assetType type, bool required = true, bool absolute = false);
|
||||||
|
|
||||||
// Devuelve un elemento de la lista a partir de una cadena
|
// Devuelve un elemento de la lista a partir de una cadena
|
||||||
std::string get(std::string text);
|
std::string get(const std::string &text);
|
||||||
|
|
||||||
|
// Devuelve toda la lista de items registrados
|
||||||
|
const std::vector<item_t> &getAll() const { return fileList; }
|
||||||
|
|
||||||
// Comprueba que existen todos los elementos
|
// Comprueba que existen todos los elementos
|
||||||
bool check();
|
bool check();
|
||||||
219
source/core/resources/resource.cpp
Normal file
219
source/core/resources/resource.cpp
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
#include "core/resources/resource.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "core/audio/jail_audio.hpp"
|
||||||
|
#include "core/rendering/text.h"
|
||||||
|
#include "core/rendering/texture.h"
|
||||||
|
#include "core/resources/asset.h"
|
||||||
|
#include "core/resources/resource_helper.h"
|
||||||
|
#include "game/ui/menu.h"
|
||||||
|
|
||||||
|
// Nota: Asset::get() e Input::get() se consultan en preloadAll y al construir
|
||||||
|
// los menús; no se guardan punteros en el objeto Resource.
|
||||||
|
|
||||||
|
Resource *Resource::instance_ = nullptr;
|
||||||
|
|
||||||
|
static std::string basename(const std::string &path) {
|
||||||
|
return path.substr(path.find_last_of("\\/") + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string stem(const std::string &path) {
|
||||||
|
std::string b = basename(path);
|
||||||
|
size_t dot = b.find_last_of('.');
|
||||||
|
if (dot == std::string::npos) return b;
|
||||||
|
return b.substr(0, dot);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Resource::init(SDL_Renderer *renderer) {
|
||||||
|
if (instance_ == nullptr) {
|
||||||
|
instance_ = new Resource(renderer);
|
||||||
|
instance_->preloadAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Resource::destroy() {
|
||||||
|
delete instance_;
|
||||||
|
instance_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource *Resource::get() {
|
||||||
|
return instance_;
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource::Resource(SDL_Renderer *renderer)
|
||||||
|
: renderer_(renderer) {}
|
||||||
|
|
||||||
|
Resource::~Resource() {
|
||||||
|
for (auto &[name, m] : menus_) delete m;
|
||||||
|
menus_.clear();
|
||||||
|
|
||||||
|
for (auto &[name, t] : texts_) delete t;
|
||||||
|
texts_.clear();
|
||||||
|
|
||||||
|
for (auto &[name, t] : textures_) delete t;
|
||||||
|
textures_.clear();
|
||||||
|
|
||||||
|
for (auto &[name, s] : sounds_) JA_DeleteSound(s);
|
||||||
|
sounds_.clear();
|
||||||
|
|
||||||
|
for (auto &[name, m] : musics_) JA_DeleteMusic(m);
|
||||||
|
musics_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Resource::preloadAll() {
|
||||||
|
const auto &items = Asset::get()->getAll();
|
||||||
|
|
||||||
|
// Pass 1: texturas, sonidos, músicas, animaciones (raw lines), demo, lenguajes
|
||||||
|
for (const auto &it : items) {
|
||||||
|
if (!ResourceHelper::shouldUseResourcePack(it.file) && it.type != t_lang) {
|
||||||
|
// Ficheros absolutos (config.txt, score.bin, systemFolder) — no se precargan
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto bytes = ResourceHelper::loadFile(it.file);
|
||||||
|
if (bytes.empty()) continue;
|
||||||
|
|
||||||
|
const std::string bname = basename(it.file);
|
||||||
|
|
||||||
|
switch (it.type) {
|
||||||
|
case t_bitmap: {
|
||||||
|
auto *tex = new Texture(renderer_, bytes);
|
||||||
|
textures_[bname] = tex;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case t_sound: {
|
||||||
|
JA_Sound_t *s = JA_LoadSound(bytes.data(), (uint32_t)bytes.size());
|
||||||
|
if (s) sounds_[bname] = s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case t_music: {
|
||||||
|
JA_Music_t *m = JA_LoadMusic(bytes.data(), (Uint32)bytes.size());
|
||||||
|
if (m) musics_[bname] = m;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case t_data: {
|
||||||
|
if (bname.size() >= 4 && bname.substr(bname.size() - 4) == ".ani") {
|
||||||
|
std::string content(reinterpret_cast<const char *>(bytes.data()), bytes.size());
|
||||||
|
std::stringstream ss(content);
|
||||||
|
std::vector<std::string> lines;
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(ss, line)) {
|
||||||
|
lines.push_back(line);
|
||||||
|
}
|
||||||
|
animationLines_[bname] = std::move(lines);
|
||||||
|
} else if (bname == "demo.bin") {
|
||||||
|
demoBytes_ = bytes;
|
||||||
|
} else if (bname.size() >= 4 && bname.substr(bname.size() - 4) == ".men") {
|
||||||
|
// Menús: se construyen en pass 2 porque dependen de textos y sonidos
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case t_font:
|
||||||
|
// Fonts: se emparejan en pass 2
|
||||||
|
break;
|
||||||
|
case t_lang:
|
||||||
|
// Lenguaje: lo sigue leyendo la clase Lang a través de ResourceHelper
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass 2: Text (fuentes emparejadas png+txt) y Menus (dependen de Text+sonidos)
|
||||||
|
// Fuentes: construimos un Text por cada par basename.png + basename.txt
|
||||||
|
// Acumulamos los bytes encontrados por stem (basename sin ext.)
|
||||||
|
std::unordered_map<std::string, std::vector<uint8_t>> fontPngs;
|
||||||
|
std::unordered_map<std::string, std::vector<uint8_t>> fontTxts;
|
||||||
|
for (const auto &it : items) {
|
||||||
|
if (it.type != t_font) continue;
|
||||||
|
auto bytes = ResourceHelper::loadFile(it.file);
|
||||||
|
if (bytes.empty()) continue;
|
||||||
|
const std::string s = stem(it.file);
|
||||||
|
const std::string bname = basename(it.file);
|
||||||
|
if (bname.size() >= 4 && bname.substr(bname.size() - 4) == ".png") {
|
||||||
|
fontPngs[s] = std::move(bytes);
|
||||||
|
} else if (bname.size() >= 4 && bname.substr(bname.size() - 4) == ".txt") {
|
||||||
|
fontTxts[s] = std::move(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto &[s, png] : fontPngs) {
|
||||||
|
auto itTxt = fontTxts.find(s);
|
||||||
|
if (itTxt == fontTxts.end()) continue;
|
||||||
|
Text *t = new Text(png, itTxt->second, renderer_);
|
||||||
|
texts_[s] = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Menus: usan aún Menu::loadFromBytes que internamente llama a asset->get() y
|
||||||
|
// Text/JA_LoadSound por path. Funciona en modo fallback; en pack estricto
|
||||||
|
// requiere que Menu se adapte a cargar desde ResourceHelper. Por ahora
|
||||||
|
// lo dejamos así y será una migración del paso 7.
|
||||||
|
for (const auto &it : items) {
|
||||||
|
if (it.type != t_data) continue;
|
||||||
|
const std::string bname = basename(it.file);
|
||||||
|
if (bname.size() < 4 || bname.substr(bname.size() - 4) != ".men") continue;
|
||||||
|
auto bytes = ResourceHelper::loadFile(it.file);
|
||||||
|
if (bytes.empty()) continue;
|
||||||
|
Menu *m = new Menu(renderer_, "");
|
||||||
|
m->loadFromBytes(bytes, bname);
|
||||||
|
const std::string s = stem(it.file);
|
||||||
|
menus_[s] = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture *Resource::getTexture(const std::string &name) {
|
||||||
|
auto it = textures_.find(name);
|
||||||
|
if (it == textures_.end()) {
|
||||||
|
std::cerr << "Resource::getTexture: missing " << name << '\n';
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
JA_Sound_t *Resource::getSound(const std::string &name) {
|
||||||
|
auto it = sounds_.find(name);
|
||||||
|
if (it == sounds_.end()) {
|
||||||
|
std::cerr << "Resource::getSound: missing " << name << '\n';
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
JA_Music_t *Resource::getMusic(const std::string &name) {
|
||||||
|
auto it = musics_.find(name);
|
||||||
|
if (it == musics_.end()) {
|
||||||
|
std::cerr << "Resource::getMusic: missing " << name << '\n';
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> &Resource::getAnimationLines(const std::string &name) {
|
||||||
|
auto it = animationLines_.find(name);
|
||||||
|
if (it == animationLines_.end()) {
|
||||||
|
static std::vector<std::string> empty;
|
||||||
|
std::cerr << "Resource::getAnimationLines: missing " << name << '\n';
|
||||||
|
return empty;
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text *Resource::getText(const std::string &name) {
|
||||||
|
auto it = texts_.find(name);
|
||||||
|
if (it == texts_.end()) {
|
||||||
|
std::cerr << "Resource::getText: missing " << name << '\n';
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu *Resource::getMenu(const std::string &name) {
|
||||||
|
auto it = menus_.find(name);
|
||||||
|
if (it == menus_.end()) {
|
||||||
|
std::cerr << "Resource::getMenu: missing " << name << '\n';
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
49
source/core/resources/resource.h
Normal file
49
source/core/resources/resource.h
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class Menu;
|
||||||
|
class Text;
|
||||||
|
class Texture;
|
||||||
|
struct JA_Music_t;
|
||||||
|
struct JA_Sound_t;
|
||||||
|
|
||||||
|
// Precarga y posee todos los recursos del juego durante toda la vida de la app.
|
||||||
|
// Singleton inicializado desde Director; las escenas consultan handles via get*().
|
||||||
|
class Resource {
|
||||||
|
public:
|
||||||
|
static void init(SDL_Renderer *renderer);
|
||||||
|
static void destroy();
|
||||||
|
static Resource *get();
|
||||||
|
|
||||||
|
Texture *getTexture(const std::string &name);
|
||||||
|
JA_Sound_t *getSound(const std::string &name);
|
||||||
|
JA_Music_t *getMusic(const std::string &name);
|
||||||
|
std::vector<std::string> &getAnimationLines(const std::string &name);
|
||||||
|
Text *getText(const std::string &name); // name sin extensión: "smb2", "nokia2", ...
|
||||||
|
Menu *getMenu(const std::string &name); // name sin extensión: "title", "options", ...
|
||||||
|
const std::vector<uint8_t> &getDemoBytes() const { return demoBytes_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit Resource(SDL_Renderer *renderer);
|
||||||
|
~Resource();
|
||||||
|
|
||||||
|
void preloadAll();
|
||||||
|
|
||||||
|
SDL_Renderer *renderer_;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, Texture *> textures_;
|
||||||
|
std::unordered_map<std::string, JA_Sound_t *> sounds_;
|
||||||
|
std::unordered_map<std::string, JA_Music_t *> musics_;
|
||||||
|
std::unordered_map<std::string, std::vector<std::string>> animationLines_;
|
||||||
|
std::unordered_map<std::string, Text *> texts_;
|
||||||
|
std::unordered_map<std::string, Menu *> menus_;
|
||||||
|
std::vector<uint8_t> demoBytes_;
|
||||||
|
|
||||||
|
static Resource *instance_;
|
||||||
|
};
|
||||||
77
source/core/resources/resource_helper.cpp
Normal file
77
source/core/resources/resource_helper.cpp
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#include "core/resources/resource_helper.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "core/resources/resource_loader.h"
|
||||||
|
|
||||||
|
namespace ResourceHelper {
|
||||||
|
static bool resource_system_initialized = false;
|
||||||
|
|
||||||
|
bool initializeResourceSystem(const std::string& pack_file, bool enable_fallback) {
|
||||||
|
auto& loader = ResourceLoader::getInstance();
|
||||||
|
bool ok = loader.initialize(pack_file, enable_fallback);
|
||||||
|
resource_system_initialized = ok;
|
||||||
|
|
||||||
|
if (ok && loader.getLoadedResourceCount() > 0) {
|
||||||
|
std::cout << "Resource system initialized with pack: " << pack_file << '\n';
|
||||||
|
} else if (ok) {
|
||||||
|
std::cout << "Resource system using fallback mode (filesystem only)" << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
void shutdownResourceSystem() {
|
||||||
|
if (resource_system_initialized) {
|
||||||
|
ResourceLoader::getInstance().shutdown();
|
||||||
|
resource_system_initialized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> loadFile(const std::string& filepath) {
|
||||||
|
if (resource_system_initialized && shouldUseResourcePack(filepath)) {
|
||||||
|
auto& loader = ResourceLoader::getInstance();
|
||||||
|
std::string pack_path = getPackPath(filepath);
|
||||||
|
|
||||||
|
auto data = loader.loadResource(pack_path);
|
||||||
|
if (!data.empty()) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
|
||||||
|
if (!file) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::streamsize file_size = file.tellg();
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
std::vector<uint8_t> data(file_size);
|
||||||
|
if (!file.read(reinterpret_cast<char*>(data.data()), file_size)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shouldUseResourcePack(const std::string& filepath) {
|
||||||
|
// Solo entran al pack los ficheros dentro de data/
|
||||||
|
return filepath.find("data/") != std::string::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getPackPath(const std::string& asset_path) {
|
||||||
|
std::string pack_path = asset_path;
|
||||||
|
std::replace(pack_path.begin(), pack_path.end(), '\\', '/');
|
||||||
|
|
||||||
|
// Toma la última aparición de "data/" como prefijo a quitar
|
||||||
|
size_t last_data = pack_path.rfind("data/");
|
||||||
|
if (last_data != std::string::npos) {
|
||||||
|
pack_path = pack_path.substr(last_data + 5);
|
||||||
|
}
|
||||||
|
return pack_path;
|
||||||
|
}
|
||||||
|
} // namespace ResourceHelper
|
||||||
15
source/core/resources/resource_helper.h
Normal file
15
source/core/resources/resource_helper.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace ResourceHelper {
|
||||||
|
bool initializeResourceSystem(const std::string& pack_file = "resources.pack", bool enable_fallback = true);
|
||||||
|
void shutdownResourceSystem();
|
||||||
|
|
||||||
|
std::vector<uint8_t> loadFile(const std::string& filepath);
|
||||||
|
|
||||||
|
bool shouldUseResourcePack(const std::string& filepath);
|
||||||
|
std::string getPackPath(const std::string& asset_path);
|
||||||
|
} // namespace ResourceHelper
|
||||||
133
source/core/resources/resource_loader.cpp
Normal file
133
source/core/resources/resource_loader.cpp
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
#include "core/resources/resource_loader.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "core/resources/resource_pack.h"
|
||||||
|
|
||||||
|
std::unique_ptr<ResourceLoader> ResourceLoader::instance = nullptr;
|
||||||
|
|
||||||
|
ResourceLoader::ResourceLoader()
|
||||||
|
: resource_pack_(nullptr),
|
||||||
|
fallback_to_files_(true) {}
|
||||||
|
|
||||||
|
ResourceLoader& ResourceLoader::getInstance() {
|
||||||
|
if (!instance) {
|
||||||
|
instance = std::unique_ptr<ResourceLoader>(new ResourceLoader());
|
||||||
|
}
|
||||||
|
return *instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceLoader::~ResourceLoader() {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourceLoader::initialize(const std::string& pack_file, bool enable_fallback) {
|
||||||
|
shutdown();
|
||||||
|
|
||||||
|
fallback_to_files_ = enable_fallback;
|
||||||
|
pack_path_ = pack_file;
|
||||||
|
|
||||||
|
if (std::filesystem::exists(pack_file)) {
|
||||||
|
resource_pack_ = new ResourcePack();
|
||||||
|
if (resource_pack_->loadPack(pack_file)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
delete resource_pack_;
|
||||||
|
resource_pack_ = nullptr;
|
||||||
|
std::cerr << "Failed to load resource pack: " << pack_file << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fallback_to_files_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "Resource pack not found and fallback disabled: " << pack_file << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceLoader::shutdown() {
|
||||||
|
if (resource_pack_ != nullptr) {
|
||||||
|
delete resource_pack_;
|
||||||
|
resource_pack_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> ResourceLoader::loadResource(const std::string& filename) {
|
||||||
|
if ((resource_pack_ != nullptr) && resource_pack_->hasResource(filename)) {
|
||||||
|
return resource_pack_->getResource(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fallback_to_files_) {
|
||||||
|
return loadFromFile(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "Resource not found: " << filename << '\n';
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourceLoader::resourceExists(const std::string& filename) {
|
||||||
|
if ((resource_pack_ != nullptr) && resource_pack_->hasResource(filename)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fallback_to_files_) {
|
||||||
|
std::string full_path = getDataPath(filename);
|
||||||
|
return std::filesystem::exists(full_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> ResourceLoader::loadFromFile(const std::string& filename) {
|
||||||
|
std::string full_path = getDataPath(filename);
|
||||||
|
|
||||||
|
std::ifstream file(full_path, std::ios::binary | std::ios::ate);
|
||||||
|
if (!file) {
|
||||||
|
std::cerr << "Error: Could not open file: " << full_path << '\n';
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::streamsize file_size = file.tellg();
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
std::vector<uint8_t> data(file_size);
|
||||||
|
if (!file.read(reinterpret_cast<char*>(data.data()), file_size)) {
|
||||||
|
std::cerr << "Error: Could not read file: " << full_path << '\n';
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ResourceLoader::getDataPath(const std::string& filename) {
|
||||||
|
return "data/" + filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ResourceLoader::getLoadedResourceCount() const {
|
||||||
|
if (resource_pack_ != nullptr) {
|
||||||
|
return resource_pack_->getResourceCount();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> ResourceLoader::getAvailableResources() const {
|
||||||
|
if (resource_pack_ != nullptr) {
|
||||||
|
return resource_pack_->getResourceList();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> result;
|
||||||
|
if (fallback_to_files_ && std::filesystem::exists("data")) {
|
||||||
|
for (const auto& entry : std::filesystem::recursive_directory_iterator("data")) {
|
||||||
|
if (entry.is_regular_file()) {
|
||||||
|
std::string filename = std::filesystem::relative(entry.path(), "data").string();
|
||||||
|
std::replace(filename.begin(), filename.end(), '\\', '/');
|
||||||
|
result.push_back(filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
39
source/core/resources/resource_loader.h
Normal file
39
source/core/resources/resource_loader.h
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class ResourcePack;
|
||||||
|
|
||||||
|
class ResourceLoader {
|
||||||
|
private:
|
||||||
|
static std::unique_ptr<ResourceLoader> instance;
|
||||||
|
ResourcePack* resource_pack_;
|
||||||
|
std::string pack_path_;
|
||||||
|
bool fallback_to_files_;
|
||||||
|
|
||||||
|
ResourceLoader();
|
||||||
|
|
||||||
|
public:
|
||||||
|
static ResourceLoader& getInstance();
|
||||||
|
~ResourceLoader();
|
||||||
|
|
||||||
|
bool initialize(const std::string& pack_file, bool enable_fallback = true);
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
std::vector<uint8_t> loadResource(const std::string& filename);
|
||||||
|
bool resourceExists(const std::string& filename);
|
||||||
|
|
||||||
|
void setFallbackToFiles(bool enable) { fallback_to_files_ = enable; }
|
||||||
|
bool getFallbackToFiles() const { return fallback_to_files_; }
|
||||||
|
|
||||||
|
size_t getLoadedResourceCount() const;
|
||||||
|
std::vector<std::string> getAvailableResources() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::vector<uint8_t> loadFromFile(const std::string& filename);
|
||||||
|
static std::string getDataPath(const std::string& filename);
|
||||||
|
};
|
||||||
221
source/core/resources/resource_pack.cpp
Normal file
221
source/core/resources/resource_pack.cpp
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
#include "core/resources/resource_pack.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <numeric>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
const std::string ResourcePack::DEFAULT_ENCRYPT_KEY = "CCRS_RESOURCES__2026";
|
||||||
|
|
||||||
|
ResourcePack::ResourcePack()
|
||||||
|
: loaded_(false) {}
|
||||||
|
|
||||||
|
ResourcePack::~ResourcePack() {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ResourcePack::calculateChecksum(const std::vector<uint8_t>& data) {
|
||||||
|
return std::accumulate(data.begin(), data.end(), uint32_t(0x12345678), [](uint32_t acc, uint8_t b) { return ((acc << 5) + acc) + b; });
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcePack::encryptData(std::vector<uint8_t>& data, const std::string& key) {
|
||||||
|
if (key.empty()) return;
|
||||||
|
for (size_t i = 0; i < data.size(); ++i) {
|
||||||
|
data[i] ^= key[i % key.length()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcePack::decryptData(std::vector<uint8_t>& data, const std::string& key) {
|
||||||
|
encryptData(data, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourcePack::loadPack(const std::string& pack_file) {
|
||||||
|
std::ifstream file(pack_file, std::ios::binary);
|
||||||
|
if (!file) {
|
||||||
|
std::cerr << "Error: Could not open pack file: " << pack_file << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<char, 4> header;
|
||||||
|
file.read(header.data(), 4);
|
||||||
|
if (std::string(header.data(), 4) != "CCRS") {
|
||||||
|
std::cerr << "Error: Invalid pack file format" << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t version;
|
||||||
|
file.read(reinterpret_cast<char*>(&version), sizeof(version));
|
||||||
|
if (version != 1) {
|
||||||
|
std::cerr << "Error: Unsupported pack version: " << version << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t resource_count;
|
||||||
|
file.read(reinterpret_cast<char*>(&resource_count), sizeof(resource_count));
|
||||||
|
|
||||||
|
resources_.clear();
|
||||||
|
resources_.reserve(resource_count);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < resource_count; ++i) {
|
||||||
|
uint32_t filename_length;
|
||||||
|
file.read(reinterpret_cast<char*>(&filename_length), sizeof(filename_length));
|
||||||
|
|
||||||
|
std::string filename(filename_length, '\0');
|
||||||
|
file.read(filename.data(), filename_length);
|
||||||
|
|
||||||
|
ResourceEntry entry;
|
||||||
|
entry.filename = filename;
|
||||||
|
file.read(reinterpret_cast<char*>(&entry.offset), sizeof(entry.offset));
|
||||||
|
file.read(reinterpret_cast<char*>(&entry.size), sizeof(entry.size));
|
||||||
|
file.read(reinterpret_cast<char*>(&entry.checksum), sizeof(entry.checksum));
|
||||||
|
|
||||||
|
resources_[filename] = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t data_size;
|
||||||
|
file.read(reinterpret_cast<char*>(&data_size), sizeof(data_size));
|
||||||
|
|
||||||
|
data_.resize(data_size);
|
||||||
|
file.read(reinterpret_cast<char*>(data_.data()), data_size);
|
||||||
|
|
||||||
|
decryptData(data_, DEFAULT_ENCRYPT_KEY);
|
||||||
|
|
||||||
|
loaded_ = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourcePack::savePack(const std::string& pack_file) {
|
||||||
|
std::ofstream file(pack_file, std::ios::binary);
|
||||||
|
if (!file) {
|
||||||
|
std::cerr << "Error: Could not create pack file: " << pack_file << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.write("CCRS", 4);
|
||||||
|
|
||||||
|
uint32_t version = 1;
|
||||||
|
file.write(reinterpret_cast<const char*>(&version), sizeof(version));
|
||||||
|
|
||||||
|
uint32_t resource_count = static_cast<uint32_t>(resources_.size());
|
||||||
|
file.write(reinterpret_cast<const char*>(&resource_count), sizeof(resource_count));
|
||||||
|
|
||||||
|
for (const auto& [filename, entry] : resources_) {
|
||||||
|
uint32_t filename_length = static_cast<uint32_t>(filename.length());
|
||||||
|
file.write(reinterpret_cast<const char*>(&filename_length), sizeof(filename_length));
|
||||||
|
file.write(filename.c_str(), filename_length);
|
||||||
|
|
||||||
|
file.write(reinterpret_cast<const char*>(&entry.offset), sizeof(entry.offset));
|
||||||
|
file.write(reinterpret_cast<const char*>(&entry.size), sizeof(entry.size));
|
||||||
|
file.write(reinterpret_cast<const char*>(&entry.checksum), sizeof(entry.checksum));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> encrypted_data = data_;
|
||||||
|
encryptData(encrypted_data, DEFAULT_ENCRYPT_KEY);
|
||||||
|
|
||||||
|
uint64_t data_size = encrypted_data.size();
|
||||||
|
file.write(reinterpret_cast<const char*>(&data_size), sizeof(data_size));
|
||||||
|
file.write(reinterpret_cast<const char*>(encrypted_data.data()), data_size);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourcePack::addFile(const std::string& filename, const std::string& filepath) {
|
||||||
|
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
|
||||||
|
if (!file) {
|
||||||
|
std::cerr << "Error: Could not open file: " << filepath << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::streamsize file_size = file.tellg();
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
std::vector<uint8_t> file_data(file_size);
|
||||||
|
if (!file.read(reinterpret_cast<char*>(file_data.data()), file_size)) {
|
||||||
|
std::cerr << "Error: Could not read file: " << filepath << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceEntry entry;
|
||||||
|
entry.filename = filename;
|
||||||
|
entry.offset = data_.size();
|
||||||
|
entry.size = file_data.size();
|
||||||
|
entry.checksum = calculateChecksum(file_data);
|
||||||
|
|
||||||
|
data_.insert(data_.end(), file_data.begin(), file_data.end());
|
||||||
|
resources_[filename] = entry;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourcePack::addDirectory(const std::string& directory) {
|
||||||
|
if (!std::filesystem::exists(directory)) {
|
||||||
|
std::cerr << "Error: Directory does not exist: " << directory << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cppcheck-suppress useStlAlgorithm
|
||||||
|
for (const auto& entry : std::filesystem::recursive_directory_iterator(directory)) {
|
||||||
|
if (entry.is_regular_file()) {
|
||||||
|
std::string filepath = entry.path().string();
|
||||||
|
std::string filename = std::filesystem::relative(entry.path(), directory).string();
|
||||||
|
|
||||||
|
std::replace(filename.begin(), filename.end(), '\\', '/');
|
||||||
|
|
||||||
|
if (!addFile(filename, filepath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> ResourcePack::getResource(const std::string& filename) {
|
||||||
|
auto it = resources_.find(filename);
|
||||||
|
if (it == resources_.end()) {
|
||||||
|
std::cerr << "Error: Resource not found: " << filename << '\n';
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResourceEntry& entry = it->second;
|
||||||
|
if (entry.offset + entry.size > data_.size()) {
|
||||||
|
std::cerr << "Error: Invalid resource data: " << filename << '\n';
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> result(data_.begin() + entry.offset,
|
||||||
|
data_.begin() + entry.offset + entry.size);
|
||||||
|
|
||||||
|
uint32_t checksum = calculateChecksum(result);
|
||||||
|
if (checksum != entry.checksum) {
|
||||||
|
std::cerr << "Warning: Checksum mismatch for resource: " << filename << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourcePack::hasResource(const std::string& filename) const {
|
||||||
|
return resources_.find(filename) != resources_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcePack::clear() {
|
||||||
|
resources_.clear();
|
||||||
|
data_.clear();
|
||||||
|
loaded_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ResourcePack::getResourceCount() const {
|
||||||
|
return resources_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> ResourcePack::getResourceList() const {
|
||||||
|
std::vector<std::string> result;
|
||||||
|
result.reserve(resources_.size());
|
||||||
|
for (const auto& [filename, entry] : resources_) {
|
||||||
|
result.push_back(filename);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
44
source/core/resources/resource_pack.h
Normal file
44
source/core/resources/resource_pack.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct ResourceEntry {
|
||||||
|
std::string filename;
|
||||||
|
uint64_t offset;
|
||||||
|
uint64_t size;
|
||||||
|
uint32_t checksum;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ResourcePack {
|
||||||
|
private:
|
||||||
|
std::unordered_map<std::string, ResourceEntry> resources_;
|
||||||
|
std::vector<uint8_t> data_;
|
||||||
|
bool loaded_;
|
||||||
|
|
||||||
|
static uint32_t calculateChecksum(const std::vector<uint8_t>& data);
|
||||||
|
static void encryptData(std::vector<uint8_t>& data, const std::string& key);
|
||||||
|
static void decryptData(std::vector<uint8_t>& data, const std::string& key);
|
||||||
|
|
||||||
|
public:
|
||||||
|
ResourcePack();
|
||||||
|
~ResourcePack();
|
||||||
|
|
||||||
|
bool loadPack(const std::string& pack_file);
|
||||||
|
bool savePack(const std::string& pack_file);
|
||||||
|
|
||||||
|
bool addFile(const std::string& filename, const std::string& filepath);
|
||||||
|
bool addDirectory(const std::string& directory);
|
||||||
|
|
||||||
|
std::vector<uint8_t> getResource(const std::string& filename);
|
||||||
|
bool hasResource(const std::string& filename) const;
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
size_t getResourceCount() const;
|
||||||
|
std::vector<std::string> getResourceList() const;
|
||||||
|
|
||||||
|
static const std::string DEFAULT_ENCRYPT_KEY;
|
||||||
|
};
|
||||||
654
source/core/system/director.cpp
Normal file
654
source/core/system/director.cpp
Normal file
@@ -0,0 +1,654 @@
|
|||||||
|
#include "core/system/director.h"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <errno.h> // for errno, EEXIST, EACCES, ENAMETOO...
|
||||||
|
#include <stdio.h> // for printf, perror
|
||||||
|
#include <string.h> // for strcmp
|
||||||
|
#ifndef __EMSCRIPTEN__
|
||||||
|
#include <sys/stat.h> // for mkdir, stat, S_IRWXU
|
||||||
|
#include <unistd.h> // for getuid
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <cstdlib> // for exit, EXIT_FAILURE, srand
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream> // for basic_ostream, operator<<, basi...
|
||||||
|
#include <iostream> // for cout
|
||||||
|
#include <memory>
|
||||||
|
#include <string> // for basic_string, operator+, char_t...
|
||||||
|
#include <vector> // for vector
|
||||||
|
|
||||||
|
#include "core/audio/audio.hpp" // for Audio::init, Audio::destroy
|
||||||
|
#include "core/input/input.h" // for Input, inputs_e, INPUT_USE_GAME...
|
||||||
|
#include "core/input/mouse.hpp" // for Mouse::handleEvent, Mouse::upda...
|
||||||
|
#include "core/locale/lang.h" // for Lang, MAX_LANGUAGES, ba_BA, en_UK
|
||||||
|
#include "core/rendering/screen.h" // for Screen
|
||||||
|
#include "core/rendering/texture.h" // for Texture
|
||||||
|
#include "core/resources/asset.h" // for Asset, assetType
|
||||||
|
#include "core/resources/resource.h"
|
||||||
|
#include "core/resources/resource_helper.h"
|
||||||
|
#include "game/defaults.hpp" // for SECTION_PROG_LOGO, GAMECANVAS_H...
|
||||||
|
#include "game/game.h" // for Game
|
||||||
|
#include "game/options.hpp" // for Options::init, loadFromFile...
|
||||||
|
#include "game/scenes/intro.h" // for Intro
|
||||||
|
#include "game/scenes/logo.h" // for Logo
|
||||||
|
#include "game/scenes/title.h" // for Title
|
||||||
|
#include "utils/utils.h" // for input_t, boolToString
|
||||||
|
|
||||||
|
#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)
|
||||||
|
#include <pwd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
Director::Director(int argc, const char *argv[]) {
|
||||||
|
std::cout << "Game start" << std::endl;
|
||||||
|
// Inicializa variables
|
||||||
|
section = new section_t();
|
||||||
|
section->name = SECTION_PROG_LOGO;
|
||||||
|
|
||||||
|
// Inicializa las opciones del programa (defaults + dispositivos d'entrada)
|
||||||
|
Options::init();
|
||||||
|
|
||||||
|
// Comprueba los parametros del programa (pot activar console)
|
||||||
|
checkProgramArguments(argc, argv);
|
||||||
|
|
||||||
|
// Crea la carpeta del sistema donde guardar datos
|
||||||
|
createSystemFolder("jailgames");
|
||||||
|
#ifndef DEBUG
|
||||||
|
createSystemFolder("jailgames/coffee_crisis");
|
||||||
|
#else
|
||||||
|
createSystemFolder("jailgames/coffee_crisis_debug");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Estableix el fitxer de configuració i carrega les opcions (o crea el
|
||||||
|
// YAML amb defaults si no existeix).
|
||||||
|
Options::setConfigFile(systemFolder + "/config.yaml");
|
||||||
|
Options::loadFromFile();
|
||||||
|
|
||||||
|
// Presets de shaders (creats amb defaults si no existeixen).
|
||||||
|
Options::setPostFXFile(systemFolder + "/postfx.yaml");
|
||||||
|
Options::loadPostFXFromFile();
|
||||||
|
Options::setCrtPiFile(systemFolder + "/crtpi.yaml");
|
||||||
|
Options::loadCrtPiFromFile();
|
||||||
|
|
||||||
|
// Inicializa el sistema de recursos (pack + fallback).
|
||||||
|
// En wasm siempre se usa filesystem (MEMFS) porque el propio --preload-file
|
||||||
|
// de emscripten ya empaqueta data/ — no hay resources.pack.
|
||||||
|
{
|
||||||
|
#if defined(__EMSCRIPTEN__)
|
||||||
|
const bool enable_fallback = true;
|
||||||
|
#elif defined(RELEASE_BUILD)
|
||||||
|
const bool enable_fallback = false;
|
||||||
|
#else
|
||||||
|
const bool enable_fallback = true;
|
||||||
|
#endif
|
||||||
|
if (!ResourceHelper::initializeResourceSystem("resources.pack", enable_fallback)) {
|
||||||
|
std::cerr << "Fatal: resource system init failed (missing resources.pack?)" << std::endl;
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crea el objeto que controla los ficheros de recursos
|
||||||
|
Asset::init(executablePath);
|
||||||
|
Asset::get()->setVerbose(Options::settings.console);
|
||||||
|
|
||||||
|
// Si falta algún fichero no inicia el programa
|
||||||
|
if (!setFileList()) {
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inicializa SDL
|
||||||
|
initSDL();
|
||||||
|
|
||||||
|
// Inicializa JailAudio
|
||||||
|
initJailAudio();
|
||||||
|
|
||||||
|
// Establece el modo de escalado de texturas
|
||||||
|
Texture::setGlobalScaleMode(Options::video.scale_mode);
|
||||||
|
|
||||||
|
// Crea los objetos
|
||||||
|
Lang::init();
|
||||||
|
Lang::get()->setLang(Options::settings.language);
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
Input::init("/gamecontrollerdb.txt");
|
||||||
|
#else
|
||||||
|
{
|
||||||
|
const std::string binDir = std::filesystem::path(executablePath).parent_path().string();
|
||||||
|
#ifdef MACOS_BUNDLE
|
||||||
|
Input::init(binDir + "/../Resources/gamecontrollerdb.txt");
|
||||||
|
#else
|
||||||
|
Input::init(binDir + "/gamecontrollerdb.txt");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
initInput();
|
||||||
|
|
||||||
|
// Orden importante: Screen + initShaders ANTES de Resource::init.
|
||||||
|
// Si `Resource::init` se ejecuta primero, carga ~100 texturas vía
|
||||||
|
// `SDL_CreateTexture` que dejan el SDL_Renderer con el swapchain en un
|
||||||
|
// estado que hace crashear al driver Vulkan cuando después `initShaders`
|
||||||
|
// intenta reclamar la ventana para el dispositivo SDL3 GPU.
|
||||||
|
//
|
||||||
|
// Por eso el constructor de Screen NO carga notificationText desde
|
||||||
|
// Resource; se enlaza después vía `Screen::get()->initNotifications()`.
|
||||||
|
Screen::init(window, renderer);
|
||||||
|
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
if (Options::video.gpu.acceleration) {
|
||||||
|
Screen::get()->initShaders();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Ahora sí, precarga todos los recursos en memoria (texturas, sonidos,
|
||||||
|
// música, ...). Vivirán durante toda la vida de la app.
|
||||||
|
Resource::init(renderer);
|
||||||
|
|
||||||
|
// Completa el enlazado de Screen con recursos que necesitan Resource
|
||||||
|
// inicializado (actualmente sólo el Text de las notificaciones).
|
||||||
|
Screen::get()->initNotifications();
|
||||||
|
|
||||||
|
activeSection = ActiveSection::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Director::~Director() {
|
||||||
|
Options::saveToFile();
|
||||||
|
|
||||||
|
// Libera las secciones primero: sus destructores tocan audio/render SDL
|
||||||
|
// (p.ej. Intro::~Intro llama a JA_DeleteMusic) y deben ejecutarse antes
|
||||||
|
// de SDL_Quit().
|
||||||
|
logo.reset();
|
||||||
|
intro.reset();
|
||||||
|
title.reset();
|
||||||
|
game.reset();
|
||||||
|
|
||||||
|
// Screen puede tener referencias a Text propiedad de Resource: destruir
|
||||||
|
// Screen antes que Resource.
|
||||||
|
Screen::destroy();
|
||||||
|
|
||||||
|
// Libera todos los recursos precargados antes de cerrar SDL.
|
||||||
|
Resource::destroy();
|
||||||
|
|
||||||
|
Asset::destroy();
|
||||||
|
Input::destroy();
|
||||||
|
Lang::destroy();
|
||||||
|
delete section;
|
||||||
|
|
||||||
|
Audio::destroy();
|
||||||
|
|
||||||
|
SDL_DestroyRenderer(renderer);
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
|
||||||
|
SDL_Quit();
|
||||||
|
|
||||||
|
ResourceHelper::shutdownResourceSystem();
|
||||||
|
|
||||||
|
std::cout << "\nBye!" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inicializa el objeto input
|
||||||
|
void Director::initInput() {
|
||||||
|
// Establece si ha de mostrar mensajes
|
||||||
|
Input::get()->setVerbose(Options::settings.console);
|
||||||
|
|
||||||
|
// Busca si hay un mando conectado
|
||||||
|
Input::get()->discoverGameController();
|
||||||
|
|
||||||
|
// Teclado - Movimiento del jugador
|
||||||
|
Input::get()->bindKey(input_up, SDL_SCANCODE_UP);
|
||||||
|
Input::get()->bindKey(input_down, SDL_SCANCODE_DOWN);
|
||||||
|
Input::get()->bindKey(input_left, SDL_SCANCODE_LEFT);
|
||||||
|
Input::get()->bindKey(input_right, SDL_SCANCODE_RIGHT);
|
||||||
|
Input::get()->bindKey(input_fire_left, SDL_SCANCODE_Q);
|
||||||
|
Input::get()->bindKey(input_fire_center, SDL_SCANCODE_W);
|
||||||
|
Input::get()->bindKey(input_fire_right, SDL_SCANCODE_E);
|
||||||
|
|
||||||
|
// Teclado - Otros
|
||||||
|
Input::get()->bindKey(input_accept, SDL_SCANCODE_RETURN);
|
||||||
|
Input::get()->bindKey(input_cancel, SDL_SCANCODE_ESCAPE);
|
||||||
|
Input::get()->bindKey(input_pause, SDL_SCANCODE_ESCAPE);
|
||||||
|
Input::get()->bindKey(input_exit, SDL_SCANCODE_ESCAPE);
|
||||||
|
Input::get()->bindKey(input_window_dec_size, SDL_SCANCODE_F1);
|
||||||
|
Input::get()->bindKey(input_window_inc_size, SDL_SCANCODE_F2);
|
||||||
|
Input::get()->bindKey(input_window_fullscreen, SDL_SCANCODE_F3);
|
||||||
|
Input::get()->bindKey(input_prev_preset, SDL_SCANCODE_F8);
|
||||||
|
Input::get()->bindKey(input_next_preset, SDL_SCANCODE_F9);
|
||||||
|
Input::get()->bindKey(input_toggle_shader, SDL_SCANCODE_F10);
|
||||||
|
Input::get()->bindKey(input_toggle_shader_type, SDL_SCANCODE_F11);
|
||||||
|
|
||||||
|
// Mando - Movimiento del jugador
|
||||||
|
Input::get()->bindGameControllerButton(input_up, SDL_GAMEPAD_BUTTON_DPAD_UP);
|
||||||
|
Input::get()->bindGameControllerButton(input_down, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
|
||||||
|
Input::get()->bindGameControllerButton(input_left, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
|
||||||
|
Input::get()->bindGameControllerButton(input_right, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
|
||||||
|
Input::get()->bindGameControllerButton(input_fire_left, SDL_GAMEPAD_BUTTON_WEST);
|
||||||
|
Input::get()->bindGameControllerButton(input_fire_center, SDL_GAMEPAD_BUTTON_NORTH);
|
||||||
|
Input::get()->bindGameControllerButton(input_fire_right, SDL_GAMEPAD_BUTTON_EAST);
|
||||||
|
|
||||||
|
// Mando - Otros
|
||||||
|
// SOUTH queda sin asignar para evitar salidas accidentales: pausa/cancel se hace con START/BACK.
|
||||||
|
Input::get()->bindGameControllerButton(input_accept, SDL_GAMEPAD_BUTTON_EAST);
|
||||||
|
#ifdef GAME_CONSOLE
|
||||||
|
Input::get()->bindGameControllerButton(input_pause, SDL_GAMEPAD_BUTTON_BACK);
|
||||||
|
Input::get()->bindGameControllerButton(input_exit, SDL_GAMEPAD_BUTTON_START);
|
||||||
|
#else
|
||||||
|
Input::get()->bindGameControllerButton(input_pause, SDL_GAMEPAD_BUTTON_START);
|
||||||
|
Input::get()->bindGameControllerButton(input_exit, SDL_GAMEPAD_BUTTON_BACK);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inicializa JailAudio
|
||||||
|
void Director::initJailAudio() {
|
||||||
|
Audio::init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arranca SDL y crea la ventana
|
||||||
|
bool Director::initSDL() {
|
||||||
|
// Indicador de éxito
|
||||||
|
bool success = true;
|
||||||
|
|
||||||
|
// Inicializa SDL
|
||||||
|
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD)) {
|
||||||
|
if (Options::settings.console) {
|
||||||
|
std::cout << "SDL could not initialize!\nSDL Error: " << SDL_GetError() << std::endl;
|
||||||
|
}
|
||||||
|
success = false;
|
||||||
|
} else {
|
||||||
|
// Inicia el generador de numeros aleatorios
|
||||||
|
std::srand(static_cast<unsigned int>(SDL_GetTicks()));
|
||||||
|
|
||||||
|
// Crea la ventana
|
||||||
|
window = SDL_CreateWindow(
|
||||||
|
Options::window.caption.c_str(),
|
||||||
|
GAMECANVAS_WIDTH * Options::window.zoom,
|
||||||
|
GAMECANVAS_HEIGHT * Options::window.zoom,
|
||||||
|
0);
|
||||||
|
if (window == nullptr) {
|
||||||
|
if (Options::settings.console) {
|
||||||
|
std::cout << "Window could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
|
||||||
|
}
|
||||||
|
success = false;
|
||||||
|
} else {
|
||||||
|
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||||
|
|
||||||
|
// Crea un renderizador para la ventana
|
||||||
|
renderer = SDL_CreateRenderer(window, NULL);
|
||||||
|
|
||||||
|
if (renderer == nullptr) {
|
||||||
|
if (Options::settings.console) {
|
||||||
|
std::cout << "Renderer could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
|
||||||
|
}
|
||||||
|
success = false;
|
||||||
|
} else {
|
||||||
|
// Modo de blending por defecto (consistente con CCAE):
|
||||||
|
// permite alpha blending para fades y notificaciones.
|
||||||
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||||
|
|
||||||
|
// Activa vsync si es necesario
|
||||||
|
if (Options::video.vsync) {
|
||||||
|
SDL_SetRenderVSync(renderer, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inicializa el color de renderizado
|
||||||
|
SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);
|
||||||
|
|
||||||
|
// Establece el tamaño del buffer de renderizado
|
||||||
|
SDL_SetRenderLogicalPresentation(renderer, GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||||
|
|
||||||
|
// Establece el modo de mezcla
|
||||||
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Options::settings.console) {
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crea el indice de ficheros
|
||||||
|
bool Director::setFileList() {
|
||||||
|
#ifdef MACOS_BUNDLE
|
||||||
|
const std::string prefix = "/../Resources";
|
||||||
|
#else
|
||||||
|
const std::string prefix = "";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Ficheros de configuración
|
||||||
|
Asset::get()->add(systemFolder + "/score.bin", t_data, false, true);
|
||||||
|
Asset::get()->add(prefix + "/data/demo/demo.bin", t_data);
|
||||||
|
|
||||||
|
// Musicas
|
||||||
|
Asset::get()->add(prefix + "/data/music/intro.ogg", t_music);
|
||||||
|
Asset::get()->add(prefix + "/data/music/playing.ogg", t_music);
|
||||||
|
Asset::get()->add(prefix + "/data/music/title.ogg", t_music);
|
||||||
|
|
||||||
|
// Sonidos
|
||||||
|
Asset::get()->add(prefix + "/data/sound/balloon.wav", t_sound);
|
||||||
|
Asset::get()->add(prefix + "/data/sound/bubble1.wav", t_sound);
|
||||||
|
Asset::get()->add(prefix + "/data/sound/bubble2.wav", t_sound);
|
||||||
|
Asset::get()->add(prefix + "/data/sound/bubble3.wav", t_sound);
|
||||||
|
Asset::get()->add(prefix + "/data/sound/bubble4.wav", t_sound);
|
||||||
|
Asset::get()->add(prefix + "/data/sound/bullet.wav", t_sound);
|
||||||
|
Asset::get()->add(prefix + "/data/sound/coffeeout.wav", t_sound);
|
||||||
|
Asset::get()->add(prefix + "/data/sound/hiscore.wav", t_sound);
|
||||||
|
Asset::get()->add(prefix + "/data/sound/itemdrop.wav", t_sound);
|
||||||
|
Asset::get()->add(prefix + "/data/sound/itempickup.wav", t_sound);
|
||||||
|
Asset::get()->add(prefix + "/data/sound/menu_move.wav", t_sound);
|
||||||
|
Asset::get()->add(prefix + "/data/sound/menu_select.wav", t_sound);
|
||||||
|
Asset::get()->add(prefix + "/data/sound/player_collision.wav", t_sound);
|
||||||
|
Asset::get()->add(prefix + "/data/sound/stage_change.wav", t_sound);
|
||||||
|
Asset::get()->add(prefix + "/data/sound/title.wav", t_sound);
|
||||||
|
Asset::get()->add(prefix + "/data/sound/clock.wav", t_sound);
|
||||||
|
Asset::get()->add(prefix + "/data/sound/powerball.wav", t_sound);
|
||||||
|
|
||||||
|
// Texturas
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/balloon1.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/balloon1.ani", t_data);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/balloon2.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/balloon2.ani", t_data);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/balloon3.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/balloon3.ani", t_data);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/balloon4.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/balloon4.ani", t_data);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/bullet.png", t_bitmap);
|
||||||
|
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/game_buildings.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/game_clouds.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/game_grass.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/game_power_meter.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/game_sky_colors.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/game_text.png", t_bitmap);
|
||||||
|
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/intro.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/logo.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/menu_game_over.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/menu_game_over_end.png", t_bitmap);
|
||||||
|
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/item_points1_disk.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/item_points1_disk.ani", t_data);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/item_points2_gavina.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/item_points2_gavina.ani", t_data);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/item_points3_pacmar.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/item_points3_pacmar.ani", t_data);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/item_clock.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/item_clock.ani", t_data);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/item_coffee.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/item_coffee.ani", t_data);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/item_coffee_machine.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/item_coffee_machine.ani", t_data);
|
||||||
|
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/title_bg_tile.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/title_coffee.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/title_crisis.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/title_dust.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/title_dust.ani", t_data);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/title_gradient.png", t_bitmap);
|
||||||
|
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/player_head.ani", t_data);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/player_body.ani", t_data);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/player_legs.ani", t_data);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/player_death.ani", t_data);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/player_fire.ani", t_data);
|
||||||
|
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/player_bal1_head.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/player_bal1_body.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/player_bal1_legs.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/player_bal1_death.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/player_bal1_fire.png", t_bitmap);
|
||||||
|
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/player_arounder_head.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/player_arounder_body.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/player_arounder_legs.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/player_arounder_death.png", t_bitmap);
|
||||||
|
Asset::get()->add(prefix + "/data/gfx/player_arounder_fire.png", t_bitmap);
|
||||||
|
|
||||||
|
// Fuentes
|
||||||
|
Asset::get()->add(prefix + "/data/font/8bithud.png", t_font);
|
||||||
|
Asset::get()->add(prefix + "/data/font/8bithud.txt", t_font);
|
||||||
|
Asset::get()->add(prefix + "/data/font/nokia.png", t_font);
|
||||||
|
Asset::get()->add(prefix + "/data/font/nokia_big2.png", t_font);
|
||||||
|
Asset::get()->add(prefix + "/data/font/nokia.txt", t_font);
|
||||||
|
Asset::get()->add(prefix + "/data/font/nokia2.png", t_font);
|
||||||
|
Asset::get()->add(prefix + "/data/font/nokia2.txt", t_font);
|
||||||
|
Asset::get()->add(prefix + "/data/font/nokia_big2.txt", t_font);
|
||||||
|
Asset::get()->add(prefix + "/data/font/smb2_big.png", t_font);
|
||||||
|
Asset::get()->add(prefix + "/data/font/smb2_big.txt", t_font);
|
||||||
|
Asset::get()->add(prefix + "/data/font/smb2.png", t_font);
|
||||||
|
Asset::get()->add(prefix + "/data/font/smb2.txt", t_font);
|
||||||
|
|
||||||
|
// Textos
|
||||||
|
Asset::get()->add(prefix + "/data/lang/es_ES.txt", t_lang);
|
||||||
|
Asset::get()->add(prefix + "/data/lang/en_UK.txt", t_lang);
|
||||||
|
Asset::get()->add(prefix + "/data/lang/ba_BA.txt", t_lang);
|
||||||
|
|
||||||
|
// Menus
|
||||||
|
Asset::get()->add(prefix + "/data/menu/title.men", t_data);
|
||||||
|
Asset::get()->add(prefix + "/data/menu/title_gc.men", t_data);
|
||||||
|
Asset::get()->add(prefix + "/data/menu/options.men", t_data);
|
||||||
|
Asset::get()->add(prefix + "/data/menu/options_gc.men", t_data);
|
||||||
|
Asset::get()->add(prefix + "/data/menu/pause.men", t_data);
|
||||||
|
Asset::get()->add(prefix + "/data/menu/gameover.men", t_data);
|
||||||
|
Asset::get()->add(prefix + "/data/menu/player_select.men", t_data);
|
||||||
|
|
||||||
|
return Asset::get()->check();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comprueba los parametros del programa
|
||||||
|
void Director::checkProgramArguments(int argc, const char *argv[]) {
|
||||||
|
// Establece la ruta del programa
|
||||||
|
executablePath = argv[0];
|
||||||
|
|
||||||
|
// Comprueba el resto de parametros
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
if (strcmp(argv[i], "--console") == 0) {
|
||||||
|
Options::settings.console = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crea la carpeta del sistema donde guardar datos
|
||||||
|
void Director::createSystemFolder(const std::string &folder) {
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
// En Emscripten usamos una carpeta en MEMFS (no persistente)
|
||||||
|
systemFolder = "/config/" + folder;
|
||||||
|
#elif _WIN32
|
||||||
|
systemFolder = std::string(getenv("APPDATA")) + "/" + folder;
|
||||||
|
#elif __APPLE__
|
||||||
|
struct passwd *pw = getpwuid(getuid());
|
||||||
|
const char *homedir = pw->pw_dir;
|
||||||
|
systemFolder = std::string(homedir) + "/Library/Application Support" + "/" + folder;
|
||||||
|
#elif __linux__
|
||||||
|
struct passwd *pw = getpwuid(getuid());
|
||||||
|
const char *homedir = pw->pw_dir;
|
||||||
|
systemFolder = std::string(homedir) + "/.config/" + folder;
|
||||||
|
|
||||||
|
{
|
||||||
|
// Intenta crear ".config", per si no existeix
|
||||||
|
std::string config_base_folder = std::string(homedir) + "/.config";
|
||||||
|
int ret = mkdir(config_base_folder.c_str(), S_IRWXU);
|
||||||
|
if (ret == -1 && errno != EEXIST) {
|
||||||
|
printf("ERROR CREATING CONFIG BASE FOLDER.");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
// En Emscripten no necesitamos crear carpetas (MEMFS las crea automáticamente)
|
||||||
|
(void)folder;
|
||||||
|
#else
|
||||||
|
struct stat st = {0};
|
||||||
|
if (stat(systemFolder.c_str(), &st) == -1) {
|
||||||
|
errno = 0;
|
||||||
|
#ifdef _WIN32
|
||||||
|
int ret = mkdir(systemFolder.c_str());
|
||||||
|
#else
|
||||||
|
int ret = mkdir(systemFolder.c_str(), S_IRWXU);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (ret == -1) {
|
||||||
|
switch (errno) {
|
||||||
|
case EACCES:
|
||||||
|
printf("the parent directory does not allow write");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
|
||||||
|
case EEXIST:
|
||||||
|
printf("pathname already exists");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
|
||||||
|
case ENAMETOOLONG:
|
||||||
|
printf("pathname is too long");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
|
||||||
|
default:
|
||||||
|
perror("mkdir");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gestiona las transiciones entre secciones
|
||||||
|
void Director::handleSectionTransition() {
|
||||||
|
// Determina qué sección debería estar activa
|
||||||
|
ActiveSection targetSection = ActiveSection::None;
|
||||||
|
switch (section->name) {
|
||||||
|
case SECTION_PROG_LOGO:
|
||||||
|
targetSection = ActiveSection::Logo;
|
||||||
|
break;
|
||||||
|
case SECTION_PROG_INTRO:
|
||||||
|
targetSection = ActiveSection::Intro;
|
||||||
|
break;
|
||||||
|
case SECTION_PROG_TITLE:
|
||||||
|
targetSection = ActiveSection::Title;
|
||||||
|
break;
|
||||||
|
case SECTION_PROG_GAME:
|
||||||
|
targetSection = ActiveSection::Game;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si no ha cambiado, no hay nada que hacer
|
||||||
|
if (targetSection == activeSection) return;
|
||||||
|
|
||||||
|
// Destruye la sección anterior
|
||||||
|
logo.reset();
|
||||||
|
intro.reset();
|
||||||
|
title.reset();
|
||||||
|
game.reset();
|
||||||
|
|
||||||
|
// Crea la nueva sección
|
||||||
|
activeSection = targetSection;
|
||||||
|
switch (activeSection) {
|
||||||
|
case ActiveSection::Logo:
|
||||||
|
logo = std::make_unique<Logo>(renderer, section);
|
||||||
|
break;
|
||||||
|
case ActiveSection::Intro:
|
||||||
|
intro = std::make_unique<Intro>(renderer, section);
|
||||||
|
break;
|
||||||
|
case ActiveSection::Title:
|
||||||
|
title = std::make_unique<Title>(renderer, section);
|
||||||
|
break;
|
||||||
|
case ActiveSection::Game: {
|
||||||
|
const int numPlayers = section->subsection == SUBSECTION_GAME_PLAY_1P ? 1 : 2;
|
||||||
|
game = std::make_unique<Game>(numPlayers, 0, renderer, false, section);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ActiveSection::None:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ejecuta un frame del juego
|
||||||
|
SDL_AppResult Director::iterate() {
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
// En WASM no se puede salir: reinicia al logo
|
||||||
|
if (section->name == SECTION_PROG_QUIT) {
|
||||||
|
section->name = SECTION_PROG_LOGO;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (section->name == SECTION_PROG_QUIT) {
|
||||||
|
return SDL_APP_SUCCESS;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Actualiza la visibilidad del cursor del ratón
|
||||||
|
Mouse::updateCursorVisibility(Options::video.fullscreen);
|
||||||
|
|
||||||
|
// Gestiona las transiciones entre secciones
|
||||||
|
handleSectionTransition();
|
||||||
|
|
||||||
|
// Ejecuta un frame de la sección activa
|
||||||
|
switch (activeSection) {
|
||||||
|
case ActiveSection::Logo:
|
||||||
|
logo->iterate();
|
||||||
|
break;
|
||||||
|
case ActiveSection::Intro:
|
||||||
|
intro->iterate();
|
||||||
|
break;
|
||||||
|
case ActiveSection::Title:
|
||||||
|
title->iterate();
|
||||||
|
break;
|
||||||
|
case ActiveSection::Game:
|
||||||
|
game->iterate();
|
||||||
|
break;
|
||||||
|
case ActiveSection::None:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SDL_APP_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Procesa un evento
|
||||||
|
SDL_AppResult Director::handleEvent(SDL_Event *event) {
|
||||||
|
#ifndef __EMSCRIPTEN__
|
||||||
|
// Evento de salida de la aplicación
|
||||||
|
if (event->type == SDL_EVENT_QUIT) {
|
||||||
|
section->name = SECTION_PROG_QUIT;
|
||||||
|
return SDL_APP_SUCCESS;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Hot-plug de mandos
|
||||||
|
if (event->type == SDL_EVENT_GAMEPAD_ADDED) {
|
||||||
|
std::string name;
|
||||||
|
if (Input::get()->handleGamepadAdded(event->gdevice.which, name)) {
|
||||||
|
Screen::get()->notify(name + " " + Lang::get()->getText(94),
|
||||||
|
color_t{0x40, 0xFF, 0x40},
|
||||||
|
color_t{0, 0, 0},
|
||||||
|
2500);
|
||||||
|
}
|
||||||
|
} else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) {
|
||||||
|
std::string name;
|
||||||
|
if (Input::get()->handleGamepadRemoved(event->gdevice.which, name)) {
|
||||||
|
Screen::get()->notify(name + " " + Lang::get()->getText(95),
|
||||||
|
color_t{0xFF, 0x50, 0x50},
|
||||||
|
color_t{0, 0, 0},
|
||||||
|
2500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gestiona la visibilidad del cursor según el movimiento del ratón
|
||||||
|
Mouse::handleEvent(*event, Options::video.fullscreen);
|
||||||
|
|
||||||
|
// Reenvía el evento a la sección activa
|
||||||
|
switch (activeSection) {
|
||||||
|
case ActiveSection::Logo:
|
||||||
|
logo->handleEvent(event);
|
||||||
|
break;
|
||||||
|
case ActiveSection::Intro:
|
||||||
|
intro->handleEvent(event);
|
||||||
|
break;
|
||||||
|
case ActiveSection::Title:
|
||||||
|
title->handleEvent(event);
|
||||||
|
break;
|
||||||
|
case ActiveSection::Game:
|
||||||
|
game->handleEvent(event);
|
||||||
|
break;
|
||||||
|
case ActiveSection::None:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SDL_APP_CONTINUE;
|
||||||
|
}
|
||||||
74
source/core/system/director.h
Normal file
74
source/core/system/director.h
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string> // for string, basic_string
|
||||||
|
class Game;
|
||||||
|
class Intro;
|
||||||
|
class Logo;
|
||||||
|
class Title;
|
||||||
|
struct section_t;
|
||||||
|
|
||||||
|
// Secciones activas del Director
|
||||||
|
enum class ActiveSection { None,
|
||||||
|
Logo,
|
||||||
|
Intro,
|
||||||
|
Title,
|
||||||
|
Game };
|
||||||
|
|
||||||
|
class Director {
|
||||||
|
private:
|
||||||
|
// Objetos y punteros
|
||||||
|
SDL_Window *window; // La ventana donde dibujamos
|
||||||
|
SDL_Renderer *renderer; // El renderizador de la ventana
|
||||||
|
section_t *section; // Sección y subsección actual del programa;
|
||||||
|
|
||||||
|
// Secciones del juego
|
||||||
|
ActiveSection activeSection;
|
||||||
|
std::unique_ptr<Logo> logo;
|
||||||
|
std::unique_ptr<Intro> intro;
|
||||||
|
std::unique_ptr<Title> title;
|
||||||
|
std::unique_ptr<Game> game;
|
||||||
|
|
||||||
|
// Variables
|
||||||
|
std::string executablePath; // Path del ejecutable
|
||||||
|
std::string systemFolder; // Carpeta del sistema donde guardar datos
|
||||||
|
|
||||||
|
// Inicializa jail_audio
|
||||||
|
void initJailAudio();
|
||||||
|
|
||||||
|
// Arranca SDL y crea la ventana
|
||||||
|
bool initSDL();
|
||||||
|
|
||||||
|
// Inicializa el objeto input
|
||||||
|
void initInput();
|
||||||
|
|
||||||
|
// Crea el indice de ficheros
|
||||||
|
bool setFileList();
|
||||||
|
|
||||||
|
// Comprueba los parametros del programa
|
||||||
|
void checkProgramArguments(int argc, const char *argv[]);
|
||||||
|
|
||||||
|
// Crea la carpeta del sistema donde guardar datos
|
||||||
|
void createSystemFolder(const std::string &folder);
|
||||||
|
|
||||||
|
// Gestiona las transiciones entre secciones
|
||||||
|
void handleSectionTransition();
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Constructor
|
||||||
|
Director(int argc, const char *argv[]);
|
||||||
|
|
||||||
|
// Destructor
|
||||||
|
~Director();
|
||||||
|
|
||||||
|
Director(const Director &) = delete;
|
||||||
|
Director &operator=(const Director &) = delete;
|
||||||
|
|
||||||
|
// Ejecuta un frame del juego
|
||||||
|
SDL_AppResult iterate();
|
||||||
|
|
||||||
|
// Procesa un evento
|
||||||
|
SDL_AppResult handleEvent(SDL_Event *event);
|
||||||
|
};
|
||||||
@@ -1,696 +0,0 @@
|
|||||||
#include "director.h"
|
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
#include <errno.h> // for errno, EEXIST, EACCES, ENAMETOO...
|
|
||||||
#include <stdio.h> // for printf, perror
|
|
||||||
#include <string.h> // for strcmp
|
|
||||||
#include <sys/stat.h> // for mkdir, stat, S_IRWXU
|
|
||||||
#include <unistd.h> // for getuid
|
|
||||||
|
|
||||||
#include <cstdlib> // for exit, EXIT_FAILURE, srand
|
|
||||||
#include <fstream> // for basic_ostream, operator<<, basi...
|
|
||||||
#include <iostream> // for cout
|
|
||||||
#include <memory>
|
|
||||||
#include <string> // for basic_string, operator+, char_t...
|
|
||||||
#include <vector> // for vector
|
|
||||||
|
|
||||||
#include "asset.h" // for Asset, assetType
|
|
||||||
#include "const.h" // for SECTION_PROG_LOGO, GAMECANVAS_H...
|
|
||||||
#include "game.h" // for Game
|
|
||||||
#include "input.h" // for Input, inputs_e, INPUT_USE_GAME...
|
|
||||||
#include "intro.h" // for Intro
|
|
||||||
#include "jail_audio.hpp" // for JA_Init
|
|
||||||
#include "lang.h" // for Lang, MAX_LANGUAGES, ba_BA, en_UK
|
|
||||||
#include "logo.h" // for Logo
|
|
||||||
#include "screen.h" // for FILTER_NEAREST, Screen, FILTER_...
|
|
||||||
#include "texture.h" // for Texture
|
|
||||||
#include "title.h" // for Title
|
|
||||||
#include "utils.h" // for options_t, input_t, boolToString
|
|
||||||
|
|
||||||
#ifndef _WIN32
|
|
||||||
#include <pwd.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Constructor
|
|
||||||
Director::Director(int argc, const char *argv[]) {
|
|
||||||
std::cout << "Game start" << std::endl;
|
|
||||||
// Inicializa variables
|
|
||||||
section = new section_t();
|
|
||||||
section->name = SECTION_PROG_LOGO;
|
|
||||||
|
|
||||||
// Inicializa las opciones del programa
|
|
||||||
initOptions();
|
|
||||||
|
|
||||||
// Comprueba los parametros del programa
|
|
||||||
checkProgramArguments(argc, argv);
|
|
||||||
|
|
||||||
// Crea la carpeta del sistema donde guardar datos
|
|
||||||
createSystemFolder("jailgames");
|
|
||||||
#ifndef DEBUG
|
|
||||||
createSystemFolder("jailgames/coffee_crisis");
|
|
||||||
#else
|
|
||||||
createSystemFolder("jailgames/coffee_crisis_debug");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Crea el objeto que controla los ficheros de recursos
|
|
||||||
asset = new Asset(executablePath);
|
|
||||||
asset->setVerbose(options->console);
|
|
||||||
|
|
||||||
// Si falta algún fichero no inicia el programa
|
|
||||||
if (!setFileList()) {
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Carga el fichero de configuración
|
|
||||||
loadConfigFile();
|
|
||||||
|
|
||||||
// Inicializa SDL
|
|
||||||
initSDL();
|
|
||||||
|
|
||||||
// Inicializa JailAudio
|
|
||||||
initJailAudio();
|
|
||||||
|
|
||||||
// Establece el modo de escalado de texturas
|
|
||||||
Texture::setGlobalScaleMode(options->filter == FILTER_NEAREST ? SDL_SCALEMODE_NEAREST : SDL_SCALEMODE_LINEAR);
|
|
||||||
|
|
||||||
// Crea los objetos
|
|
||||||
lang = new Lang(asset);
|
|
||||||
lang->setLang(options->language);
|
|
||||||
|
|
||||||
input = new Input(asset->get("gamecontrollerdb.txt"));
|
|
||||||
initInput();
|
|
||||||
|
|
||||||
screen = new Screen(window, renderer, asset, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
Director::~Director() {
|
|
||||||
saveConfigFile();
|
|
||||||
|
|
||||||
delete asset;
|
|
||||||
delete input;
|
|
||||||
delete screen;
|
|
||||||
delete lang;
|
|
||||||
delete options;
|
|
||||||
delete section;
|
|
||||||
|
|
||||||
SDL_DestroyRenderer(renderer);
|
|
||||||
SDL_DestroyWindow(window);
|
|
||||||
|
|
||||||
SDL_Quit();
|
|
||||||
|
|
||||||
std::cout << "\nBye!" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inicializa el objeto input
|
|
||||||
void Director::initInput() {
|
|
||||||
// Establece si ha de mostrar mensajes
|
|
||||||
input->setVerbose(options->console);
|
|
||||||
|
|
||||||
// Busca si hay un mando conectado
|
|
||||||
input->discoverGameController();
|
|
||||||
|
|
||||||
// Teclado - Movimiento del jugador
|
|
||||||
input->bindKey(input_up, SDL_SCANCODE_UP);
|
|
||||||
input->bindKey(input_down, SDL_SCANCODE_DOWN);
|
|
||||||
input->bindKey(input_left, SDL_SCANCODE_LEFT);
|
|
||||||
input->bindKey(input_right, SDL_SCANCODE_RIGHT);
|
|
||||||
input->bindKey(input_fire_left, SDL_SCANCODE_Q);
|
|
||||||
input->bindKey(input_fire_center, SDL_SCANCODE_W);
|
|
||||||
input->bindKey(input_fire_right, SDL_SCANCODE_E);
|
|
||||||
|
|
||||||
// Teclado - Otros
|
|
||||||
input->bindKey(input_accept, SDL_SCANCODE_RETURN);
|
|
||||||
input->bindKey(input_cancel, SDL_SCANCODE_ESCAPE);
|
|
||||||
input->bindKey(input_pause, SDL_SCANCODE_ESCAPE);
|
|
||||||
input->bindKey(input_exit, SDL_SCANCODE_ESCAPE);
|
|
||||||
input->bindKey(input_window_dec_size, SDL_SCANCODE_F1);
|
|
||||||
input->bindKey(input_window_inc_size, SDL_SCANCODE_F2);
|
|
||||||
input->bindKey(input_window_fullscreen, SDL_SCANCODE_F3);
|
|
||||||
|
|
||||||
// Mando - Movimiento del jugador
|
|
||||||
input->bindGameControllerButton(input_up, SDL_GAMEPAD_BUTTON_DPAD_UP);
|
|
||||||
input->bindGameControllerButton(input_down, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
|
|
||||||
input->bindGameControllerButton(input_left, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
|
|
||||||
input->bindGameControllerButton(input_right, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
|
|
||||||
input->bindGameControllerButton(input_fire_left, SDL_GAMEPAD_BUTTON_WEST);
|
|
||||||
input->bindGameControllerButton(input_fire_center, SDL_GAMEPAD_BUTTON_NORTH);
|
|
||||||
input->bindGameControllerButton(input_fire_right, SDL_GAMEPAD_BUTTON_EAST);
|
|
||||||
|
|
||||||
// Mando - Otros
|
|
||||||
input->bindGameControllerButton(input_accept, SDL_GAMEPAD_BUTTON_EAST);
|
|
||||||
input->bindGameControllerButton(input_cancel, SDL_GAMEPAD_BUTTON_SOUTH);
|
|
||||||
#ifdef GAME_CONSOLE
|
|
||||||
input->bindGameControllerButton(input_pause, SDL_GAMEPAD_BUTTON_BACK);
|
|
||||||
input->bindGameControllerButton(input_exit, SDL_GAMEPAD_BUTTON_START);
|
|
||||||
#else
|
|
||||||
input->bindGameControllerButton(input_pause, SDL_GAMEPAD_BUTTON_START);
|
|
||||||
input->bindGameControllerButton(input_exit, SDL_GAMEPAD_BUTTON_BACK);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inicializa JailAudio
|
|
||||||
void Director::initJailAudio() {
|
|
||||||
JA_Init(48000, SDL_AUDIO_S16, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Arranca SDL y crea la ventana
|
|
||||||
bool Director::initSDL() {
|
|
||||||
// Indicador de éxito
|
|
||||||
bool success = true;
|
|
||||||
|
|
||||||
// Inicializa SDL
|
|
||||||
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD)) {
|
|
||||||
if (options->console) {
|
|
||||||
std::cout << "SDL could not initialize!\nSDL Error: " << SDL_GetError() << std::endl;
|
|
||||||
}
|
|
||||||
success = false;
|
|
||||||
} else {
|
|
||||||
// Inicia el generador de numeros aleatorios
|
|
||||||
std::srand(static_cast<unsigned int>(SDL_GetTicks()));
|
|
||||||
|
|
||||||
// Crea la ventana
|
|
||||||
int incW = 0;
|
|
||||||
int incH = 0;
|
|
||||||
if (options->borderEnabled) {
|
|
||||||
incW = options->borderWidth * 2;
|
|
||||||
incH = options->borderHeight * 2;
|
|
||||||
}
|
|
||||||
window = SDL_CreateWindow(WINDOW_CAPTION, (options->gameWidth + incW) * options->windowSize, (options->gameHeight + incH) * options->windowSize, 0);
|
|
||||||
if (window == nullptr) {
|
|
||||||
if (options->console) {
|
|
||||||
std::cout << "Window could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
|
|
||||||
}
|
|
||||||
success = false;
|
|
||||||
} else {
|
|
||||||
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
|
||||||
|
|
||||||
// Crea un renderizador para la ventana
|
|
||||||
renderer = SDL_CreateRenderer(window, NULL);
|
|
||||||
|
|
||||||
if (renderer == nullptr) {
|
|
||||||
if (options->console) {
|
|
||||||
std::cout << "Renderer could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
|
|
||||||
}
|
|
||||||
success = false;
|
|
||||||
} else {
|
|
||||||
// Activa vsync si es necesario
|
|
||||||
if (options->vSync) {
|
|
||||||
SDL_SetRenderVSync(renderer, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inicializa el color de renderizado
|
|
||||||
SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);
|
|
||||||
|
|
||||||
// Establece el tamaño del buffer de renderizado
|
|
||||||
SDL_SetRenderLogicalPresentation(renderer, options->gameWidth, options->gameHeight, SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
|
||||||
|
|
||||||
// Establece el modo de mezcla
|
|
||||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options->console) {
|
|
||||||
std::cout << std::endl;
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Crea el indice de ficheros
|
|
||||||
bool Director::setFileList() {
|
|
||||||
#ifdef MACOS_BUNDLE
|
|
||||||
const std::string prefix = "/../Resources";
|
|
||||||
#else
|
|
||||||
const std::string prefix = "";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Ficheros de configuración
|
|
||||||
asset->add(systemFolder + "/config.txt", t_data, false, true);
|
|
||||||
asset->add(systemFolder + "/score.bin", t_data, false, true);
|
|
||||||
asset->add(prefix + "/data/config/demo.bin", t_data);
|
|
||||||
asset->add(prefix + "/data/config/gamecontrollerdb.txt", t_data);
|
|
||||||
|
|
||||||
// Musicas
|
|
||||||
asset->add(prefix + "/data/music/intro.ogg", t_music);
|
|
||||||
asset->add(prefix + "/data/music/playing.ogg", t_music);
|
|
||||||
asset->add(prefix + "/data/music/title.ogg", t_music);
|
|
||||||
|
|
||||||
// Sonidos
|
|
||||||
asset->add(prefix + "/data/sound/balloon.wav", t_sound);
|
|
||||||
asset->add(prefix + "/data/sound/bubble1.wav", t_sound);
|
|
||||||
asset->add(prefix + "/data/sound/bubble2.wav", t_sound);
|
|
||||||
asset->add(prefix + "/data/sound/bubble3.wav", t_sound);
|
|
||||||
asset->add(prefix + "/data/sound/bubble4.wav", t_sound);
|
|
||||||
asset->add(prefix + "/data/sound/bullet.wav", t_sound);
|
|
||||||
asset->add(prefix + "/data/sound/coffeeout.wav", t_sound);
|
|
||||||
asset->add(prefix + "/data/sound/hiscore.wav", t_sound);
|
|
||||||
asset->add(prefix + "/data/sound/itemdrop.wav", t_sound);
|
|
||||||
asset->add(prefix + "/data/sound/itempickup.wav", t_sound);
|
|
||||||
asset->add(prefix + "/data/sound/menu_move.wav", t_sound);
|
|
||||||
asset->add(prefix + "/data/sound/menu_select.wav", t_sound);
|
|
||||||
asset->add(prefix + "/data/sound/player_collision.wav", t_sound);
|
|
||||||
asset->add(prefix + "/data/sound/stage_change.wav", t_sound);
|
|
||||||
asset->add(prefix + "/data/sound/title.wav", t_sound);
|
|
||||||
asset->add(prefix + "/data/sound/clock.wav", t_sound);
|
|
||||||
asset->add(prefix + "/data/sound/powerball.wav", t_sound);
|
|
||||||
|
|
||||||
// Texturas
|
|
||||||
asset->add(prefix + "/data/gfx/balloon1.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/balloon1.ani", t_data);
|
|
||||||
asset->add(prefix + "/data/gfx/balloon2.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/balloon2.ani", t_data);
|
|
||||||
asset->add(prefix + "/data/gfx/balloon3.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/balloon3.ani", t_data);
|
|
||||||
asset->add(prefix + "/data/gfx/balloon4.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/balloon4.ani", t_data);
|
|
||||||
asset->add(prefix + "/data/gfx/bullet.png", t_bitmap);
|
|
||||||
|
|
||||||
asset->add(prefix + "/data/gfx/game_buildings.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/game_clouds.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/game_grass.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/game_power_meter.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/game_sky_colors.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/game_text.png", t_bitmap);
|
|
||||||
|
|
||||||
asset->add(prefix + "/data/gfx/intro.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/logo.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/menu_game_over.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/menu_game_over_end.png", t_bitmap);
|
|
||||||
|
|
||||||
asset->add(prefix + "/data/gfx/item_points1_disk.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/item_points1_disk.ani", t_data);
|
|
||||||
asset->add(prefix + "/data/gfx/item_points2_gavina.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/item_points2_gavina.ani", t_data);
|
|
||||||
asset->add(prefix + "/data/gfx/item_points3_pacmar.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/item_points3_pacmar.ani", t_data);
|
|
||||||
asset->add(prefix + "/data/gfx/item_clock.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/item_clock.ani", t_data);
|
|
||||||
asset->add(prefix + "/data/gfx/item_coffee.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/item_coffee.ani", t_data);
|
|
||||||
asset->add(prefix + "/data/gfx/item_coffee_machine.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/item_coffee_machine.ani", t_data);
|
|
||||||
|
|
||||||
asset->add(prefix + "/data/gfx/title_bg_tile.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/title_coffee.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/title_crisis.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/title_dust.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/title_dust.ani", t_data);
|
|
||||||
asset->add(prefix + "/data/gfx/title_gradient.png", t_bitmap);
|
|
||||||
|
|
||||||
asset->add(prefix + "/data/gfx/player_head.ani", t_data);
|
|
||||||
asset->add(prefix + "/data/gfx/player_body.ani", t_data);
|
|
||||||
asset->add(prefix + "/data/gfx/player_legs.ani", t_data);
|
|
||||||
asset->add(prefix + "/data/gfx/player_death.ani", t_data);
|
|
||||||
asset->add(prefix + "/data/gfx/player_fire.ani", t_data);
|
|
||||||
|
|
||||||
asset->add(prefix + "/data/gfx/player_bal1_head.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/player_bal1_body.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/player_bal1_legs.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/player_bal1_death.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/player_bal1_fire.png", t_bitmap);
|
|
||||||
|
|
||||||
asset->add(prefix + "/data/gfx/player_arounder_head.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/player_arounder_body.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/player_arounder_legs.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/player_arounder_death.png", t_bitmap);
|
|
||||||
asset->add(prefix + "/data/gfx/player_arounder_fire.png", t_bitmap);
|
|
||||||
|
|
||||||
// Fuentes
|
|
||||||
asset->add(prefix + "/data/font/8bithud.png", t_font);
|
|
||||||
asset->add(prefix + "/data/font/8bithud.txt", t_font);
|
|
||||||
asset->add(prefix + "/data/font/nokia.png", t_font);
|
|
||||||
asset->add(prefix + "/data/font/nokia_big2.png", t_font);
|
|
||||||
asset->add(prefix + "/data/font/nokia.txt", t_font);
|
|
||||||
asset->add(prefix + "/data/font/nokia2.png", t_font);
|
|
||||||
asset->add(prefix + "/data/font/nokia2.txt", t_font);
|
|
||||||
asset->add(prefix + "/data/font/nokia_big2.txt", t_font);
|
|
||||||
asset->add(prefix + "/data/font/smb2_big.png", t_font);
|
|
||||||
asset->add(prefix + "/data/font/smb2_big.txt", t_font);
|
|
||||||
asset->add(prefix + "/data/font/smb2.png", t_font);
|
|
||||||
asset->add(prefix + "/data/font/smb2.txt", t_font);
|
|
||||||
|
|
||||||
// Textos
|
|
||||||
asset->add(prefix + "/data/lang/es_ES.txt", t_lang);
|
|
||||||
asset->add(prefix + "/data/lang/en_UK.txt", t_lang);
|
|
||||||
asset->add(prefix + "/data/lang/ba_BA.txt", t_lang);
|
|
||||||
|
|
||||||
// Menus
|
|
||||||
asset->add(prefix + "/data/menu/title.men", t_data);
|
|
||||||
asset->add(prefix + "/data/menu/title_gc.men", t_data);
|
|
||||||
asset->add(prefix + "/data/menu/options.men", t_data);
|
|
||||||
asset->add(prefix + "/data/menu/options_gc.men", t_data);
|
|
||||||
asset->add(prefix + "/data/menu/pause.men", t_data);
|
|
||||||
asset->add(prefix + "/data/menu/gameover.men", t_data);
|
|
||||||
asset->add(prefix + "/data/menu/player_select.men", t_data);
|
|
||||||
|
|
||||||
return asset->check();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inicializa las opciones del programa
|
|
||||||
void Director::initOptions() {
|
|
||||||
// Crea el puntero a la estructura de opciones
|
|
||||||
options = new options_t;
|
|
||||||
|
|
||||||
// Pone unos valores por defecto para las opciones de control
|
|
||||||
options->input.clear();
|
|
||||||
|
|
||||||
input_t inp;
|
|
||||||
inp.id = 0;
|
|
||||||
inp.name = "KEYBOARD";
|
|
||||||
inp.deviceType = INPUT_USE_KEYBOARD;
|
|
||||||
options->input.push_back(inp);
|
|
||||||
|
|
||||||
inp.id = 0;
|
|
||||||
inp.name = "GAME CONTROLLER";
|
|
||||||
inp.deviceType = INPUT_USE_GAMECONTROLLER;
|
|
||||||
options->input.push_back(inp);
|
|
||||||
|
|
||||||
// Opciones de video
|
|
||||||
options->gameWidth = GAMECANVAS_WIDTH;
|
|
||||||
options->gameHeight = GAMECANVAS_HEIGHT;
|
|
||||||
options->videoMode = 0;
|
|
||||||
options->windowSize = 3;
|
|
||||||
options->filter = FILTER_NEAREST;
|
|
||||||
options->vSync = true;
|
|
||||||
options->integerScale = true;
|
|
||||||
options->keepAspect = true;
|
|
||||||
options->borderWidth = 0;
|
|
||||||
options->borderHeight = 0;
|
|
||||||
options->borderEnabled = false;
|
|
||||||
|
|
||||||
// Opciones varios
|
|
||||||
options->playerSelected = 0;
|
|
||||||
options->difficulty = DIFFICULTY_NORMAL;
|
|
||||||
options->language = ba_BA;
|
|
||||||
options->console = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comprueba los parametros del programa
|
|
||||||
void Director::checkProgramArguments(int argc, const char *argv[]) {
|
|
||||||
// Establece la ruta del programa
|
|
||||||
executablePath = argv[0];
|
|
||||||
|
|
||||||
// Comprueba el resto de parametros
|
|
||||||
for (int i = 1; i < argc; ++i) {
|
|
||||||
if (strcmp(argv[i], "--console") == 0) {
|
|
||||||
options->console = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Crea la carpeta del sistema donde guardar datos
|
|
||||||
void Director::createSystemFolder(const std::string &folder) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
systemFolder = std::string(getenv("APPDATA")) + "/" + folder;
|
|
||||||
#elif __APPLE__
|
|
||||||
struct passwd *pw = getpwuid(getuid());
|
|
||||||
const char *homedir = pw->pw_dir;
|
|
||||||
systemFolder = std::string(homedir) + "/Library/Application Support" + "/" + folder;
|
|
||||||
#elif __linux__
|
|
||||||
struct passwd *pw = getpwuid(getuid());
|
|
||||||
const char *homedir = pw->pw_dir;
|
|
||||||
systemFolder = std::string(homedir) + "/.config/" + folder;
|
|
||||||
|
|
||||||
{
|
|
||||||
// Intenta crear ".config", per si no existeix
|
|
||||||
std::string config_base_folder = std::string(homedir) + "/.config";
|
|
||||||
int ret = mkdir(config_base_folder.c_str(), S_IRWXU);
|
|
||||||
if (ret == -1 && errno != EEXIST) {
|
|
||||||
printf("ERROR CREATING CONFIG BASE FOLDER.");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct stat st = {0};
|
|
||||||
if (stat(systemFolder.c_str(), &st) == -1) {
|
|
||||||
errno = 0;
|
|
||||||
#ifdef _WIN32
|
|
||||||
int ret = mkdir(systemFolder.c_str());
|
|
||||||
#else
|
|
||||||
int ret = mkdir(systemFolder.c_str(), S_IRWXU);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (ret == -1) {
|
|
||||||
switch (errno) {
|
|
||||||
case EACCES:
|
|
||||||
printf("the parent directory does not allow write");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
|
|
||||||
case EEXIST:
|
|
||||||
printf("pathname already exists");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
|
|
||||||
case ENAMETOOLONG:
|
|
||||||
printf("pathname is too long");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
|
|
||||||
default:
|
|
||||||
perror("mkdir");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Carga el fichero de configuración
|
|
||||||
bool Director::loadConfigFile() {
|
|
||||||
// Indicador de éxito en la carga
|
|
||||||
bool success = true;
|
|
||||||
|
|
||||||
// Variables para manejar el fichero
|
|
||||||
const std::string filePath = "config.txt";
|
|
||||||
std::string line;
|
|
||||||
std::ifstream file(asset->get(filePath));
|
|
||||||
|
|
||||||
// Si el fichero se puede abrir
|
|
||||||
if (file.good()) {
|
|
||||||
// Procesa el fichero linea a linea
|
|
||||||
if (options->console) {
|
|
||||||
std::cout << "Reading file " << filePath << std::endl;
|
|
||||||
}
|
|
||||||
while (std::getline(file, line)) {
|
|
||||||
// Comprueba que la linea no sea un comentario
|
|
||||||
if (line.substr(0, 1) != "#") {
|
|
||||||
// Encuentra la posición del caracter '='
|
|
||||||
int pos = line.find("=");
|
|
||||||
// Procesa las dos subcadenas
|
|
||||||
if (!setOptions(options, line.substr(0, pos), line.substr(pos + 1, line.length()))) {
|
|
||||||
if (options->console) {
|
|
||||||
std::cout << "Warning: file " << filePath << std::endl;
|
|
||||||
std::cout << "Unknown parameter " << line.substr(0, pos).c_str() << std::endl;
|
|
||||||
}
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cierra el fichero
|
|
||||||
if (options->console) {
|
|
||||||
std::cout << "Closing file " << filePath << std::endl;
|
|
||||||
}
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// El fichero no existe
|
|
||||||
else { // Crea el fichero con los valores por defecto
|
|
||||||
saveConfigFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normaliza los valores
|
|
||||||
if (options->videoMode != 0 && options->videoMode != SDL_WINDOW_FULLSCREEN) {
|
|
||||||
options->videoMode = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options->windowSize < 1 || options->windowSize > 4) {
|
|
||||||
options->windowSize = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options->language < 0 || options->language > MAX_LANGUAGES) {
|
|
||||||
options->language = en_UK;
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Guarda el fichero de configuración
|
|
||||||
bool Director::saveConfigFile() {
|
|
||||||
bool success = true;
|
|
||||||
|
|
||||||
// Crea y abre el fichero de texto
|
|
||||||
std::ofstream file(asset->get("config.txt"));
|
|
||||||
|
|
||||||
if (file.good()) {
|
|
||||||
if (options->console) {
|
|
||||||
std::cout << asset->get("config.txt") << " open for writing" << std::endl;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (options->console) {
|
|
||||||
std::cout << asset->get("config.txt") << " can't be opened" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Opciones g´raficas
|
|
||||||
file << "## VISUAL OPTIONS\n";
|
|
||||||
if (options->videoMode == 0) {
|
|
||||||
file << "videoMode=0\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (options->videoMode == SDL_WINDOW_FULLSCREEN) {
|
|
||||||
file << "videoMode=SDL_WINDOW_FULLSCREEN\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
file << "windowSize=" + std::to_string(options->windowSize) + "\n";
|
|
||||||
|
|
||||||
if (options->filter == FILTER_NEAREST) {
|
|
||||||
file << "filter=FILTER_NEAREST\n";
|
|
||||||
} else {
|
|
||||||
file << "filter=FILTER_LINEAL\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
file << "vSync=" + boolToString(options->vSync) + "\n";
|
|
||||||
file << "integerScale=" + boolToString(options->integerScale) + "\n";
|
|
||||||
file << "keepAspect=" + boolToString(options->keepAspect) + "\n";
|
|
||||||
file << "borderEnabled=" + boolToString(options->borderEnabled) + "\n";
|
|
||||||
file << "borderWidth=" + std::to_string(options->borderWidth) + "\n";
|
|
||||||
file << "borderHeight=" + std::to_string(options->borderHeight) + "\n";
|
|
||||||
|
|
||||||
// Otras opciones del programa
|
|
||||||
file << "\n## OTHER OPTIONS\n";
|
|
||||||
file << "language=" + std::to_string(options->language) + "\n";
|
|
||||||
file << "difficulty=" + std::to_string(options->difficulty) + "\n";
|
|
||||||
file << "input0=" + std::to_string(options->input[0].deviceType) + "\n";
|
|
||||||
file << "input1=" + std::to_string(options->input[1].deviceType) + "\n";
|
|
||||||
|
|
||||||
// Cierra el fichero
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Director::runLogo() {
|
|
||||||
auto logo = std::make_unique<Logo>(renderer, screen, asset, input, section);
|
|
||||||
logo->run();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Director::runIntro() {
|
|
||||||
auto intro = std::make_unique<Intro>(renderer, screen, asset, input, lang, section);
|
|
||||||
intro->run();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Director::runTitle() {
|
|
||||||
auto title = std::make_unique<Title>(renderer, screen, input, asset, options, lang, section);
|
|
||||||
title->run();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Director::runGame() {
|
|
||||||
const int numPlayers = section->subsection == SUBSECTION_GAME_PLAY_1P ? 1 : 2;
|
|
||||||
auto game = std::make_unique<Game>(numPlayers, 0, renderer, screen, asset, lang, input, false, options, section);
|
|
||||||
game->run();
|
|
||||||
}
|
|
||||||
|
|
||||||
int Director::run() {
|
|
||||||
// Bucle principal
|
|
||||||
while (section->name != SECTION_PROG_QUIT) {
|
|
||||||
switch (section->name) {
|
|
||||||
case SECTION_PROG_LOGO:
|
|
||||||
runLogo();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SECTION_PROG_INTRO:
|
|
||||||
runIntro();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SECTION_PROG_TITLE:
|
|
||||||
runTitle();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SECTION_PROG_GAME:
|
|
||||||
runGame();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Asigna variables a partir de dos cadenas
|
|
||||||
bool Director::setOptions(options_t *options, std::string var, std::string value) {
|
|
||||||
// Indicador de éxito en la asignación
|
|
||||||
bool success = true;
|
|
||||||
|
|
||||||
// Opciones de video
|
|
||||||
if (var == "videoMode") {
|
|
||||||
if (value == "SDL_WINDOW_FULLSCREEN" || value == "SDL_WINDOW_FULLSCREEN_DESKTOP") {
|
|
||||||
options->videoMode = SDL_WINDOW_FULLSCREEN;
|
|
||||||
} else {
|
|
||||||
options->videoMode = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (var == "windowSize") {
|
|
||||||
options->windowSize = std::stoi(value);
|
|
||||||
if ((options->windowSize < 1) || (options->windowSize > 4)) {
|
|
||||||
options->windowSize = 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (var == "filter") {
|
|
||||||
if (value == "FILTER_LINEAL") {
|
|
||||||
options->filter = FILTER_LINEAL;
|
|
||||||
} else {
|
|
||||||
options->filter = FILTER_NEAREST;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (var == "vSync") {
|
|
||||||
options->vSync = stringToBool(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (var == "integerScale") {
|
|
||||||
options->integerScale = stringToBool(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (var == "keepAspect") {
|
|
||||||
options->keepAspect = stringToBool(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (var == "borderEnabled") {
|
|
||||||
options->borderEnabled = stringToBool(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (var == "borderWidth") {
|
|
||||||
options->borderWidth = std::stoi(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (var == "borderHeight") {
|
|
||||||
options->borderHeight = std::stoi(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Opciones varias
|
|
||||||
else if (var == "language") {
|
|
||||||
options->language = std::stoi(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (var == "difficulty") {
|
|
||||||
options->difficulty = std::stoi(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (var == "input0") {
|
|
||||||
options->input[0].deviceType = std::stoi(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (var == "input1") {
|
|
||||||
options->input[1].deviceType = std::stoi(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lineas vacias o que empiezan por comentario
|
|
||||||
else if (var == "" || var.substr(0, 1) == "#") {
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
#include <string> // for string, basic_string
|
|
||||||
class Asset;
|
|
||||||
class Game;
|
|
||||||
class Input;
|
|
||||||
class Intro;
|
|
||||||
class Lang;
|
|
||||||
class Logo;
|
|
||||||
class Screen;
|
|
||||||
class Title;
|
|
||||||
struct options_t;
|
|
||||||
struct section_t;
|
|
||||||
|
|
||||||
// Textos
|
|
||||||
constexpr const char *WINDOW_CAPTION = "© 2020 Coffee Crisis — JailDesigner";
|
|
||||||
|
|
||||||
class Director {
|
|
||||||
private:
|
|
||||||
// Objetos y punteros
|
|
||||||
SDL_Window *window; // La ventana donde dibujamos
|
|
||||||
SDL_Renderer *renderer; // El renderizador de la ventana
|
|
||||||
Screen *screen; // Objeto encargado de dibujar en pantalla
|
|
||||||
Input *input; // Objeto Input para gestionar las entradas
|
|
||||||
Lang *lang; // Objeto para gestionar los textos en diferentes idiomas
|
|
||||||
Asset *asset; // Objeto que gestiona todos los ficheros de recursos
|
|
||||||
section_t *section; // Sección y subsección actual del programa;
|
|
||||||
|
|
||||||
// Variables
|
|
||||||
struct options_t *options; // Variable con todas las opciones del programa
|
|
||||||
std::string executablePath; // Path del ejecutable
|
|
||||||
std::string systemFolder; // Carpeta del sistema donde guardar datos
|
|
||||||
|
|
||||||
// Inicializa jail_audio
|
|
||||||
void initJailAudio();
|
|
||||||
|
|
||||||
// Arranca SDL y crea la ventana
|
|
||||||
bool initSDL();
|
|
||||||
|
|
||||||
// Inicializa el objeto input
|
|
||||||
void initInput();
|
|
||||||
|
|
||||||
// Inicializa las opciones del programa
|
|
||||||
void initOptions();
|
|
||||||
|
|
||||||
// Asigna variables a partir de dos cadenas
|
|
||||||
bool setOptions(options_t *options, std::string var, std::string value);
|
|
||||||
|
|
||||||
// Crea el indice de ficheros
|
|
||||||
bool setFileList();
|
|
||||||
|
|
||||||
// Carga el fichero de configuración
|
|
||||||
bool loadConfigFile();
|
|
||||||
|
|
||||||
// Guarda el fichero de configuración
|
|
||||||
bool saveConfigFile();
|
|
||||||
|
|
||||||
// Comprueba los parametros del programa
|
|
||||||
void checkProgramArguments(int argc, const char *argv[]);
|
|
||||||
|
|
||||||
// Crea la carpeta del sistema donde guardar datos
|
|
||||||
void createSystemFolder(const std::string &folder);
|
|
||||||
|
|
||||||
// Ejecuta la seccion de juego con el logo
|
|
||||||
void runLogo();
|
|
||||||
|
|
||||||
// Ejecuta la seccion de juego de la introducción
|
|
||||||
void runIntro();
|
|
||||||
|
|
||||||
// Ejecuta la seccion de juego con el titulo y los menus
|
|
||||||
void runTitle();
|
|
||||||
|
|
||||||
// Ejecuta la seccion de juego donde se juega
|
|
||||||
void runGame();
|
|
||||||
|
|
||||||
public:
|
|
||||||
// Constructor
|
|
||||||
Director(int argc, const char *argv[]);
|
|
||||||
|
|
||||||
// Destructor
|
|
||||||
~Director();
|
|
||||||
|
|
||||||
// Bucle principal
|
|
||||||
int run();
|
|
||||||
};
|
|
||||||
2
source/external/.clang-format
vendored
Normal file
2
source/external/.clang-format
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
DisableFormat: true
|
||||||
|
SortIncludes: Never
|
||||||
4
source/external/.clang-tidy
vendored
Normal file
4
source/external/.clang-tidy
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# source/external/.clang-tidy
|
||||||
|
Checks: '-*'
|
||||||
|
WarningsAsErrors: ''
|
||||||
|
HeaderFilterRegex: ''
|
||||||
14726
source/external/fkyaml_node.hpp
vendored
Normal file
14726
source/external/fkyaml_node.hpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
117
source/stb_vorbis.c → source/external/stb_vorbis.c
vendored
117
source/stb_vorbis.c → source/external/stb_vorbis.c
vendored
@@ -1,4 +1,4 @@
|
|||||||
// Ogg Vorbis audio decoder - v1.20 - public domain
|
// Ogg Vorbis audio decoder - v1.22 - public domain
|
||||||
// http://nothings.org/stb_vorbis/
|
// http://nothings.org/stb_vorbis/
|
||||||
//
|
//
|
||||||
// Original version written by Sean Barrett in 2007.
|
// Original version written by Sean Barrett in 2007.
|
||||||
@@ -29,12 +29,15 @@
|
|||||||
// Bernhard Wodo Evan Balster github:alxprd
|
// Bernhard Wodo Evan Balster github:alxprd
|
||||||
// Tom Beaumont Ingo Leitgeb Nicolas Guillemot
|
// Tom Beaumont Ingo Leitgeb Nicolas Guillemot
|
||||||
// Phillip Bennefall Rohit Thiago Goulart
|
// Phillip Bennefall Rohit Thiago Goulart
|
||||||
// github:manxorist saga musix github:infatum
|
// github:manxorist Saga Musix github:infatum
|
||||||
// Timur Gagiev Maxwell Koo Peter Waller
|
// Timur Gagiev Maxwell Koo Peter Waller
|
||||||
// github:audinowho Dougall Johnson David Reid
|
// github:audinowho Dougall Johnson David Reid
|
||||||
// github:Clownacy Pedro J. Estebanez Remi Verschelde
|
// github:Clownacy Pedro J. Estebanez Remi Verschelde
|
||||||
|
// AnthoFoxo github:morlat Gabriel Ravier
|
||||||
//
|
//
|
||||||
// Partial history:
|
// Partial history:
|
||||||
|
// 1.22 - 2021-07-11 - various small fixes
|
||||||
|
// 1.21 - 2021-07-02 - fix bug for files with no comments
|
||||||
// 1.20 - 2020-07-11 - several small fixes
|
// 1.20 - 2020-07-11 - several small fixes
|
||||||
// 1.19 - 2020-02-05 - warnings
|
// 1.19 - 2020-02-05 - warnings
|
||||||
// 1.18 - 2020-02-02 - fix seek bugs; parse header comments; misc warnings etc.
|
// 1.18 - 2020-02-02 - fix seek bugs; parse header comments; misc warnings etc.
|
||||||
@@ -220,6 +223,12 @@ extern int stb_vorbis_decode_frame_pushdata(
|
|||||||
// channel. In other words, (*output)[0][0] contains the first sample from
|
// channel. In other words, (*output)[0][0] contains the first sample from
|
||||||
// the first channel, and (*output)[1][0] contains the first sample from
|
// the first channel, and (*output)[1][0] contains the first sample from
|
||||||
// the second channel.
|
// the second channel.
|
||||||
|
//
|
||||||
|
// *output points into stb_vorbis's internal output buffer storage; these
|
||||||
|
// buffers are owned by stb_vorbis and application code should not free
|
||||||
|
// them or modify their contents. They are transient and will be overwritten
|
||||||
|
// once you ask for more data to get decoded, so be sure to grab any data
|
||||||
|
// you need before then.
|
||||||
|
|
||||||
extern void stb_vorbis_flush_pushdata(stb_vorbis *f);
|
extern void stb_vorbis_flush_pushdata(stb_vorbis *f);
|
||||||
// inform stb_vorbis that your next datablock will not be contiguous with
|
// inform stb_vorbis that your next datablock will not be contiguous with
|
||||||
@@ -579,7 +588,7 @@ enum STBVorbisError
|
|||||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||||
#include <malloc.h>
|
#include <malloc.h>
|
||||||
#endif
|
#endif
|
||||||
#if defined(__linux__) || defined(__linux) || defined(__EMSCRIPTEN__) || defined(__NEWLIB__)
|
#if defined(__linux__) || defined(__linux) || defined(__sun__) || defined(__EMSCRIPTEN__) || defined(__NEWLIB__)
|
||||||
#include <alloca.h>
|
#include <alloca.h>
|
||||||
#endif
|
#endif
|
||||||
#else // STB_VORBIS_NO_CRT
|
#else // STB_VORBIS_NO_CRT
|
||||||
@@ -646,6 +655,12 @@ typedef signed int int32;
|
|||||||
|
|
||||||
typedef float codetype;
|
typedef float codetype;
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define STBV_NOTUSED(v) (void)(v)
|
||||||
|
#else
|
||||||
|
#define STBV_NOTUSED(v) (void)sizeof(v)
|
||||||
|
#endif
|
||||||
|
|
||||||
// @NOTE
|
// @NOTE
|
||||||
//
|
//
|
||||||
// Some arrays below are tagged "//varies", which means it's actually
|
// Some arrays below are tagged "//varies", which means it's actually
|
||||||
@@ -1046,7 +1061,7 @@ static float float32_unpack(uint32 x)
|
|||||||
uint32 sign = x & 0x80000000;
|
uint32 sign = x & 0x80000000;
|
||||||
uint32 exp = (x & 0x7fe00000) >> 21;
|
uint32 exp = (x & 0x7fe00000) >> 21;
|
||||||
double res = sign ? -(double)mantissa : (double)mantissa;
|
double res = sign ? -(double)mantissa : (double)mantissa;
|
||||||
return (float) ldexp((float)res, exp-788);
|
return (float) ldexp((float)res, (int)exp-788);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1077,6 +1092,7 @@ static int compute_codewords(Codebook *c, uint8 *len, int n, uint32 *values)
|
|||||||
// find the first entry
|
// find the first entry
|
||||||
for (k=0; k < n; ++k) if (len[k] < NO_CODE) break;
|
for (k=0; k < n; ++k) if (len[k] < NO_CODE) break;
|
||||||
if (k == n) { assert(c->sorted_entries == 0); return TRUE; }
|
if (k == n) { assert(c->sorted_entries == 0); return TRUE; }
|
||||||
|
assert(len[k] < 32); // no error return required, code reading lens checks this
|
||||||
// add to the list
|
// add to the list
|
||||||
add_entry(c, 0, k, m++, len[k], values);
|
add_entry(c, 0, k, m++, len[k], values);
|
||||||
// add all available leaves
|
// add all available leaves
|
||||||
@@ -1090,6 +1106,7 @@ static int compute_codewords(Codebook *c, uint8 *len, int n, uint32 *values)
|
|||||||
uint32 res;
|
uint32 res;
|
||||||
int z = len[i], y;
|
int z = len[i], y;
|
||||||
if (z == NO_CODE) continue;
|
if (z == NO_CODE) continue;
|
||||||
|
assert(z < 32); // no error return required, code reading lens checks this
|
||||||
// find lowest available leaf (should always be earliest,
|
// find lowest available leaf (should always be earliest,
|
||||||
// which is what the specification calls for)
|
// which is what the specification calls for)
|
||||||
// note that this property, and the fact we can never have
|
// note that this property, and the fact we can never have
|
||||||
@@ -1099,12 +1116,10 @@ static int compute_codewords(Codebook *c, uint8 *len, int n, uint32 *values)
|
|||||||
while (z > 0 && !available[z]) --z;
|
while (z > 0 && !available[z]) --z;
|
||||||
if (z == 0) { return FALSE; }
|
if (z == 0) { return FALSE; }
|
||||||
res = available[z];
|
res = available[z];
|
||||||
assert(z >= 0 && z < 32);
|
|
||||||
available[z] = 0;
|
available[z] = 0;
|
||||||
add_entry(c, bit_reverse(res), i, m++, len[i], values);
|
add_entry(c, bit_reverse(res), i, m++, len[i], values);
|
||||||
// propagate availability up the tree
|
// propagate availability up the tree
|
||||||
if (z != len[i]) {
|
if (z != len[i]) {
|
||||||
assert(len[i] >= 0 && len[i] < 32);
|
|
||||||
for (y=len[i]; y > z; --y) {
|
for (y=len[i]; y > z; --y) {
|
||||||
assert(available[y] == 0);
|
assert(available[y] == 0);
|
||||||
available[y] = res + (1 << (32-y));
|
available[y] = res + (1 << (32-y));
|
||||||
@@ -2577,34 +2592,33 @@ static void imdct_step3_inner_s_loop_ld654(int n, float *e, int i_off, float *A,
|
|||||||
|
|
||||||
while (z > base) {
|
while (z > base) {
|
||||||
float k00,k11;
|
float k00,k11;
|
||||||
|
float l00,l11;
|
||||||
|
|
||||||
k00 = z[-0] - z[-8];
|
k00 = z[-0] - z[ -8];
|
||||||
k11 = z[-1] - z[-9];
|
k11 = z[-1] - z[ -9];
|
||||||
z[-0] = z[-0] + z[-8];
|
l00 = z[-2] - z[-10];
|
||||||
z[-1] = z[-1] + z[-9];
|
l11 = z[-3] - z[-11];
|
||||||
z[-8] = k00;
|
z[ -0] = z[-0] + z[ -8];
|
||||||
z[-9] = k11 ;
|
z[ -1] = z[-1] + z[ -9];
|
||||||
|
z[ -2] = z[-2] + z[-10];
|
||||||
|
z[ -3] = z[-3] + z[-11];
|
||||||
|
z[ -8] = k00;
|
||||||
|
z[ -9] = k11;
|
||||||
|
z[-10] = (l00+l11) * A2;
|
||||||
|
z[-11] = (l11-l00) * A2;
|
||||||
|
|
||||||
k00 = z[ -2] - z[-10];
|
k00 = z[ -4] - z[-12];
|
||||||
k11 = z[ -3] - z[-11];
|
|
||||||
z[ -2] = z[ -2] + z[-10];
|
|
||||||
z[ -3] = z[ -3] + z[-11];
|
|
||||||
z[-10] = (k00+k11) * A2;
|
|
||||||
z[-11] = (k11-k00) * A2;
|
|
||||||
|
|
||||||
k00 = z[-12] - z[ -4]; // reverse to avoid a unary negation
|
|
||||||
k11 = z[ -5] - z[-13];
|
k11 = z[ -5] - z[-13];
|
||||||
|
l00 = z[ -6] - z[-14];
|
||||||
|
l11 = z[ -7] - z[-15];
|
||||||
z[ -4] = z[ -4] + z[-12];
|
z[ -4] = z[ -4] + z[-12];
|
||||||
z[ -5] = z[ -5] + z[-13];
|
z[ -5] = z[ -5] + z[-13];
|
||||||
z[-12] = k11;
|
|
||||||
z[-13] = k00;
|
|
||||||
|
|
||||||
k00 = z[-14] - z[ -6]; // reverse to avoid a unary negation
|
|
||||||
k11 = z[ -7] - z[-15];
|
|
||||||
z[ -6] = z[ -6] + z[-14];
|
z[ -6] = z[ -6] + z[-14];
|
||||||
z[ -7] = z[ -7] + z[-15];
|
z[ -7] = z[ -7] + z[-15];
|
||||||
z[-14] = (k00+k11) * A2;
|
z[-12] = k11;
|
||||||
z[-15] = (k00-k11) * A2;
|
z[-13] = -k00;
|
||||||
|
z[-14] = (l11-l00) * A2;
|
||||||
|
z[-15] = (l00+l11) * -A2;
|
||||||
|
|
||||||
iter_54(z);
|
iter_54(z);
|
||||||
iter_54(z-8);
|
iter_54(z-8);
|
||||||
@@ -3069,6 +3083,7 @@ static int do_floor(vorb *f, Mapping *map, int i, int n, float *target, YTYPE *f
|
|||||||
for (q=1; q < g->values; ++q) {
|
for (q=1; q < g->values; ++q) {
|
||||||
j = g->sorted_order[q];
|
j = g->sorted_order[q];
|
||||||
#ifndef STB_VORBIS_NO_DEFER_FLOOR
|
#ifndef STB_VORBIS_NO_DEFER_FLOOR
|
||||||
|
STBV_NOTUSED(step2_flag);
|
||||||
if (finalY[j] >= 0)
|
if (finalY[j] >= 0)
|
||||||
#else
|
#else
|
||||||
if (step2_flag[j])
|
if (step2_flag[j])
|
||||||
@@ -3171,6 +3186,7 @@ static int vorbis_decode_packet_rest(vorb *f, int *len, Mode *m, int left_start,
|
|||||||
|
|
||||||
// WINDOWING
|
// WINDOWING
|
||||||
|
|
||||||
|
STBV_NOTUSED(left_end);
|
||||||
n = f->blocksize[m->blockflag];
|
n = f->blocksize[m->blockflag];
|
||||||
map = &f->mapping[m->mapping];
|
map = &f->mapping[m->mapping];
|
||||||
|
|
||||||
@@ -3368,7 +3384,7 @@ static int vorbis_decode_packet_rest(vorb *f, int *len, Mode *m, int left_start,
|
|||||||
// this isn't to spec, but spec would require us to read ahead
|
// this isn't to spec, but spec would require us to read ahead
|
||||||
// and decode the size of all current frames--could be done,
|
// and decode the size of all current frames--could be done,
|
||||||
// but presumably it's not a commonly used feature
|
// but presumably it's not a commonly used feature
|
||||||
f->current_loc = -n2; // start of first frame is positioned for discard
|
f->current_loc = 0u - n2; // start of first frame is positioned for discard (NB this is an intentional unsigned overflow/wrap-around)
|
||||||
// we might have to discard samples "from" the next frame too,
|
// we might have to discard samples "from" the next frame too,
|
||||||
// if we're lapping a large block then a small at the start?
|
// if we're lapping a large block then a small at the start?
|
||||||
f->discard_samples_deferred = n - right_end;
|
f->discard_samples_deferred = n - right_end;
|
||||||
@@ -3642,9 +3658,11 @@ static int start_decoder(vorb *f)
|
|||||||
f->vendor[len] = (char)'\0';
|
f->vendor[len] = (char)'\0';
|
||||||
//user comments
|
//user comments
|
||||||
f->comment_list_length = get32_packet(f);
|
f->comment_list_length = get32_packet(f);
|
||||||
if (f->comment_list_length > 0) {
|
f->comment_list = NULL;
|
||||||
f->comment_list = (char**)setup_malloc(f, sizeof(char*) * (f->comment_list_length));
|
if (f->comment_list_length > 0)
|
||||||
if (f->comment_list == NULL) return error(f, VORBIS_outofmem);
|
{
|
||||||
|
f->comment_list = (char**) setup_malloc(f, sizeof(char*) * (f->comment_list_length));
|
||||||
|
if (f->comment_list == NULL) return error(f, VORBIS_outofmem);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(i=0; i < f->comment_list_length; ++i) {
|
for(i=0; i < f->comment_list_length; ++i) {
|
||||||
@@ -3867,8 +3885,7 @@ static int start_decoder(vorb *f)
|
|||||||
unsigned int div=1;
|
unsigned int div=1;
|
||||||
for (k=0; k < c->dimensions; ++k) {
|
for (k=0; k < c->dimensions; ++k) {
|
||||||
int off = (z / div) % c->lookup_values;
|
int off = (z / div) % c->lookup_values;
|
||||||
float val = mults[off];
|
float val = mults[off]*c->delta_value + c->minimum_value + last;
|
||||||
val = mults[off]*c->delta_value + c->minimum_value + last;
|
|
||||||
c->multiplicands[j*c->dimensions + k] = val;
|
c->multiplicands[j*c->dimensions + k] = val;
|
||||||
if (c->sequence_p)
|
if (c->sequence_p)
|
||||||
last = val;
|
last = val;
|
||||||
@@ -3951,7 +3968,7 @@ static int start_decoder(vorb *f)
|
|||||||
if (g->class_masterbooks[j] >= f->codebook_count) return error(f, VORBIS_invalid_setup);
|
if (g->class_masterbooks[j] >= f->codebook_count) return error(f, VORBIS_invalid_setup);
|
||||||
}
|
}
|
||||||
for (k=0; k < 1 << g->class_subclasses[j]; ++k) {
|
for (k=0; k < 1 << g->class_subclasses[j]; ++k) {
|
||||||
g->subclass_books[j][k] = get_bits(f,8)-1;
|
g->subclass_books[j][k] = (int16)get_bits(f,8)-1;
|
||||||
if (g->subclass_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup);
|
if (g->subclass_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4509,6 +4526,7 @@ stb_vorbis *stb_vorbis_open_pushdata(
|
|||||||
*error = VORBIS_need_more_data;
|
*error = VORBIS_need_more_data;
|
||||||
else
|
else
|
||||||
*error = p.error;
|
*error = p.error;
|
||||||
|
vorbis_deinit(&p);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
f = vorbis_alloc(&p);
|
f = vorbis_alloc(&p);
|
||||||
@@ -4566,7 +4584,7 @@ static uint32 vorbis_find_page(stb_vorbis *f, uint32 *end, uint32 *last)
|
|||||||
header[i] = get8(f);
|
header[i] = get8(f);
|
||||||
if (f->eof) return 0;
|
if (f->eof) return 0;
|
||||||
if (header[4] != 0) goto invalid;
|
if (header[4] != 0) goto invalid;
|
||||||
goal = header[22] + (header[23] << 8) + (header[24]<<16) + (header[25]<<24);
|
goal = header[22] + (header[23] << 8) + (header[24]<<16) + ((uint32)header[25]<<24);
|
||||||
for (i=22; i < 26; ++i)
|
for (i=22; i < 26; ++i)
|
||||||
header[i] = 0;
|
header[i] = 0;
|
||||||
crc = 0;
|
crc = 0;
|
||||||
@@ -4970,7 +4988,7 @@ unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f)
|
|||||||
// set. whoops!
|
// set. whoops!
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
previous_safe = last_page_loc+1;
|
//previous_safe = last_page_loc+1; // NOTE: not used after this point, but note for debugging
|
||||||
last_page_loc = stb_vorbis_get_file_offset(f);
|
last_page_loc = stb_vorbis_get_file_offset(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5081,7 +5099,10 @@ stb_vorbis * stb_vorbis_open_filename(const char *filename, int *error, const st
|
|||||||
stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, int *error, const stb_vorbis_alloc *alloc)
|
stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, int *error, const stb_vorbis_alloc *alloc)
|
||||||
{
|
{
|
||||||
stb_vorbis *f, p;
|
stb_vorbis *f, p;
|
||||||
if (data == NULL) return NULL;
|
if (!data) {
|
||||||
|
if (error) *error = VORBIS_unexpected_eof;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
vorbis_init(&p, alloc);
|
vorbis_init(&p, alloc);
|
||||||
p.stream = (uint8 *) data;
|
p.stream = (uint8 *) data;
|
||||||
p.stream_end = (uint8 *) data + len;
|
p.stream_end = (uint8 *) data + len;
|
||||||
@@ -5156,11 +5177,11 @@ static void copy_samples(short *dest, float *src, int len)
|
|||||||
|
|
||||||
static void compute_samples(int mask, short *output, int num_c, float **data, int d_offset, int len)
|
static void compute_samples(int mask, short *output, int num_c, float **data, int d_offset, int len)
|
||||||
{
|
{
|
||||||
#define BUFFER_SIZE 32
|
#define STB_BUFFER_SIZE 32
|
||||||
float buffer[BUFFER_SIZE];
|
float buffer[STB_BUFFER_SIZE];
|
||||||
int i,j,o,n = BUFFER_SIZE;
|
int i,j,o,n = STB_BUFFER_SIZE;
|
||||||
check_endianness();
|
check_endianness();
|
||||||
for (o = 0; o < len; o += BUFFER_SIZE) {
|
for (o = 0; o < len; o += STB_BUFFER_SIZE) {
|
||||||
memset(buffer, 0, sizeof(buffer));
|
memset(buffer, 0, sizeof(buffer));
|
||||||
if (o + n > len) n = len - o;
|
if (o + n > len) n = len - o;
|
||||||
for (j=0; j < num_c; ++j) {
|
for (j=0; j < num_c; ++j) {
|
||||||
@@ -5177,16 +5198,17 @@ static void compute_samples(int mask, short *output, int num_c, float **data, in
|
|||||||
output[o+i] = v;
|
output[o+i] = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#undef STB_BUFFER_SIZE
|
||||||
}
|
}
|
||||||
|
|
||||||
static void compute_stereo_samples(short *output, int num_c, float **data, int d_offset, int len)
|
static void compute_stereo_samples(short *output, int num_c, float **data, int d_offset, int len)
|
||||||
{
|
{
|
||||||
#define BUFFER_SIZE 32
|
#define STB_BUFFER_SIZE 32
|
||||||
float buffer[BUFFER_SIZE];
|
float buffer[STB_BUFFER_SIZE];
|
||||||
int i,j,o,n = BUFFER_SIZE >> 1;
|
int i,j,o,n = STB_BUFFER_SIZE >> 1;
|
||||||
// o is the offset in the source data
|
// o is the offset in the source data
|
||||||
check_endianness();
|
check_endianness();
|
||||||
for (o = 0; o < len; o += BUFFER_SIZE >> 1) {
|
for (o = 0; o < len; o += STB_BUFFER_SIZE >> 1) {
|
||||||
// o2 is the offset in the output data
|
// o2 is the offset in the output data
|
||||||
int o2 = o << 1;
|
int o2 = o << 1;
|
||||||
memset(buffer, 0, sizeof(buffer));
|
memset(buffer, 0, sizeof(buffer));
|
||||||
@@ -5216,6 +5238,7 @@ static void compute_stereo_samples(short *output, int num_c, float **data, int d
|
|||||||
output[o2+i] = v;
|
output[o2+i] = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#undef STB_BUFFER_SIZE
|
||||||
}
|
}
|
||||||
|
|
||||||
static void convert_samples_short(int buf_c, short **buffer, int b_offset, int data_c, float **data, int d_offset, int samples)
|
static void convert_samples_short(int buf_c, short **buffer, int b_offset, int data_c, float **data, int d_offset, int samples)
|
||||||
@@ -5288,8 +5311,6 @@ int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short
|
|||||||
float **outputs;
|
float **outputs;
|
||||||
int len = num_shorts / channels;
|
int len = num_shorts / channels;
|
||||||
int n=0;
|
int n=0;
|
||||||
int z = f->channels;
|
|
||||||
if (z > channels) z = channels;
|
|
||||||
while (n < len) {
|
while (n < len) {
|
||||||
int k = f->channel_buffer_end - f->channel_buffer_start;
|
int k = f->channel_buffer_end - f->channel_buffer_start;
|
||||||
if (n+k >= len) k = len - n;
|
if (n+k >= len) k = len - n;
|
||||||
@@ -5308,8 +5329,6 @@ int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, in
|
|||||||
{
|
{
|
||||||
float **outputs;
|
float **outputs;
|
||||||
int n=0;
|
int n=0;
|
||||||
int z = f->channels;
|
|
||||||
if (z > channels) z = channels;
|
|
||||||
while (n < len) {
|
while (n < len) {
|
||||||
int k = f->channel_buffer_end - f->channel_buffer_start;
|
int k = f->channel_buffer_end - f->channel_buffer_start;
|
||||||
if (n+k >= len) k = len - n;
|
if (n+k >= len) k = len - n;
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
// Tipos de fundido
|
|
||||||
constexpr int FADE_FULLSCREEN = 0;
|
|
||||||
constexpr int FADE_CENTER = 1;
|
|
||||||
constexpr int FADE_RANDOM_SQUARE = 2;
|
|
||||||
|
|
||||||
// Clase Fade
|
|
||||||
class Fade {
|
|
||||||
private:
|
|
||||||
SDL_Renderer *mRenderer; // El renderizador de la ventana
|
|
||||||
SDL_Texture *mBackbuffer; // Textura para usar como backbuffer
|
|
||||||
Uint8 mFadeType; // Tipo de fade a realizar
|
|
||||||
Uint16 mCounter; // Contador interno
|
|
||||||
bool mEnabled; // Indica si el fade está activo
|
|
||||||
bool mFinished; // Indica si ha terminado la transición
|
|
||||||
Uint8 mR, mG, mB; // Colores para el fade
|
|
||||||
SDL_Rect mRect1; // Rectangulo usado para crear los efectos de transición
|
|
||||||
SDL_Rect mRect2; // Rectangulo usado para crear los efectos de transición
|
|
||||||
|
|
||||||
public:
|
|
||||||
// Constructor
|
|
||||||
Fade(SDL_Renderer *renderer);
|
|
||||||
|
|
||||||
// Destructor
|
|
||||||
~Fade();
|
|
||||||
|
|
||||||
// Inicializa las variables
|
|
||||||
void init(Uint8 r, Uint8 g, Uint8 b);
|
|
||||||
|
|
||||||
// Pinta una transición en pantalla
|
|
||||||
void render();
|
|
||||||
|
|
||||||
// Actualiza las variables internas
|
|
||||||
void update();
|
|
||||||
|
|
||||||
// Activa el fade
|
|
||||||
void activateFade();
|
|
||||||
|
|
||||||
// Comprueba si ha terminado la transicion
|
|
||||||
bool hasEnded();
|
|
||||||
|
|
||||||
// Comprueba si está activo
|
|
||||||
bool isEnabled();
|
|
||||||
|
|
||||||
// Establece el tipo de fade
|
|
||||||
void setFadeType(Uint8 fadeType);
|
|
||||||
};
|
|
||||||
@@ -2,8 +2,58 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include "lang.h"
|
#include "core/locale/lang.h"
|
||||||
#include "utils.h"
|
#include "utils/utils.h"
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Defaults per a Options (alineats amb coffee_crisis_arcade_edition).
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
namespace Defaults::Window {
|
||||||
|
constexpr const char *CAPTION = "© 2020 Coffee Crisis — JailDesigner";
|
||||||
|
constexpr int ZOOM = 3;
|
||||||
|
constexpr int MAX_ZOOM = 4;
|
||||||
|
} // namespace Defaults::Window
|
||||||
|
|
||||||
|
namespace Defaults::Video {
|
||||||
|
constexpr SDL_ScaleMode SCALE_MODE = SDL_ScaleMode::SDL_SCALEMODE_NEAREST;
|
||||||
|
constexpr bool FULLSCREEN = false;
|
||||||
|
constexpr bool VSYNC = true;
|
||||||
|
constexpr bool INTEGER_SCALE = true;
|
||||||
|
constexpr bool GPU_ACCELERATION = true;
|
||||||
|
constexpr const char *GPU_PREFERRED_DRIVER = "";
|
||||||
|
constexpr bool SHADER_ENABLED = false;
|
||||||
|
constexpr bool SUPERSAMPLING = false;
|
||||||
|
constexpr bool LINEAR_UPSCALE = false;
|
||||||
|
constexpr int DOWNSCALE_ALGO = 1;
|
||||||
|
} // namespace Defaults::Video
|
||||||
|
|
||||||
|
namespace Defaults::Audio {
|
||||||
|
constexpr bool ENABLED = true;
|
||||||
|
constexpr float VOLUME = 1.0F;
|
||||||
|
} // namespace Defaults::Audio
|
||||||
|
|
||||||
|
namespace Defaults::Music {
|
||||||
|
constexpr bool ENABLED = true;
|
||||||
|
constexpr float VOLUME = 0.8F;
|
||||||
|
} // namespace Defaults::Music
|
||||||
|
|
||||||
|
namespace Defaults::Sound {
|
||||||
|
constexpr bool ENABLED = true;
|
||||||
|
constexpr float VOLUME = 1.0F;
|
||||||
|
} // namespace Defaults::Sound
|
||||||
|
|
||||||
|
namespace Defaults::Loading {
|
||||||
|
constexpr bool SHOW = false;
|
||||||
|
constexpr bool SHOW_RESOURCE_NAME = true;
|
||||||
|
constexpr bool WAIT_FOR_INPUT = false;
|
||||||
|
} // namespace Defaults::Loading
|
||||||
|
|
||||||
|
namespace Defaults::Settings {
|
||||||
|
constexpr int DIFFICULTY = DIFFICULTY_NORMAL;
|
||||||
|
constexpr int LANGUAGE = ba_BA;
|
||||||
|
constexpr palette_e PALETTE = p_zxspectrum;
|
||||||
|
} // namespace Defaults::Settings
|
||||||
|
|
||||||
// Tamaño de bloque
|
// Tamaño de bloque
|
||||||
constexpr int BLOCK = 8;
|
constexpr int BLOCK = 8;
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
#include "balloon.h"
|
#include "game/entities/balloon.h"
|
||||||
|
|
||||||
#include <math.h> // for abs
|
#include <math.h> // for abs
|
||||||
|
|
||||||
#include "animatedsprite.h" // for AnimatedSprite
|
#include "core/rendering/animatedsprite.h" // for AnimatedSprite
|
||||||
#include "const.h" // for PLAY_AREA_LEFT, PLAY_AREA_RIGHT, PLAY_AR...
|
#include "core/rendering/movingsprite.h" // for MovingSprite
|
||||||
#include "movingsprite.h" // for MovingSprite
|
#include "core/rendering/sprite.h" // for Sprite
|
||||||
#include "sprite.h" // for Sprite
|
#include "core/rendering/texture.h" // for Texture
|
||||||
#include "texture.h" // for Texture
|
#include "game/defaults.hpp" // for PLAY_AREA_LEFT, PLAY_AREA_RIGHT, PLAY_AR...
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Balloon::Balloon(float x, float y, Uint8 kind, float velx, float speed, Uint16 creationtimer, Texture *texture, std::vector<std::string> *animation, SDL_Renderer *renderer) {
|
Balloon::Balloon(float x, float y, Uint8 kind, float velx, float speed, Uint16 creationtimer, Texture *texture, std::vector<std::string> *animation, SDL_Renderer *renderer) {
|
||||||
@@ -261,15 +261,13 @@ Balloon::Balloon(float x, float y, Uint8 kind, float velx, float speed, Uint16 c
|
|||||||
stoppedCounter = 0;
|
stoppedCounter = 0;
|
||||||
blinking = false;
|
blinking = false;
|
||||||
visible = true;
|
visible = true;
|
||||||
invulnerable = true;
|
|
||||||
beingCreated = true;
|
|
||||||
creationCounter = creationtimer;
|
creationCounter = creationtimer;
|
||||||
creationCounterIni = creationtimer;
|
creationCounterIni = creationtimer;
|
||||||
popping = false;
|
popping = false;
|
||||||
|
|
||||||
// Actualiza valores
|
// Valores iniciales dependentes del timer
|
||||||
beingCreated = creationCounter == 0 ? false : true;
|
beingCreated = creationCounter != 0;
|
||||||
invulnerable = beingCreated == false ? false : true;
|
invulnerable = beingCreated;
|
||||||
|
|
||||||
counter = 0;
|
counter = 0;
|
||||||
travelY = 1.0f;
|
travelY = 1.0f;
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
#include <string> // for string
|
#include <string> // for string
|
||||||
#include <vector> // for vector
|
#include <vector> // for vector
|
||||||
|
|
||||||
#include "utils.h" // for circle_t
|
#include "utils/utils.h" // for circle_t
|
||||||
class AnimatedSprite;
|
class AnimatedSprite;
|
||||||
class Texture;
|
class Texture;
|
||||||
|
|
||||||
@@ -146,6 +146,9 @@ class Balloon {
|
|||||||
// Destructor
|
// Destructor
|
||||||
~Balloon();
|
~Balloon();
|
||||||
|
|
||||||
|
Balloon(const Balloon &) = delete;
|
||||||
|
Balloon &operator=(const Balloon &) = delete;
|
||||||
|
|
||||||
// Centra el globo en la posición X
|
// Centra el globo en la posición X
|
||||||
void allignTo(int x);
|
void allignTo(int x);
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "bullet.h"
|
#include "game/entities/bullet.h"
|
||||||
|
|
||||||
#include "const.h" // for NO_KIND, PLAY_AREA_LEFT, PLAY_AREA_RIGHT, PLAY_A...
|
#include "core/rendering/sprite.h" // for Sprite
|
||||||
#include "sprite.h" // for Sprite
|
#include "game/defaults.hpp" // for NO_KIND, PLAY_AREA_LEFT, PLAY_AREA_RIGHT, PLAY_A...
|
||||||
class Texture;
|
class Texture;
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include "utils.h" // for circle_t
|
#include "utils/utils.h" // for circle_t
|
||||||
class Sprite;
|
class Sprite;
|
||||||
class Texture;
|
class Texture;
|
||||||
|
|
||||||
@@ -42,6 +42,9 @@ class Bullet {
|
|||||||
// Destructor
|
// Destructor
|
||||||
~Bullet();
|
~Bullet();
|
||||||
|
|
||||||
|
Bullet(const Bullet &) = delete;
|
||||||
|
Bullet &operator=(const Bullet &) = delete;
|
||||||
|
|
||||||
// Pinta el objeto en pantalla
|
// Pinta el objeto en pantalla
|
||||||
void render();
|
void render();
|
||||||
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
#include "item.h"
|
#include "game/entities/item.h"
|
||||||
|
|
||||||
#include <stdlib.h> // for rand
|
#include <stdlib.h> // for rand
|
||||||
|
|
||||||
#include "animatedsprite.h" // for AnimatedSprite
|
#include "core/rendering/animatedsprite.h" // for AnimatedSprite
|
||||||
#include "const.h" // for PLAY_AREA_LEFT, PLAY_AREA_RIGHT, PLAY_AR...
|
#include "game/defaults.hpp" // for PLAY_AREA_LEFT, PLAY_AREA_RIGHT, PLAY_AR...
|
||||||
class Texture;
|
class Texture;
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
@@ -107,9 +107,6 @@ void Item::move() {
|
|||||||
|
|
||||||
// Si el objeto se sale por la parte inferior
|
// Si el objeto se sale por la parte inferior
|
||||||
if (posY + height > PLAY_AREA_BOTTOM) {
|
if (posY + height > PLAY_AREA_BOTTOM) {
|
||||||
// Corrige
|
|
||||||
posY -= velY;
|
|
||||||
|
|
||||||
// Detiene el objeto
|
// Detiene el objeto
|
||||||
velY = 0;
|
velY = 0;
|
||||||
velX = 0;
|
velX = 0;
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
#include <string> // for string
|
#include <string> // for string
|
||||||
#include <vector> // for vector
|
#include <vector> // for vector
|
||||||
|
|
||||||
#include "utils.h" // for circle_t
|
#include "utils/utils.h" // for circle_t
|
||||||
class AnimatedSprite;
|
class AnimatedSprite;
|
||||||
class Texture;
|
class Texture;
|
||||||
|
|
||||||
@@ -52,6 +52,9 @@ class Item {
|
|||||||
// Destructor
|
// Destructor
|
||||||
~Item();
|
~Item();
|
||||||
|
|
||||||
|
Item(const Item &) = delete;
|
||||||
|
Item &operator=(const Item &) = delete;
|
||||||
|
|
||||||
// Centra el objeto en la posición X
|
// Centra el objeto en la posición X
|
||||||
void allignTo(int x);
|
void allignTo(int x);
|
||||||
|
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
#include "player.h"
|
#include "game/entities/player.h"
|
||||||
|
|
||||||
#include <stdlib.h> // for rand
|
#include <stdlib.h> // for rand
|
||||||
|
|
||||||
#include "animatedsprite.h" // for AnimatedSprite
|
#include "core/input/input.h" // for inputs_e
|
||||||
#include "const.h" // for PLAY_AREA_LEFT, PLAY_AREA_RIGHT
|
#include "core/rendering/animatedsprite.h" // for AnimatedSprite
|
||||||
#include "input.h" // for inputs_e
|
#include "core/rendering/texture.h" // for Texture
|
||||||
#include "texture.h" // for Texture
|
#include "game/defaults.hpp" // for PLAY_AREA_LEFT, PLAY_AREA_RIGHT
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Player::Player(float x, int y, SDL_Renderer *renderer, std::vector<Texture *> texture, std::vector<std::vector<std::string> *> animations) {
|
Player::Player(float x, int y, SDL_Renderer *renderer, const std::vector<Texture *> &texture, const std::vector<std::vector<std::string> *> &animations) {
|
||||||
// Copia los punteros
|
// Copia los punteros
|
||||||
this->renderer = renderer;
|
this->renderer = renderer;
|
||||||
|
|
||||||
@@ -189,19 +189,12 @@ void Player::render() {
|
|||||||
|
|
||||||
// Establece el estado del jugador cuando camina
|
// Establece el estado del jugador cuando camina
|
||||||
void Player::setWalkingStatus(Uint8 status) {
|
void Player::setWalkingStatus(Uint8 status) {
|
||||||
// Si cambiamos de estado, reiniciamos la animación
|
statusWalking = status;
|
||||||
if (statusWalking != status) {
|
|
||||||
statusWalking = status;
|
|
||||||
// legsSprite->setCurrentFrame(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Establece el estado del jugador cuando dispara
|
// Establece el estado del jugador cuando dispara
|
||||||
void Player::setFiringStatus(Uint8 status) {
|
void Player::setFiringStatus(Uint8 status) {
|
||||||
// Si cambiamos de estado, reiniciamos la animación
|
statusFiring = status;
|
||||||
if (statusFiring != status) {
|
|
||||||
statusFiring = status;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Establece la animación correspondiente al estado
|
// Establece la animación correspondiente al estado
|
||||||
@@ -521,7 +514,7 @@ void Player::updatePowerUpHeadOffset() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pone las texturas del jugador
|
// Pone las texturas del jugador
|
||||||
void Player::setPlayerTextures(std::vector<Texture *> texture) {
|
void Player::setPlayerTextures(const std::vector<Texture *> &texture) {
|
||||||
headSprite->setTexture(texture[0]);
|
headSprite->setTexture(texture[0]);
|
||||||
bodySprite->setTexture(texture[1]);
|
bodySprite->setTexture(texture[1]);
|
||||||
legsSprite->setTexture(texture[2]);
|
legsSprite->setTexture(texture[2]);
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
#include <string> // for string
|
#include <string> // for string
|
||||||
#include <vector> // for vector
|
#include <vector> // for vector
|
||||||
|
|
||||||
#include "utils.h" // for circle_t
|
#include "utils/utils.h" // for circle_t
|
||||||
class AnimatedSprite;
|
class AnimatedSprite;
|
||||||
class Texture;
|
class Texture;
|
||||||
|
|
||||||
@@ -81,11 +81,14 @@ class Player {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
// Constructor
|
// Constructor
|
||||||
Player(float x, int y, SDL_Renderer *renderer, std::vector<Texture *> texture, std::vector<std::vector<std::string> *> animations);
|
Player(float x, int y, SDL_Renderer *renderer, const std::vector<Texture *> &texture, const std::vector<std::vector<std::string> *> &animations);
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
~Player();
|
~Player();
|
||||||
|
|
||||||
|
Player(const Player &) = delete;
|
||||||
|
Player &operator=(const Player &) = delete;
|
||||||
|
|
||||||
// Iniciador
|
// Iniciador
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
@@ -96,7 +99,7 @@ class Player {
|
|||||||
void render();
|
void render();
|
||||||
|
|
||||||
// Pone las texturas del jugador
|
// Pone las texturas del jugador
|
||||||
void setPlayerTextures(std::vector<Texture *> texture);
|
void setPlayerTextures(const std::vector<Texture *> &texture);
|
||||||
|
|
||||||
// Actua en consecuencia de la entrada recibida
|
// Actua en consecuencia de la entrada recibida
|
||||||
void setInput(Uint8 input);
|
void setInput(Uint8 input);
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -5,18 +5,14 @@
|
|||||||
#include <string> // for string, basic_string
|
#include <string> // for string, basic_string
|
||||||
#include <vector> // for vector
|
#include <vector> // for vector
|
||||||
|
|
||||||
#include "utils.h" // for demoKeys_t, color_t
|
#include "utils/utils.h" // for demoKeys_t, color_t
|
||||||
class Asset;
|
|
||||||
class Balloon;
|
class Balloon;
|
||||||
class Bullet;
|
class Bullet;
|
||||||
class Fade;
|
class Fade;
|
||||||
class Input;
|
|
||||||
class Item;
|
class Item;
|
||||||
class Lang;
|
|
||||||
class Menu;
|
class Menu;
|
||||||
class MovingSprite;
|
class MovingSprite;
|
||||||
class Player;
|
class Player;
|
||||||
class Screen;
|
|
||||||
class SmartSprite;
|
class SmartSprite;
|
||||||
class Sprite;
|
class Sprite;
|
||||||
class Text;
|
class Text;
|
||||||
@@ -88,6 +84,26 @@ class Game {
|
|||||||
Uint8 shakeCounter; // Contador para medir el tiempo que dura el efecto
|
Uint8 shakeCounter; // Contador para medir el tiempo que dura el efecto
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Estado para el efecto de agitación intensa (muerte del jugador)
|
||||||
|
struct deathShake_t {
|
||||||
|
bool active; // Indica si el efecto está activo
|
||||||
|
Uint8 step; // Paso actual del efecto (0-7)
|
||||||
|
Uint32 lastStepTicks; // Ticks del último paso
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fases de la secuencia de muerte del jugador
|
||||||
|
enum class DeathPhase { None,
|
||||||
|
Shaking,
|
||||||
|
Waiting,
|
||||||
|
Done };
|
||||||
|
|
||||||
|
// Estado de la secuencia de muerte del jugador
|
||||||
|
struct deathSequence_t {
|
||||||
|
DeathPhase phase; // Fase actual
|
||||||
|
Uint32 phaseStartTicks; // Ticks del inicio de la fase actual
|
||||||
|
Player *player; // Jugador que está muriendo
|
||||||
|
};
|
||||||
|
|
||||||
struct helper_t {
|
struct helper_t {
|
||||||
bool needCoffee; // Indica si se necesitan cafes
|
bool needCoffee; // Indica si se necesitan cafes
|
||||||
bool needCoffeeMachine; // Indica si se necesita PowerUp
|
bool needCoffeeMachine; // Indica si se necesita PowerUp
|
||||||
@@ -111,10 +127,6 @@ class Game {
|
|||||||
|
|
||||||
// Objetos y punteros
|
// Objetos y punteros
|
||||||
SDL_Renderer *renderer; // El renderizador de la ventana
|
SDL_Renderer *renderer; // El renderizador de la ventana
|
||||||
Screen *screen; // Objeto encargado de dibujar en pantalla
|
|
||||||
Asset *asset; // Objeto que gestiona todos los ficheros de recursos
|
|
||||||
Lang *lang; // Objeto para gestionar los textos en diferentes idiomas
|
|
||||||
Input *input; // Manejador de entrada
|
|
||||||
section_t *section; // Seccion actual dentro del juego
|
section_t *section; // Seccion actual dentro del juego
|
||||||
|
|
||||||
std::vector<Player *> players; // Vector con los jugadores
|
std::vector<Player *> players; // Vector con los jugadores
|
||||||
@@ -214,6 +226,8 @@ class Game {
|
|||||||
float enemySpeed; // Velocidad a la que se mueven los enemigos
|
float enemySpeed; // Velocidad a la que se mueven los enemigos
|
||||||
float defaultEnemySpeed; // Velocidad base de los enemigos, sin incrementar
|
float defaultEnemySpeed; // Velocidad base de los enemigos, sin incrementar
|
||||||
effect_t effect; // Variable para gestionar los efectos visuales
|
effect_t effect; // Variable para gestionar los efectos visuales
|
||||||
|
deathShake_t deathShake; // Variable para gestionar el efecto de agitación intensa
|
||||||
|
deathSequence_t deathSequence; // Variable para gestionar la secuencia de muerte
|
||||||
helper_t helper; // Variable para gestionar las ayudas
|
helper_t helper; // Variable para gestionar las ayudas
|
||||||
bool powerBallEnabled; // Indica si hay una powerball ya activa
|
bool powerBallEnabled; // Indica si hay una powerball ya activa
|
||||||
Uint8 powerBallCounter; // Contador de formaciones enemigas entre la aparicion de una PowerBall y otra
|
Uint8 powerBallCounter; // Contador de formaciones enemigas entre la aparicion de una PowerBall y otra
|
||||||
@@ -223,16 +237,18 @@ class Game {
|
|||||||
Uint8 difficulty; // Dificultad del juego
|
Uint8 difficulty; // Dificultad del juego
|
||||||
float difficultyScoreMultiplier; // Multiplicador de puntos en función de la dificultad
|
float difficultyScoreMultiplier; // Multiplicador de puntos en función de la dificultad
|
||||||
color_t difficultyColor; // Color asociado a la dificultad
|
color_t difficultyColor; // Color asociado a la dificultad
|
||||||
struct options_t *options; // Variable con todas las variables de las opciones del programa
|
|
||||||
Uint8 onePlayerControl; // Variable para almacenar el valor de las opciones
|
Uint8 onePlayerControl; // Variable para almacenar el valor de las opciones
|
||||||
enemyFormation_t enemyFormation[NUMBER_OF_ENEMY_FORMATIONS]; // Vector con todas las formaciones enemigas
|
enemyFormation_t enemyFormation[NUMBER_OF_ENEMY_FORMATIONS]; // Vector con todas las formaciones enemigas
|
||||||
enemyPool_t enemyPool[10]; // Variable con los diferentes conjuntos de formaciones enemigas
|
enemyPool_t enemyPool[10]; // Variable con los diferentes conjuntos de formaciones enemigas
|
||||||
Uint8 lastStageReached; // Contiene el numero de la última pantalla que se ha alcanzado
|
Uint8 lastStageReached; // Contiene el numero de la última pantalla que se ha alcanzado
|
||||||
demo_t demo; // Variable con todas las variables relacionadas con el modo demo
|
demo_t demo; // Variable con todas las variables relacionadas con el modo demo
|
||||||
int totalPowerToCompleteGame; // La suma del poder necesario para completar todas las fases
|
int totalPowerToCompleteGame; // La suma del poder necesario para completar todas las fases
|
||||||
int cloudsSpeed; // Velocidad a la que se desplazan las nubes
|
int cloudsSpeed{0}; // Velocidad a la que se desplazan las nubes
|
||||||
int pauseCounter; // Contador para salir del menu de pausa y volver al juego
|
int pauseCounter; // Contador para salir del menu de pausa y volver al juego
|
||||||
bool leavingPauseMenu; // Indica si esta saliendo del menu de pausa para volver al juego
|
bool leavingPauseMenu; // Indica si esta saliendo del menu de pausa para volver al juego
|
||||||
|
bool pauseInitialized; // Indica si la pausa ha sido inicializada
|
||||||
|
bool gameOverInitialized; // Indica si el game over ha sido inicializado
|
||||||
|
int gameOverPostFade; // Opción a realizar cuando termina el fundido del game over
|
||||||
#ifdef PAUSE
|
#ifdef PAUSE
|
||||||
bool pause;
|
bool pause;
|
||||||
#endif
|
#endif
|
||||||
@@ -243,9 +259,6 @@ class Game {
|
|||||||
// Dibuja el juego
|
// Dibuja el juego
|
||||||
void render();
|
void render();
|
||||||
|
|
||||||
// Comprueba los eventos que hay en cola
|
|
||||||
void checkEvents();
|
|
||||||
|
|
||||||
// Inicializa las variables necesarias para la sección 'Game'
|
// Inicializa las variables necesarias para la sección 'Game'
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
@@ -324,9 +337,6 @@ class Game {
|
|||||||
// Incrementa la velocidad de los globos
|
// Incrementa la velocidad de los globos
|
||||||
void incBalloonSpeed();
|
void incBalloonSpeed();
|
||||||
|
|
||||||
// Decrementa la velocidad de los globos
|
|
||||||
void decBalloonSpeed();
|
|
||||||
|
|
||||||
// Actualiza la velocidad de los globos en funcion del poder acumulado de la fase
|
// Actualiza la velocidad de los globos en funcion del poder acumulado de la fase
|
||||||
void updateBalloonSpeed();
|
void updateBalloonSpeed();
|
||||||
|
|
||||||
@@ -336,9 +346,6 @@ class Game {
|
|||||||
// Explosiona un globo. Lo destruye
|
// Explosiona un globo. Lo destruye
|
||||||
void destroyBalloon(Balloon *balloon);
|
void destroyBalloon(Balloon *balloon);
|
||||||
|
|
||||||
// Explosiona todos los globos
|
|
||||||
void popAllBalloons();
|
|
||||||
|
|
||||||
// Destruye todos los globos
|
// Destruye todos los globos
|
||||||
void destroyAllBalloons();
|
void destroyAllBalloons();
|
||||||
|
|
||||||
@@ -348,9 +355,6 @@ class Game {
|
|||||||
// Pone en marcha todos los globos
|
// Pone en marcha todos los globos
|
||||||
void startAllBalloons();
|
void startAllBalloons();
|
||||||
|
|
||||||
// Obtiene el numero de globos activos
|
|
||||||
Uint8 countBalloons();
|
|
||||||
|
|
||||||
// Vacia el vector de globos
|
// Vacia el vector de globos
|
||||||
void freeBalloons();
|
void freeBalloons();
|
||||||
|
|
||||||
@@ -391,7 +395,7 @@ class Game {
|
|||||||
void freeItems();
|
void freeItems();
|
||||||
|
|
||||||
// Crea un objeto SmartSprite
|
// Crea un objeto SmartSprite
|
||||||
void createItemScoreSprite(int x, int y, SmartSprite *sprite);
|
void createItemScoreSprite(int x, int y, const SmartSprite *sprite);
|
||||||
|
|
||||||
// Vacia el vector de smartsprites
|
// Vacia el vector de smartsprites
|
||||||
void freeSmartSprites();
|
void freeSmartSprites();
|
||||||
@@ -459,17 +463,26 @@ class Game {
|
|||||||
// Deshabilita el efecto del item de detener el tiempo
|
// Deshabilita el efecto del item de detener el tiempo
|
||||||
void disableTimeStopItem();
|
void disableTimeStopItem();
|
||||||
|
|
||||||
// Agita la pantalla
|
// Inicia el efecto de agitación intensa de la pantalla
|
||||||
void shakeScreen();
|
void shakeScreen();
|
||||||
|
|
||||||
|
// Actualiza el efecto de agitación intensa
|
||||||
|
void updateDeathShake();
|
||||||
|
|
||||||
|
// Indica si el efecto de agitación intensa está activo
|
||||||
|
bool isDeathShaking();
|
||||||
|
|
||||||
|
// Actualiza la secuencia de muerte del jugador
|
||||||
|
void updateDeathSequence();
|
||||||
|
|
||||||
// Actualiza las variables del menu de pausa del juego
|
// Actualiza las variables del menu de pausa del juego
|
||||||
void updatePausedGame();
|
void updatePausedGame();
|
||||||
|
|
||||||
// Dibuja el menu de pausa del juego
|
// Dibuja el menu de pausa del juego
|
||||||
void renderPausedGame();
|
void renderPausedGame();
|
||||||
|
|
||||||
// Bucle para el menu de pausa del juego
|
// Inicializa el estado de pausa del juego
|
||||||
void runPausedGame();
|
void enterPausedGame();
|
||||||
|
|
||||||
// Actualiza los elementos de la pantalla de game over
|
// Actualiza los elementos de la pantalla de game over
|
||||||
void updateGameOverScreen();
|
void updateGameOverScreen();
|
||||||
@@ -477,8 +490,8 @@ class Game {
|
|||||||
// Dibuja los elementos de la pantalla de game over
|
// Dibuja los elementos de la pantalla de game over
|
||||||
void renderGameOverScreen();
|
void renderGameOverScreen();
|
||||||
|
|
||||||
// Bucle para la pantalla de game over
|
// Inicializa el estado de game over
|
||||||
void runGameOverScreen();
|
void enterGameOverScreen();
|
||||||
|
|
||||||
// Indica si se puede crear una powerball
|
// Indica si se puede crear una powerball
|
||||||
bool canPowerBallBeCreated();
|
bool canPowerBallBeCreated();
|
||||||
@@ -498,25 +511,31 @@ class Game {
|
|||||||
// Comprueba si todos los jugadores han muerto
|
// Comprueba si todos los jugadores han muerto
|
||||||
bool allPlayersAreDead();
|
bool allPlayersAreDead();
|
||||||
|
|
||||||
// Carga las animaciones
|
|
||||||
void loadAnimations(std::string filePath, std::vector<std::string> *buffer);
|
|
||||||
|
|
||||||
// Elimina todos los objetos contenidos en vectores
|
// Elimina todos los objetos contenidos en vectores
|
||||||
void deleteAllVectorObjects();
|
void deleteAllVectorObjects();
|
||||||
|
|
||||||
// Recarga las texturas
|
|
||||||
void reloadTextures();
|
|
||||||
|
|
||||||
// Establece la máxima puntuación desde fichero o desde las puntuaciones online
|
// Establece la máxima puntuación desde fichero o desde las puntuaciones online
|
||||||
void setHiScore();
|
void setHiScore();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Constructor
|
// Constructor
|
||||||
Game(int numPlayers, int currentStage, SDL_Renderer *renderer, Screen *screen, Asset *asset, Lang *lang, Input *input, bool demo, options_t *options, section_t *section);
|
Game(int numPlayers, int currentStage, SDL_Renderer *renderer, bool demo, section_t *section);
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
~Game();
|
~Game();
|
||||||
|
|
||||||
|
Game(const Game &) = delete;
|
||||||
|
Game &operator=(const Game &) = delete;
|
||||||
|
|
||||||
// Bucle para el juego
|
// Bucle para el juego
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
|
// Ejecuta un frame del juego
|
||||||
|
void iterate();
|
||||||
|
|
||||||
|
// Indica si el juego ha terminado
|
||||||
|
bool hasFinished() const;
|
||||||
|
|
||||||
|
// Procesa un evento
|
||||||
|
void handleEvent(const SDL_Event *event);
|
||||||
};
|
};
|
||||||
574
source/game/options.cpp
Normal file
574
source/game/options.cpp
Normal file
@@ -0,0 +1,574 @@
|
|||||||
|
#include "game/options.hpp"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "core/input/input.h" // for INPUT_USE_KEYBOARD, INPUT_USE_GAMECONTROLLER
|
||||||
|
#include "core/locale/lang.h" // for MAX_LANGUAGES, en_UK
|
||||||
|
#include "external/fkyaml_node.hpp" // for fkyaml::node
|
||||||
|
#include "utils/utils.h" // for boolToString
|
||||||
|
|
||||||
|
namespace Options {
|
||||||
|
|
||||||
|
// --- Variables globales ---
|
||||||
|
Window window;
|
||||||
|
Video video;
|
||||||
|
Audio audio;
|
||||||
|
Loading loading;
|
||||||
|
Settings settings;
|
||||||
|
std::vector<input_t> inputs;
|
||||||
|
|
||||||
|
std::vector<PostFXPreset> postfx_presets;
|
||||||
|
std::string postfx_file_path;
|
||||||
|
int current_postfx_preset = 0;
|
||||||
|
|
||||||
|
std::vector<CrtPiPreset> crtpi_presets;
|
||||||
|
std::string crtpi_file_path;
|
||||||
|
int current_crtpi_preset = 0;
|
||||||
|
|
||||||
|
// --- Helpers locals ---
|
||||||
|
namespace {
|
||||||
|
void parseBoolField(const fkyaml::node &node, const std::string &key, bool &target) {
|
||||||
|
if (node.contains(key)) {
|
||||||
|
try {
|
||||||
|
target = node[key].get_value<bool>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void parseIntField(const fkyaml::node &node, const std::string &key, int &target) {
|
||||||
|
if (node.contains(key)) {
|
||||||
|
try {
|
||||||
|
target = node[key].get_value<int>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void parseStringField(const fkyaml::node &node, const std::string &key, std::string &target) {
|
||||||
|
if (node.contains(key)) {
|
||||||
|
try {
|
||||||
|
target = node[key].get_value<std::string>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadWindowFromYaml(const fkyaml::node &yaml) {
|
||||||
|
if (!yaml.contains("window")) { return; }
|
||||||
|
const auto &win = yaml["window"];
|
||||||
|
parseIntField(win, "zoom", window.zoom);
|
||||||
|
if (window.zoom < 1 || window.zoom > window.max_zoom) {
|
||||||
|
window.zoom = Defaults::Window::ZOOM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadVideoFromYaml(const fkyaml::node &yaml) {
|
||||||
|
if (!yaml.contains("video")) { return; }
|
||||||
|
const auto &vid = yaml["video"];
|
||||||
|
|
||||||
|
parseBoolField(vid, "fullscreen", video.fullscreen);
|
||||||
|
parseBoolField(vid, "vsync", video.vsync);
|
||||||
|
parseBoolField(vid, "integer_scale", video.integer_scale);
|
||||||
|
if (vid.contains("scale_mode")) {
|
||||||
|
try {
|
||||||
|
video.scale_mode = static_cast<SDL_ScaleMode>(vid["scale_mode"].get_value<int>());
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vid.contains("gpu")) {
|
||||||
|
const auto &gpu = vid["gpu"];
|
||||||
|
parseBoolField(gpu, "acceleration", video.gpu.acceleration);
|
||||||
|
parseStringField(gpu, "preferred_driver", video.gpu.preferred_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vid.contains("supersampling")) {
|
||||||
|
const auto &ss = vid["supersampling"];
|
||||||
|
parseBoolField(ss, "enabled", video.supersampling.enabled);
|
||||||
|
parseBoolField(ss, "linear_upscale", video.supersampling.linear_upscale);
|
||||||
|
parseIntField(ss, "downscale_algo", video.supersampling.downscale_algo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vid.contains("shader")) {
|
||||||
|
const auto &sh = vid["shader"];
|
||||||
|
parseBoolField(sh, "enabled", video.shader.enabled);
|
||||||
|
if (sh.contains("current_shader")) {
|
||||||
|
try {
|
||||||
|
auto s = sh["current_shader"].get_value<std::string>();
|
||||||
|
video.shader.current_shader = (s == "crtpi" || s == "CRTPI")
|
||||||
|
? Rendering::ShaderType::CRTPI
|
||||||
|
: Rendering::ShaderType::POSTFX;
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
parseStringField(sh, "current_postfx_preset", video.shader.current_postfx_preset_name);
|
||||||
|
parseStringField(sh, "current_crtpi_preset", video.shader.current_crtpi_preset_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadAudioFromYaml(const fkyaml::node &yaml) {
|
||||||
|
if (!yaml.contains("audio")) { return; }
|
||||||
|
const auto &aud = yaml["audio"];
|
||||||
|
|
||||||
|
parseBoolField(aud, "enabled", audio.enabled);
|
||||||
|
if (aud.contains("volume")) {
|
||||||
|
try {
|
||||||
|
audio.volume = std::clamp(aud["volume"].get_value<float>(), 0.0F, 1.0F);
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
if (aud.contains("music")) {
|
||||||
|
const auto &mus = aud["music"];
|
||||||
|
parseBoolField(mus, "enabled", audio.music.enabled);
|
||||||
|
if (mus.contains("volume")) {
|
||||||
|
try {
|
||||||
|
audio.music.volume = std::clamp(mus["volume"].get_value<float>(), 0.0F, 1.0F);
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (aud.contains("sound")) {
|
||||||
|
const auto &snd = aud["sound"];
|
||||||
|
parseBoolField(snd, "enabled", audio.sound.enabled);
|
||||||
|
if (snd.contains("volume")) {
|
||||||
|
try {
|
||||||
|
audio.sound.volume = std::clamp(snd["volume"].get_value<float>(), 0.0F, 1.0F);
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadLoadingFromYaml(const fkyaml::node &yaml) {
|
||||||
|
if (!yaml.contains("loading")) { return; }
|
||||||
|
const auto &ld = yaml["loading"];
|
||||||
|
parseBoolField(ld, "show", loading.show);
|
||||||
|
parseBoolField(ld, "show_resource_name", loading.show_resource_name);
|
||||||
|
parseBoolField(ld, "wait_for_input", loading.wait_for_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadSettingsFromYaml(const fkyaml::node &yaml) {
|
||||||
|
if (!yaml.contains("settings")) { return; }
|
||||||
|
const auto &st = yaml["settings"];
|
||||||
|
parseIntField(st, "difficulty", settings.difficulty);
|
||||||
|
parseIntField(st, "language", settings.language);
|
||||||
|
if (settings.language < 0 || settings.language > MAX_LANGUAGES) {
|
||||||
|
settings.language = en_UK;
|
||||||
|
}
|
||||||
|
if (st.contains("palette")) {
|
||||||
|
try {
|
||||||
|
settings.palette = static_cast<palette_e>(st["palette"].get_value<int>());
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
parseIntField(st, "player_selected", settings.player_selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadInputsFromYaml(const fkyaml::node &yaml) {
|
||||||
|
if (!yaml.contains("input") || inputs.size() < 2) { return; }
|
||||||
|
const auto &ins = yaml["input"];
|
||||||
|
size_t i = 0;
|
||||||
|
for (const auto &entry : ins) {
|
||||||
|
if (i >= inputs.size()) { break; }
|
||||||
|
if (entry.contains("device_type")) {
|
||||||
|
try {
|
||||||
|
inputs[i].deviceType = static_cast<Uint8>(entry["device_type"].get_value<int>());
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// --- Funciones públiques ---
|
||||||
|
|
||||||
|
void setConfigFile(const std::string &file_path) {
|
||||||
|
settings.config_file = file_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
// Reinicia structs a defaults (els member-initializers ho fan sols).
|
||||||
|
window = Window{};
|
||||||
|
video = Video{};
|
||||||
|
audio = Audio{};
|
||||||
|
loading = Loading{};
|
||||||
|
|
||||||
|
// Preserva config_file si ja s'ha establert abans.
|
||||||
|
const std::string PREV_CONFIG_FILE = settings.config_file;
|
||||||
|
settings = Settings{};
|
||||||
|
settings.config_file = PREV_CONFIG_FILE;
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
// En Emscripten la ventana la gestiona el navegador
|
||||||
|
window.zoom = 4;
|
||||||
|
video.fullscreen = false;
|
||||||
|
video.integer_scale = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Dispositius d'entrada per defecte
|
||||||
|
inputs.clear();
|
||||||
|
input_t kb;
|
||||||
|
kb.id = 0;
|
||||||
|
kb.name = "KEYBOARD";
|
||||||
|
kb.deviceType = INPUT_USE_KEYBOARD;
|
||||||
|
inputs.push_back(kb);
|
||||||
|
|
||||||
|
input_t gc;
|
||||||
|
gc.id = 0;
|
||||||
|
gc.name = "GAME CONTROLLER";
|
||||||
|
gc.deviceType = INPUT_USE_GAMECONTROLLER;
|
||||||
|
inputs.push_back(gc);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto loadFromFile() -> bool {
|
||||||
|
init();
|
||||||
|
|
||||||
|
std::ifstream file(settings.config_file);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
// Primera execució: crea el YAML amb defaults.
|
||||||
|
return saveToFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string CONTENT((std::istreambuf_iterator<char>(file)),
|
||||||
|
std::istreambuf_iterator<char>());
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto yaml = fkyaml::node::deserialize(CONTENT);
|
||||||
|
|
||||||
|
int file_version = 0;
|
||||||
|
if (yaml.contains("config_version")) {
|
||||||
|
try {
|
||||||
|
file_version = yaml["config_version"].get_value<int>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
if (file_version != Settings::CURRENT_CONFIG_VERSION) {
|
||||||
|
std::cout << "Config version " << file_version
|
||||||
|
<< " != expected " << Settings::CURRENT_CONFIG_VERSION
|
||||||
|
<< ". Recreating defaults.\n";
|
||||||
|
init();
|
||||||
|
return saveToFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadWindowFromYaml(yaml);
|
||||||
|
loadVideoFromYaml(yaml);
|
||||||
|
loadAudioFromYaml(yaml);
|
||||||
|
loadLoadingFromYaml(yaml);
|
||||||
|
loadSettingsFromYaml(yaml);
|
||||||
|
loadInputsFromYaml(yaml);
|
||||||
|
|
||||||
|
} catch (const fkyaml::exception &e) {
|
||||||
|
std::cout << "Error parsing YAML config: " << e.what() << ". Using defaults.\n";
|
||||||
|
init();
|
||||||
|
return saveToFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto saveToFile() -> bool {
|
||||||
|
if (settings.config_file.empty()) { return false; }
|
||||||
|
std::ofstream file(settings.config_file);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
std::cout << "Error: " << settings.config_file << " can't be opened for writing\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
file << "# Coffee Crisis - Configuration file\n";
|
||||||
|
file << "# Auto-generated, managed by the game.\n\n";
|
||||||
|
|
||||||
|
file << "config_version: " << settings.config_version << "\n\n";
|
||||||
|
|
||||||
|
// WINDOW
|
||||||
|
file << "# WINDOW\n";
|
||||||
|
file << "window:\n";
|
||||||
|
file << " zoom: " << window.zoom << "\n";
|
||||||
|
file << " max_zoom: " << window.max_zoom << "\n\n";
|
||||||
|
|
||||||
|
// VIDEO
|
||||||
|
file << "# VIDEO\n";
|
||||||
|
file << "video:\n";
|
||||||
|
file << " fullscreen: " << boolToString(video.fullscreen) << "\n";
|
||||||
|
file << " vsync: " << boolToString(video.vsync) << "\n";
|
||||||
|
file << " integer_scale: " << boolToString(video.integer_scale) << "\n";
|
||||||
|
file << " scale_mode: " << static_cast<int>(video.scale_mode)
|
||||||
|
<< " # " << static_cast<int>(SDL_SCALEMODE_NEAREST) << ": nearest, "
|
||||||
|
<< static_cast<int>(SDL_SCALEMODE_LINEAR) << ": linear\n";
|
||||||
|
file << " gpu:\n";
|
||||||
|
file << " acceleration: " << boolToString(video.gpu.acceleration) << "\n";
|
||||||
|
file << " preferred_driver: \"" << video.gpu.preferred_driver << "\"\n";
|
||||||
|
file << " supersampling:\n";
|
||||||
|
file << " enabled: " << boolToString(video.supersampling.enabled) << "\n";
|
||||||
|
file << " linear_upscale: " << boolToString(video.supersampling.linear_upscale) << "\n";
|
||||||
|
file << " downscale_algo: " << video.supersampling.downscale_algo << "\n";
|
||||||
|
file << " shader:\n";
|
||||||
|
file << " enabled: " << boolToString(video.shader.enabled) << "\n";
|
||||||
|
file << " current_shader: "
|
||||||
|
<< (video.shader.current_shader == Rendering::ShaderType::CRTPI ? "crtpi" : "postfx")
|
||||||
|
<< "\n";
|
||||||
|
file << " current_postfx_preset: \"" << video.shader.current_postfx_preset_name << "\"\n";
|
||||||
|
file << " current_crtpi_preset: \"" << video.shader.current_crtpi_preset_name << "\"\n\n";
|
||||||
|
|
||||||
|
// AUDIO
|
||||||
|
file << "# AUDIO (volume range: 0.0..1.0)\n";
|
||||||
|
file << "audio:\n";
|
||||||
|
file << " enabled: " << boolToString(audio.enabled) << "\n";
|
||||||
|
file << " volume: " << audio.volume << "\n";
|
||||||
|
file << " music:\n";
|
||||||
|
file << " enabled: " << boolToString(audio.music.enabled) << "\n";
|
||||||
|
file << " volume: " << audio.music.volume << "\n";
|
||||||
|
file << " sound:\n";
|
||||||
|
file << " enabled: " << boolToString(audio.sound.enabled) << "\n";
|
||||||
|
file << " volume: " << audio.sound.volume << "\n\n";
|
||||||
|
|
||||||
|
// LOADING
|
||||||
|
file << "# LOADING SCREEN\n";
|
||||||
|
file << "loading:\n";
|
||||||
|
file << " show: " << boolToString(loading.show) << "\n";
|
||||||
|
file << " show_resource_name: " << boolToString(loading.show_resource_name) << "\n";
|
||||||
|
file << " wait_for_input: " << boolToString(loading.wait_for_input) << "\n\n";
|
||||||
|
|
||||||
|
// SETTINGS
|
||||||
|
file << "# SETTINGS\n";
|
||||||
|
file << "settings:\n";
|
||||||
|
file << " difficulty: " << settings.difficulty << "\n";
|
||||||
|
file << " language: " << settings.language << "\n";
|
||||||
|
file << " palette: " << static_cast<int>(settings.palette) << "\n";
|
||||||
|
file << " player_selected: " << settings.player_selected << "\n\n";
|
||||||
|
|
||||||
|
// INPUT
|
||||||
|
file << "# INPUT DEVICES (device_type: "
|
||||||
|
<< static_cast<int>(INPUT_USE_KEYBOARD) << "=KEYBOARD, "
|
||||||
|
<< static_cast<int>(INPUT_USE_GAMECONTROLLER) << "=GAMECONTROLLER)\n";
|
||||||
|
file << "input:\n";
|
||||||
|
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||||
|
file << " - slot: " << i << "\n";
|
||||||
|
file << " device_type: " << static_cast<int>(inputs[i].deviceType) << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Presets de shaders (postfx.yaml / crtpi.yaml)
|
||||||
|
//
|
||||||
|
// Els defaults viuen en una única font (defaultPostFXPresets / defaultCrtPiPresets).
|
||||||
|
// Generem el YAML a partir d'ells el primer cop i els usem també com a
|
||||||
|
// fallback si el YAML és absent o corrupte. Si algú toca els valors, ho fa
|
||||||
|
// en un sol lloc.
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void parseFloatField(const fkyaml::node &node, const std::string &key, float &target) {
|
||||||
|
if (node.contains(key)) {
|
||||||
|
try {
|
||||||
|
target = node[key].get_value<float>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto defaultPostFXPresets() -> const std::vector<PostFXPreset> & {
|
||||||
|
static const std::vector<PostFXPreset> DEFAULTS = {
|
||||||
|
{"CRT", 0.6F, 0.7F, 0.15F, 0.6F, 0.8F},
|
||||||
|
{"NTSC", 0.4F, 0.5F, 0.2F, 0.4F, 0.5F, 0.0F, 0.6F},
|
||||||
|
{"CURVED", 0.5F, 0.6F, 0.1F, 0.5F, 0.7F, 0.8F},
|
||||||
|
{"SCANLINES", 0.0F, 0.8F},
|
||||||
|
{"SUBTLE", 0.3F, 0.4F, 0.05F, 0.0F, 0.3F},
|
||||||
|
{"CRT LIVE", 0.5F, 0.6F, 0.3F, 0.3F, 0.4F, 0.3F, 0.4F, 0.8F},
|
||||||
|
};
|
||||||
|
return DEFAULTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto defaultCrtPiPresets() -> const std::vector<CrtPiPreset> & {
|
||||||
|
static const std::vector<CrtPiPreset> DEFAULTS = {
|
||||||
|
{"Default", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, false, false},
|
||||||
|
{"Curved", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, true, false},
|
||||||
|
{"Sharp", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, false, true, false, true},
|
||||||
|
{"Minimal", 8.0F, 0.05F, 2.0F, 2.4F, 2.2F, 1.00F, 0.0F, 0.0F, 0, true, false, false, false, false},
|
||||||
|
};
|
||||||
|
return DEFAULTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void writePostFXDefaults(std::ostream &out) {
|
||||||
|
out << "# Coffee Crisis - PostFX Shader Presets\n\n";
|
||||||
|
out << "presets:\n";
|
||||||
|
for (const auto &p : defaultPostFXPresets()) {
|
||||||
|
out << " - name: \"" << p.name << "\"\n";
|
||||||
|
out << " vignette: " << p.vignette << "\n";
|
||||||
|
out << " scanlines: " << p.scanlines << "\n";
|
||||||
|
out << " chroma: " << p.chroma << "\n";
|
||||||
|
out << " mask: " << p.mask << "\n";
|
||||||
|
out << " gamma: " << p.gamma << "\n";
|
||||||
|
out << " curvature: " << p.curvature << "\n";
|
||||||
|
out << " bleeding: " << p.bleeding << "\n";
|
||||||
|
out << " flicker: " << p.flicker << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeCrtPiDefaults(std::ostream &out) {
|
||||||
|
out << "# Coffee Crisis - CrtPi Shader Presets\n\n";
|
||||||
|
out << "presets:\n";
|
||||||
|
for (const auto &p : defaultCrtPiPresets()) {
|
||||||
|
out << " - name: \"" << p.name << "\"\n";
|
||||||
|
out << " scanline_weight: " << p.scanline_weight << "\n";
|
||||||
|
out << " scanline_gap_brightness: " << p.scanline_gap_brightness << "\n";
|
||||||
|
out << " bloom_factor: " << p.bloom_factor << "\n";
|
||||||
|
out << " input_gamma: " << p.input_gamma << "\n";
|
||||||
|
out << " output_gamma: " << p.output_gamma << "\n";
|
||||||
|
out << " mask_brightness: " << p.mask_brightness << "\n";
|
||||||
|
out << " curvature_x: " << p.curvature_x << "\n";
|
||||||
|
out << " curvature_y: " << p.curvature_y << "\n";
|
||||||
|
out << " mask_type: " << p.mask_type << "\n";
|
||||||
|
out << " enable_scanlines: " << boolToString(p.enable_scanlines) << "\n";
|
||||||
|
out << " enable_multisample: " << boolToString(p.enable_multisample) << "\n";
|
||||||
|
out << " enable_gamma: " << boolToString(p.enable_gamma) << "\n";
|
||||||
|
out << " enable_curvature: " << boolToString(p.enable_curvature) << "\n";
|
||||||
|
out << " enable_sharper: " << boolToString(p.enable_sharper) << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void setPostFXFile(const std::string &path) {
|
||||||
|
postfx_file_path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto loadPostFXFromFile() -> bool {
|
||||||
|
postfx_presets.clear();
|
||||||
|
current_postfx_preset = 0;
|
||||||
|
|
||||||
|
std::ifstream file(postfx_file_path);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
// No existeix: escriu el YAML a partir dels defaults i copia'ls a memòria.
|
||||||
|
std::ofstream out(postfx_file_path);
|
||||||
|
if (out.is_open()) {
|
||||||
|
writePostFXDefaults(out);
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
postfx_presets = defaultPostFXPresets();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string CONTENT((std::istreambuf_iterator<char>(file)),
|
||||||
|
std::istreambuf_iterator<char>());
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto yaml = fkyaml::node::deserialize(CONTENT);
|
||||||
|
if (yaml.contains("presets")) {
|
||||||
|
for (const auto &p : yaml["presets"]) {
|
||||||
|
PostFXPreset preset;
|
||||||
|
if (p.contains("name")) {
|
||||||
|
try {
|
||||||
|
preset.name = p["name"].get_value<std::string>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
parseFloatField(p, "vignette", preset.vignette);
|
||||||
|
parseFloatField(p, "scanlines", preset.scanlines);
|
||||||
|
parseFloatField(p, "chroma", preset.chroma);
|
||||||
|
parseFloatField(p, "mask", preset.mask);
|
||||||
|
parseFloatField(p, "gamma", preset.gamma);
|
||||||
|
parseFloatField(p, "curvature", preset.curvature);
|
||||||
|
parseFloatField(p, "bleeding", preset.bleeding);
|
||||||
|
parseFloatField(p, "flicker", preset.flicker);
|
||||||
|
postfx_presets.push_back(preset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cout << "PostFX loaded: " << postfx_presets.size() << " preset(s)\n";
|
||||||
|
} catch (const fkyaml::exception &e) {
|
||||||
|
std::cout << "Error parsing PostFX YAML: " << e.what() << ". Using defaults.\n";
|
||||||
|
postfx_presets = defaultPostFXPresets();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (postfx_presets.empty()) {
|
||||||
|
postfx_presets = defaultPostFXPresets();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCrtPiFile(const std::string &path) {
|
||||||
|
crtpi_file_path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto loadCrtPiFromFile() -> bool {
|
||||||
|
crtpi_presets.clear();
|
||||||
|
current_crtpi_preset = 0;
|
||||||
|
|
||||||
|
std::ifstream file(crtpi_file_path);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
std::ofstream out(crtpi_file_path);
|
||||||
|
if (out.is_open()) {
|
||||||
|
writeCrtPiDefaults(out);
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
crtpi_presets = defaultCrtPiPresets();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string CONTENT((std::istreambuf_iterator<char>(file)),
|
||||||
|
std::istreambuf_iterator<char>());
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto yaml = fkyaml::node::deserialize(CONTENT);
|
||||||
|
if (yaml.contains("presets")) {
|
||||||
|
for (const auto &p : yaml["presets"]) {
|
||||||
|
CrtPiPreset preset;
|
||||||
|
if (p.contains("name")) {
|
||||||
|
try {
|
||||||
|
preset.name = p["name"].get_value<std::string>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
parseFloatField(p, "scanline_weight", preset.scanline_weight);
|
||||||
|
parseFloatField(p, "scanline_gap_brightness", preset.scanline_gap_brightness);
|
||||||
|
parseFloatField(p, "bloom_factor", preset.bloom_factor);
|
||||||
|
parseFloatField(p, "input_gamma", preset.input_gamma);
|
||||||
|
parseFloatField(p, "output_gamma", preset.output_gamma);
|
||||||
|
parseFloatField(p, "mask_brightness", preset.mask_brightness);
|
||||||
|
parseFloatField(p, "curvature_x", preset.curvature_x);
|
||||||
|
parseFloatField(p, "curvature_y", preset.curvature_y);
|
||||||
|
if (p.contains("mask_type")) {
|
||||||
|
try {
|
||||||
|
preset.mask_type = p["mask_type"].get_value<int>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
if (p.contains("enable_scanlines")) {
|
||||||
|
try {
|
||||||
|
preset.enable_scanlines = p["enable_scanlines"].get_value<bool>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
if (p.contains("enable_multisample")) {
|
||||||
|
try {
|
||||||
|
preset.enable_multisample = p["enable_multisample"].get_value<bool>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
if (p.contains("enable_gamma")) {
|
||||||
|
try {
|
||||||
|
preset.enable_gamma = p["enable_gamma"].get_value<bool>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
if (p.contains("enable_curvature")) {
|
||||||
|
try {
|
||||||
|
preset.enable_curvature = p["enable_curvature"].get_value<bool>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
if (p.contains("enable_sharper")) {
|
||||||
|
try {
|
||||||
|
preset.enable_sharper = p["enable_sharper"].get_value<bool>();
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
crtpi_presets.push_back(preset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cout << "CrtPi loaded: " << crtpi_presets.size() << " preset(s)\n";
|
||||||
|
} catch (const fkyaml::exception &e) {
|
||||||
|
std::cout << "Error parsing CrtPi YAML: " << e.what() << ". Using defaults.\n";
|
||||||
|
crtpi_presets = defaultCrtPiPresets();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crtpi_presets.empty()) {
|
||||||
|
crtpi_presets = defaultCrtPiPresets();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Options
|
||||||
153
source/game/options.hpp
Normal file
153
source/game/options.hpp
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "core/rendering/shader_backend.hpp" // for Rendering::ShaderType
|
||||||
|
#include "game/defaults.hpp"
|
||||||
|
#include "utils/utils.h" // for input_t, palette_e
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Opciones del programa, alineades amb coffee_crisis_arcade_edition.
|
||||||
|
// L'estat viu en globals dins el namespace Options:: (window, video, audio,
|
||||||
|
// loading, settings, inputs). La persistència usa fkyaml i es guarda a
|
||||||
|
// config.yaml dins la carpeta de configuració del sistema.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
namespace Options {
|
||||||
|
|
||||||
|
struct Window {
|
||||||
|
std::string caption = Defaults::Window::CAPTION;
|
||||||
|
int zoom = Defaults::Window::ZOOM;
|
||||||
|
int max_zoom = Defaults::Window::MAX_ZOOM;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPU {
|
||||||
|
bool acceleration = Defaults::Video::GPU_ACCELERATION;
|
||||||
|
std::string preferred_driver = Defaults::Video::GPU_PREFERRED_DRIVER;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Supersampling {
|
||||||
|
bool enabled = Defaults::Video::SUPERSAMPLING;
|
||||||
|
bool linear_upscale = Defaults::Video::LINEAR_UPSCALE;
|
||||||
|
int downscale_algo = Defaults::Video::DOWNSCALE_ALGO;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ShaderConfig {
|
||||||
|
bool enabled = Defaults::Video::SHADER_ENABLED;
|
||||||
|
Rendering::ShaderType current_shader = Rendering::ShaderType::POSTFX;
|
||||||
|
std::string current_postfx_preset_name = "CRT";
|
||||||
|
std::string current_crtpi_preset_name = "Default";
|
||||||
|
int current_postfx_preset = 0;
|
||||||
|
int current_crtpi_preset = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Video {
|
||||||
|
SDL_ScaleMode scale_mode = Defaults::Video::SCALE_MODE;
|
||||||
|
bool fullscreen = Defaults::Video::FULLSCREEN;
|
||||||
|
bool vsync = Defaults::Video::VSYNC;
|
||||||
|
bool integer_scale = Defaults::Video::INTEGER_SCALE;
|
||||||
|
GPU gpu;
|
||||||
|
Supersampling supersampling;
|
||||||
|
ShaderConfig shader;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Music {
|
||||||
|
bool enabled = Defaults::Music::ENABLED;
|
||||||
|
float volume = Defaults::Music::VOLUME;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Sound {
|
||||||
|
bool enabled = Defaults::Sound::ENABLED;
|
||||||
|
float volume = Defaults::Sound::VOLUME;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Audio {
|
||||||
|
bool enabled = Defaults::Audio::ENABLED;
|
||||||
|
float volume = Defaults::Audio::VOLUME;
|
||||||
|
Music music;
|
||||||
|
Sound sound;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Loading {
|
||||||
|
bool show = Defaults::Loading::SHOW;
|
||||||
|
bool show_resource_name = Defaults::Loading::SHOW_RESOURCE_NAME;
|
||||||
|
bool wait_for_input = Defaults::Loading::WAIT_FOR_INPUT;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Settings {
|
||||||
|
static constexpr int CURRENT_CONFIG_VERSION = 1;
|
||||||
|
int config_version = CURRENT_CONFIG_VERSION;
|
||||||
|
int difficulty = Defaults::Settings::DIFFICULTY;
|
||||||
|
int language = Defaults::Settings::LANGUAGE;
|
||||||
|
palette_e palette = Defaults::Settings::PALETTE;
|
||||||
|
bool console = false;
|
||||||
|
int player_selected = 0;
|
||||||
|
std::string config_file;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Preset PostFX
|
||||||
|
struct PostFXPreset {
|
||||||
|
std::string name;
|
||||||
|
float vignette{0.0F};
|
||||||
|
float scanlines{0.0F};
|
||||||
|
float chroma{0.0F};
|
||||||
|
float mask{0.0F};
|
||||||
|
float gamma{0.0F};
|
||||||
|
float curvature{0.0F};
|
||||||
|
float bleeding{0.0F};
|
||||||
|
float flicker{0.0F};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Preset CrtPi
|
||||||
|
struct CrtPiPreset {
|
||||||
|
std::string name;
|
||||||
|
float scanline_weight{6.0F};
|
||||||
|
float scanline_gap_brightness{0.12F};
|
||||||
|
float bloom_factor{3.5F};
|
||||||
|
float input_gamma{2.4F};
|
||||||
|
float output_gamma{2.2F};
|
||||||
|
float mask_brightness{0.80F};
|
||||||
|
float curvature_x{0.05F};
|
||||||
|
float curvature_y{0.10F};
|
||||||
|
int mask_type{2};
|
||||||
|
bool enable_scanlines{true};
|
||||||
|
bool enable_multisample{true};
|
||||||
|
bool enable_gamma{true};
|
||||||
|
bool enable_curvature{false};
|
||||||
|
bool enable_sharper{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Variables globales ---
|
||||||
|
extern Window window;
|
||||||
|
extern Video video;
|
||||||
|
extern Audio audio;
|
||||||
|
extern Loading loading;
|
||||||
|
extern Settings settings;
|
||||||
|
extern std::vector<input_t> inputs; // [0]=KEYBOARD, [1]=GAMECONTROLLER per defecte
|
||||||
|
|
||||||
|
// Presets de shaders (carregats de postfx.yaml / crtpi.yaml al config folder)
|
||||||
|
extern std::vector<PostFXPreset> postfx_presets;
|
||||||
|
extern std::string postfx_file_path;
|
||||||
|
extern int current_postfx_preset; // Índex dins `postfx_presets`
|
||||||
|
|
||||||
|
extern std::vector<CrtPiPreset> crtpi_presets;
|
||||||
|
extern std::string crtpi_file_path;
|
||||||
|
extern int current_crtpi_preset; // Índex dins `crtpi_presets`
|
||||||
|
|
||||||
|
// --- Funciones ---
|
||||||
|
void init(); // Reinicia a defaults i omple `inputs`
|
||||||
|
void setConfigFile(const std::string &file_path); // Ruta del config.yaml
|
||||||
|
auto loadFromFile() -> bool; // Carrega el YAML; si no existeix, crea'l amb defaults
|
||||||
|
auto saveToFile() -> bool; // Guarda el YAML
|
||||||
|
|
||||||
|
// Presets de shaders. Si el fitxer no existeix, l'escriu amb els defaults
|
||||||
|
// i deixa els presets carregats en memòria.
|
||||||
|
void setPostFXFile(const std::string &path);
|
||||||
|
auto loadPostFXFromFile() -> bool;
|
||||||
|
void setCrtPiFile(const std::string &path);
|
||||||
|
auto loadCrtPiFromFile() -> bool;
|
||||||
|
|
||||||
|
} // namespace Options
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "instructions.h"
|
#include "game/scenes/instructions.h"
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
@@ -6,52 +6,37 @@
|
|||||||
#include <iostream> // for char_traits, basic_ostream, operator<<
|
#include <iostream> // for char_traits, basic_ostream, operator<<
|
||||||
#include <string> // for basic_string
|
#include <string> // for basic_string
|
||||||
|
|
||||||
#include "asset.h" // for Asset
|
#include "core/audio/audio.hpp" // for Audio::update
|
||||||
#include "const.h" // for shdwTxtColor, GAMECANVAS_CENTER_X, GAME...
|
#include "core/input/global_inputs.hpp" // for GlobalInputs::handle
|
||||||
#include "input.h" // for Input, REPEAT_FALSE, inputs_e
|
#include "core/input/input.h" // for Input, REPEAT_FALSE, inputs_e
|
||||||
#include "jail_audio.hpp" // for JA_StopMusic
|
#include "core/locale/lang.h" // for Lang
|
||||||
#include "lang.h" // for Lang
|
#include "core/rendering/screen.h" // for Screen
|
||||||
#include "screen.h" // for Screen
|
#include "core/rendering/sprite.h" // for Sprite
|
||||||
#include "sprite.h" // for Sprite
|
#include "core/rendering/text.h" // for Text, TXT_CENTER, TXT_COLOR, TXT_SHADOW
|
||||||
#include "text.h" // for Text, TXT_CENTER, TXT_COLOR, TXT_SHADOW
|
#include "core/rendering/texture.h" // for Texture
|
||||||
#include "texture.h" // for Texture
|
#include "core/resources/resource.h"
|
||||||
#include "utils.h" // for color_t, section_t
|
#include "game/defaults.hpp" // for shdwTxtColor, GAMECANVAS_CENTER_X, GAME...
|
||||||
|
#include "utils/utils.h" // for color_t, section_t
|
||||||
const Uint8 SELF = 0;
|
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Instructions::Instructions(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input, Lang *lang, section_t *section) {
|
Instructions::Instructions(SDL_Renderer *renderer, section_t *section) {
|
||||||
// Copia los punteros
|
// Copia los punteros
|
||||||
this->renderer = renderer;
|
this->renderer = renderer;
|
||||||
this->screen = screen;
|
|
||||||
this->asset = asset;
|
|
||||||
this->input = input;
|
|
||||||
this->lang = lang;
|
|
||||||
this->section = section;
|
this->section = section;
|
||||||
|
|
||||||
// Reserva memoria para los punteros
|
// Texturas (handles compartidos de Resource)
|
||||||
Texture *item1 = new Texture(renderer, asset->get("item_points1_disk.png"));
|
Resource *R = Resource::get();
|
||||||
itemTextures.push_back(item1);
|
itemTextures.push_back(R->getTexture("item_points1_disk.png"));
|
||||||
|
itemTextures.push_back(R->getTexture("item_points2_gavina.png"));
|
||||||
Texture *item2 = new Texture(renderer, asset->get("item_points2_gavina.png"));
|
itemTextures.push_back(R->getTexture("item_points3_pacmar.png"));
|
||||||
itemTextures.push_back(item2);
|
itemTextures.push_back(R->getTexture("item_clock.png"));
|
||||||
|
itemTextures.push_back(R->getTexture("item_coffee.png"));
|
||||||
Texture *item3 = new Texture(renderer, asset->get("item_points3_pacmar.png"));
|
itemTextures.push_back(R->getTexture("item_coffee_machine.png"));
|
||||||
itemTextures.push_back(item3);
|
|
||||||
|
|
||||||
Texture *item4 = new Texture(renderer, asset->get("item_clock.png"));
|
|
||||||
itemTextures.push_back(item4);
|
|
||||||
|
|
||||||
Texture *item5 = new Texture(renderer, asset->get("item_coffee.png"));
|
|
||||||
itemTextures.push_back(item5);
|
|
||||||
|
|
||||||
Texture *item6 = new Texture(renderer, asset->get("item_coffee_machine.png"));
|
|
||||||
itemTextures.push_back(item6);
|
|
||||||
|
|
||||||
eventHandler = new SDL_Event();
|
eventHandler = new SDL_Event();
|
||||||
|
|
||||||
sprite = new Sprite(0, 0, 16, 16, itemTextures[0], renderer);
|
sprite = new Sprite(0, 0, 16, 16, itemTextures[0], renderer);
|
||||||
text = new Text(asset->get("smb2.png"), asset->get("smb2.txt"), renderer);
|
text = R->getText("smb2");
|
||||||
|
|
||||||
// Crea un backbuffer para el renderizador
|
// Crea un backbuffer para el renderizador
|
||||||
backbuffer = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT);
|
backbuffer = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT);
|
||||||
@@ -62,31 +47,32 @@ Instructions::Instructions(SDL_Renderer *renderer, Screen *screen, Asset *asset,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Inicializa variables
|
// Inicializa variables
|
||||||
section->name = SELF;
|
|
||||||
ticks = 0;
|
ticks = 0;
|
||||||
ticksSpeed = 15;
|
ticksSpeed = 15;
|
||||||
manualQuit = false;
|
manualQuit = false;
|
||||||
counter = 0;
|
counter = 0;
|
||||||
counterEnd = 600;
|
counterEnd = 600;
|
||||||
|
finished = false;
|
||||||
|
quitRequested = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
Instructions::~Instructions() {
|
Instructions::~Instructions() {
|
||||||
for (auto texture : itemTextures) {
|
// itemTextures y text son propiedad de Resource — no liberar.
|
||||||
texture->unload();
|
|
||||||
delete texture;
|
|
||||||
}
|
|
||||||
itemTextures.clear();
|
itemTextures.clear();
|
||||||
|
|
||||||
delete sprite;
|
delete sprite;
|
||||||
delete eventHandler;
|
delete eventHandler;
|
||||||
delete text;
|
|
||||||
|
|
||||||
SDL_DestroyTexture(backbuffer);
|
SDL_DestroyTexture(backbuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualiza las variables
|
// Actualiza las variables
|
||||||
void Instructions::update() {
|
void Instructions::update() {
|
||||||
|
// Bombea el stream de música: si no se llama, el buffer se vacía y la
|
||||||
|
// música se corta hasta que volvamos a una escena que sí lo haga.
|
||||||
|
Audio::update();
|
||||||
|
|
||||||
// Comprueba las entradas
|
// Comprueba las entradas
|
||||||
checkInput();
|
checkInput();
|
||||||
|
|
||||||
@@ -99,15 +85,13 @@ void Instructions::update() {
|
|||||||
counter++;
|
counter++;
|
||||||
|
|
||||||
if (counter == counterEnd) {
|
if (counter == counterEnd) {
|
||||||
section->name = SECTION_PROG_TITLE;
|
finished = true;
|
||||||
section->subsection = SUBSECTION_TITLE_1;
|
|
||||||
}
|
}
|
||||||
} else { // Modo manual
|
} else { // Modo manual
|
||||||
++counter %= 60000;
|
++counter %= 60000;
|
||||||
|
|
||||||
if (manualQuit) {
|
if (manualQuit) {
|
||||||
section->name = SECTION_PROG_TITLE;
|
finished = true;
|
||||||
section->subsection = SUBSECTION_TITLE_3;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,21 +117,21 @@ void Instructions::render() {
|
|||||||
SDL_RenderClear(renderer);
|
SDL_RenderClear(renderer);
|
||||||
|
|
||||||
// Escribe el texto
|
// Escribe el texto
|
||||||
text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 8, lang->getText(11), 1, orangeColor, 1, shdwTxtColor);
|
text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 8, Lang::get()->getText(11), 1, orangeColor, 1, shdwTxtColor);
|
||||||
text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 24, lang->getText(12), 1, noColor, 1, shdwTxtColor);
|
text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 24, Lang::get()->getText(12), 1, noColor, 1, shdwTxtColor);
|
||||||
text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 34, lang->getText(13), 1, noColor, 1, shdwTxtColor);
|
text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 34, Lang::get()->getText(13), 1, noColor, 1, shdwTxtColor);
|
||||||
text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 48, lang->getText(14), 1, noColor, 1, shdwTxtColor);
|
text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 48, Lang::get()->getText(14), 1, noColor, 1, shdwTxtColor);
|
||||||
text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 58, lang->getText(15), 1, noColor, 1, shdwTxtColor);
|
text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 58, Lang::get()->getText(15), 1, noColor, 1, shdwTxtColor);
|
||||||
text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 75, lang->getText(16), 1, orangeColor, 1, shdwTxtColor);
|
text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 75, Lang::get()->getText(16), 1, orangeColor, 1, shdwTxtColor);
|
||||||
|
|
||||||
text->writeShadowed(84, 92, lang->getText(17), shdwTxtColor);
|
text->writeShadowed(84, 92, Lang::get()->getText(17), shdwTxtColor);
|
||||||
text->writeShadowed(84, 108, lang->getText(18), shdwTxtColor);
|
text->writeShadowed(84, 108, Lang::get()->getText(18), shdwTxtColor);
|
||||||
text->writeShadowed(84, 124, lang->getText(19), shdwTxtColor);
|
text->writeShadowed(84, 124, Lang::get()->getText(19), shdwTxtColor);
|
||||||
text->writeShadowed(84, 140, lang->getText(20), shdwTxtColor);
|
text->writeShadowed(84, 140, Lang::get()->getText(20), shdwTxtColor);
|
||||||
text->writeShadowed(84, 156, lang->getText(21), shdwTxtColor);
|
text->writeShadowed(84, 156, Lang::get()->getText(21), shdwTxtColor);
|
||||||
|
|
||||||
if ((mode == m_manual) && (counter % 50 > 14)) {
|
if ((mode == m_manual) && (counter % 50 > 14)) {
|
||||||
text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, GAMECANVAS_HEIGHT - 12, lang->getText(22), 1, orangeColor, 1, shdwTxtColor);
|
text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, GAMECANVAS_HEIGHT - 12, Lang::get()->getText(22), 1, orangeColor, 1, shdwTxtColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disquito
|
// Disquito
|
||||||
@@ -189,10 +173,10 @@ void Instructions::render() {
|
|||||||
SDL_SetRenderTarget(renderer, nullptr);
|
SDL_SetRenderTarget(renderer, nullptr);
|
||||||
|
|
||||||
// Prepara para empezar a dibujar en la textura de juego
|
// Prepara para empezar a dibujar en la textura de juego
|
||||||
screen->start();
|
Screen::get()->start();
|
||||||
|
|
||||||
// Limpia la pantalla
|
// Limpia la pantalla
|
||||||
screen->clean(bgColor);
|
Screen::get()->clean(bgColor);
|
||||||
|
|
||||||
// Establece la ventana del backbuffer
|
// Establece la ventana del backbuffer
|
||||||
if (mode == m_auto) {
|
if (mode == m_auto) {
|
||||||
@@ -206,44 +190,38 @@ void Instructions::render() {
|
|||||||
SDL_RenderTexture(renderer, backbuffer, nullptr, &fWindow);
|
SDL_RenderTexture(renderer, backbuffer, nullptr, &fWindow);
|
||||||
|
|
||||||
// Vuelca el contenido del renderizador en pantalla
|
// Vuelca el contenido del renderizador en pantalla
|
||||||
screen->blit();
|
Screen::get()->blit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comprueba los eventos
|
// Comprueba los eventos
|
||||||
void Instructions::checkEvents() {
|
void Instructions::checkEvents() {
|
||||||
|
#ifndef __EMSCRIPTEN__
|
||||||
// Comprueba los eventos que hay en la cola
|
// Comprueba los eventos que hay en la cola
|
||||||
while (SDL_PollEvent(eventHandler) != 0) {
|
while (SDL_PollEvent(eventHandler) != 0) {
|
||||||
// Evento de salida de la aplicación
|
// Evento de salida de la aplicación
|
||||||
if (eventHandler->type == SDL_EVENT_QUIT) {
|
if (eventHandler->type == SDL_EVENT_QUIT) {
|
||||||
section->name = SECTION_PROG_QUIT;
|
quitRequested = true;
|
||||||
|
finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comprueba las entradas
|
// Comprueba las entradas
|
||||||
void Instructions::checkInput() {
|
void Instructions::checkInput() {
|
||||||
if (input->checkInput(input_exit, REPEAT_FALSE)) {
|
#ifndef __EMSCRIPTEN__
|
||||||
section->name = SECTION_PROG_QUIT;
|
if (Input::get()->checkInput(input_exit, REPEAT_FALSE)) {
|
||||||
|
quitRequested = true;
|
||||||
|
finished = true;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
if (GlobalInputs::handle()) { return; }
|
||||||
|
|
||||||
else if (input->checkInput(input_window_fullscreen, REPEAT_FALSE)) {
|
if (Input::get()->checkInput(input_pause, REPEAT_FALSE) || Input::get()->checkInput(input_accept, REPEAT_FALSE) || Input::get()->checkInput(input_fire_left, REPEAT_FALSE) || Input::get()->checkInput(input_fire_center, REPEAT_FALSE) || Input::get()->checkInput(input_fire_right, REPEAT_FALSE)) {
|
||||||
screen->switchVideoMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (input->checkInput(input_window_dec_size, REPEAT_FALSE)) {
|
|
||||||
screen->decWindowSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (input->checkInput(input_window_inc_size, REPEAT_FALSE)) {
|
|
||||||
screen->incWindowSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (input->checkInput(input_pause, REPEAT_FALSE) || input->checkInput(input_accept, REPEAT_FALSE) || input->checkInput(input_fire_left, REPEAT_FALSE) || input->checkInput(input_fire_center, REPEAT_FALSE) || input->checkInput(input_fire_right, REPEAT_FALSE)) {
|
|
||||||
if (mode == m_auto) {
|
if (mode == m_auto) {
|
||||||
JA_StopMusic();
|
finished = true;
|
||||||
section->name = SECTION_PROG_TITLE;
|
|
||||||
section->subsection = SUBSECTION_TITLE_1;
|
|
||||||
} else {
|
} else {
|
||||||
if (counter > 30) {
|
if (counter > 30) {
|
||||||
manualQuit = true;
|
manualQuit = true;
|
||||||
@@ -252,13 +230,41 @@ void Instructions::checkInput() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bucle para la pantalla de instrucciones
|
// Bucle para la pantalla de instrucciones (compatibilidad)
|
||||||
void Instructions::run(mode_e mode) {
|
void Instructions::run(mode_e mode) {
|
||||||
this->mode = mode;
|
start(mode);
|
||||||
|
|
||||||
while (section->name == SELF) {
|
while (!finished) {
|
||||||
update();
|
update();
|
||||||
checkEvents();
|
checkEvents();
|
||||||
render();
|
render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Aplica los cambios de sección según el resultado
|
||||||
|
if (quitRequested) {
|
||||||
|
section->name = SECTION_PROG_QUIT;
|
||||||
|
} else {
|
||||||
|
section->name = SECTION_PROG_TITLE;
|
||||||
|
section->subsection = (mode == m_auto) ? SUBSECTION_TITLE_1 : SUBSECTION_TITLE_3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inicia las instrucciones (sin bucle)
|
||||||
|
void Instructions::start(mode_e mode) {
|
||||||
|
this->mode = mode;
|
||||||
|
finished = false;
|
||||||
|
quitRequested = false;
|
||||||
|
manualQuit = false;
|
||||||
|
counter = 0;
|
||||||
|
ticks = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indica si las instrucciones han terminado
|
||||||
|
bool Instructions::hasFinished() const {
|
||||||
|
return finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indica si se ha solicitado salir de la aplicación
|
||||||
|
bool Instructions::isQuitRequested() const {
|
||||||
|
return quitRequested;
|
||||||
}
|
}
|
||||||
@@ -3,10 +3,6 @@
|
|||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <vector> // for vector
|
#include <vector> // for vector
|
||||||
class Asset;
|
|
||||||
class Input;
|
|
||||||
class Lang;
|
|
||||||
class Screen;
|
|
||||||
class Sprite;
|
class Sprite;
|
||||||
class Text;
|
class Text;
|
||||||
class Texture;
|
class Texture;
|
||||||
@@ -22,24 +18,41 @@ class Instructions {
|
|||||||
private:
|
private:
|
||||||
// Objetos y punteros
|
// Objetos y punteros
|
||||||
SDL_Renderer *renderer; // El renderizador de la ventana
|
SDL_Renderer *renderer; // El renderizador de la ventana
|
||||||
Screen *screen; // Objeto encargado de dibujar en pantalla
|
|
||||||
std::vector<Texture *> itemTextures; // Vector con las texturas de los items
|
std::vector<Texture *> itemTextures; // Vector con las texturas de los items
|
||||||
SDL_Event *eventHandler; // Manejador de eventos
|
SDL_Event *eventHandler; // Manejador de eventos
|
||||||
SDL_Texture *backbuffer; // Textura para usar como backbuffer
|
SDL_Texture *backbuffer; // Textura para usar como backbuffer
|
||||||
Sprite *sprite; // Sprite con la textura de las instrucciones
|
Sprite *sprite; // Sprite con la textura de las instrucciones
|
||||||
Asset *asset; // Objeto que gestiona todos los ficheros de recursos
|
|
||||||
Input *input; // Objeto pata gestionar la entrada
|
|
||||||
Lang *lang; // Objeto para gestionar los textos en diferentes idiomas
|
|
||||||
Text *text; // Objeto para escribir texto
|
Text *text; // Objeto para escribir texto
|
||||||
section_t *section; // Estado del bucle principal para saber si continua o se sale
|
section_t *section; // Estado del bucle principal para saber si continua o se sale
|
||||||
|
|
||||||
// Variables
|
// Variables
|
||||||
Uint16 counter; // Contador
|
Uint16 counter; // Contador
|
||||||
Uint16 counterEnd; // Valor final para el contador
|
Uint16 counterEnd; // Valor final para el contador
|
||||||
Uint32 ticks; // Contador de ticks para ajustar la velocidad del programa
|
Uint32 ticks; // Contador de ticks para ajustar la velocidad del programa
|
||||||
Uint32 ticksSpeed; // Velocidad a la que se repiten los bucles del programa
|
Uint32 ticksSpeed; // Velocidad a la que se repiten los bucles del programa
|
||||||
bool manualQuit; // Indica si se quiere salir del modo manual
|
bool manualQuit; // Indica si se quiere salir del modo manual
|
||||||
mode_e mode; // Modo en el que se van a ejecutar las instrucciones
|
mode_e mode{m_auto}; // Modo en el que se van a ejecutar las instrucciones
|
||||||
|
bool finished; // Indica si las instrucciones han terminado
|
||||||
|
bool quitRequested; // Indica si se ha solicitado salir de la aplicación
|
||||||
|
|
||||||
|
// Comprueba las entradas
|
||||||
|
void checkInput();
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Constructor
|
||||||
|
Instructions(SDL_Renderer *renderer, section_t *section);
|
||||||
|
|
||||||
|
// Destructor
|
||||||
|
~Instructions();
|
||||||
|
|
||||||
|
Instructions(const Instructions &) = delete;
|
||||||
|
Instructions &operator=(const Instructions &) = delete;
|
||||||
|
|
||||||
|
// Bucle principal
|
||||||
|
void run(mode_e mode);
|
||||||
|
|
||||||
|
// Inicia las instrucciones (sin bucle)
|
||||||
|
void start(mode_e mode);
|
||||||
|
|
||||||
// Actualiza las variables
|
// Actualiza las variables
|
||||||
void update();
|
void update();
|
||||||
@@ -50,16 +63,9 @@ class Instructions {
|
|||||||
// Comprueba los eventos
|
// Comprueba los eventos
|
||||||
void checkEvents();
|
void checkEvents();
|
||||||
|
|
||||||
// Comprueba las entradas
|
// Indica si las instrucciones han terminado
|
||||||
void checkInput();
|
bool hasFinished() const;
|
||||||
|
|
||||||
public:
|
// Indica si se ha solicitado salir de la aplicación
|
||||||
// Constructor
|
bool isQuitRequested() const;
|
||||||
Instructions(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input, Lang *lang, section_t *section);
|
|
||||||
|
|
||||||
// Destructor
|
|
||||||
~Instructions();
|
|
||||||
|
|
||||||
// Bucle principal
|
|
||||||
void run(mode_e mode);
|
|
||||||
};
|
};
|
||||||
@@ -1,38 +1,33 @@
|
|||||||
#include "intro.h"
|
#include "game/scenes/intro.h"
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <string> // for basic_string
|
#include <string> // for basic_string
|
||||||
|
|
||||||
#include "asset.h" // for Asset
|
#include "core/audio/audio.hpp" // for Audio::get, Audio::update
|
||||||
#include "const.h" // for GAMECANVAS_CENTER_X, GAMECANVAS_FIRST_QU...
|
#include "core/input/global_inputs.hpp" // for GlobalInputs::handle
|
||||||
#include "input.h" // for Input, REPEAT_FALSE, inputs_e
|
#include "core/input/input.h" // for Input, REPEAT_FALSE, inputs_e
|
||||||
#include "jail_audio.hpp" // for JA_StopMusic, JA_DeleteMusic, JA_LoadMusic
|
#include "core/locale/lang.h" // for Lang
|
||||||
#include "lang.h" // for Lang
|
#include "core/rendering/screen.h" // for Screen
|
||||||
#include "screen.h" // for Screen
|
#include "core/rendering/smartsprite.h" // for SmartSprite
|
||||||
#include "smartsprite.h" // for SmartSprite
|
#include "core/rendering/text.h" // for Text
|
||||||
#include "text.h" // for Text
|
#include "core/rendering/texture.h" // for Texture
|
||||||
#include "texture.h" // for Texture
|
#include "core/rendering/writer.h" // for Writer
|
||||||
#include "utils.h" // for section_t, color_t
|
#include "core/resources/resource.h"
|
||||||
#include "writer.h" // for Writer
|
#include "game/defaults.hpp" // for GAMECANVAS_CENTER_X, GAMECANVAS_FIRST_QU...
|
||||||
|
#include "utils/utils.h" // for section_t, color_t
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Intro::Intro(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input, Lang *lang, section_t *section) {
|
Intro::Intro(SDL_Renderer *renderer, section_t *section) {
|
||||||
// Copia los punteros
|
// Copia los punteros
|
||||||
this->renderer = renderer;
|
this->renderer = renderer;
|
||||||
this->screen = screen;
|
|
||||||
this->lang = lang;
|
|
||||||
this->asset = asset;
|
|
||||||
this->input = input;
|
|
||||||
this->section = section;
|
this->section = section;
|
||||||
|
|
||||||
// Reserva memoria para los objetos
|
// Reserva memoria para los objetos
|
||||||
eventHandler = new SDL_Event();
|
eventHandler = new SDL_Event();
|
||||||
texture = new Texture(renderer, asset->get("intro.png"));
|
texture = Resource::get()->getTexture("intro.png");
|
||||||
text = new Text(asset->get("nokia.png"), asset->get("nokia.txt"), renderer);
|
text = Resource::get()->getText("nokia");
|
||||||
|
music = Resource::get()->getMusic("intro.ogg");
|
||||||
// Carga los recursos
|
|
||||||
loadMedia();
|
|
||||||
|
|
||||||
// Inicializa variables
|
// Inicializa variables
|
||||||
section->name = SECTION_PROG_INTRO;
|
section->name = SECTION_PROG_INTRO;
|
||||||
@@ -115,104 +110,74 @@ Intro::Intro(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Un dia qualsevol de l'any 2000
|
// Un dia qualsevol de l'any 2000
|
||||||
texts[0]->setCaption(lang->getText(27));
|
texts[0]->setCaption(Lang::get()->getText(27));
|
||||||
texts[0]->setSpeed(8);
|
texts[0]->setSpeed(8);
|
||||||
|
|
||||||
// Tot esta tranquil a la UPV
|
// Tot esta tranquil a la UPV
|
||||||
texts[1]->setCaption(lang->getText(28));
|
texts[1]->setCaption(Lang::get()->getText(28));
|
||||||
texts[1]->setSpeed(8);
|
texts[1]->setSpeed(8);
|
||||||
|
|
||||||
// Fins que un desaprensiu...
|
// Fins que un desaprensiu...
|
||||||
texts[2]->setCaption(lang->getText(29));
|
texts[2]->setCaption(Lang::get()->getText(29));
|
||||||
texts[2]->setSpeed(12);
|
texts[2]->setSpeed(12);
|
||||||
|
|
||||||
// HEY! ME ANE A FERME UN CORTAET...
|
// HEY! ME ANE A FERME UN CORTAET...
|
||||||
texts[3]->setCaption(lang->getText(30));
|
texts[3]->setCaption(Lang::get()->getText(30));
|
||||||
texts[3]->setSpeed(8);
|
texts[3]->setSpeed(8);
|
||||||
|
|
||||||
// UAAAAAAAAAAAAA!!!
|
// UAAAAAAAAAAAAA!!!
|
||||||
texts[4]->setCaption(lang->getText(31));
|
texts[4]->setCaption(Lang::get()->getText(31));
|
||||||
texts[4]->setSpeed(1);
|
texts[4]->setSpeed(1);
|
||||||
|
|
||||||
// Espera un moment...
|
// Espera un moment...
|
||||||
texts[5]->setCaption(lang->getText(32));
|
texts[5]->setCaption(Lang::get()->getText(32));
|
||||||
texts[5]->setSpeed(16);
|
texts[5]->setSpeed(16);
|
||||||
|
|
||||||
// Si resulta que no tinc solt!
|
// Si resulta que no tinc solt!
|
||||||
texts[6]->setCaption(lang->getText(33));
|
texts[6]->setCaption(Lang::get()->getText(33));
|
||||||
texts[6]->setSpeed(2);
|
texts[6]->setSpeed(2);
|
||||||
|
|
||||||
// MERDA DE MAQUINA!
|
// MERDA DE MAQUINA!
|
||||||
texts[7]->setCaption(lang->getText(34));
|
texts[7]->setCaption(Lang::get()->getText(34));
|
||||||
texts[7]->setSpeed(3);
|
texts[7]->setSpeed(3);
|
||||||
|
|
||||||
// Blop... blop... blop...
|
// Blop... blop... blop...
|
||||||
texts[8]->setCaption(lang->getText(35));
|
texts[8]->setCaption(Lang::get()->getText(35));
|
||||||
texts[8]->setSpeed(16);
|
texts[8]->setSpeed(16);
|
||||||
|
|
||||||
for (auto text : texts) {
|
for (auto *t : texts) {
|
||||||
text->center(GAMECANVAS_CENTER_X);
|
t->center(GAMECANVAS_CENTER_X);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Audio::get()->playMusic(music, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
Intro::~Intro() {
|
Intro::~Intro() {
|
||||||
delete eventHandler;
|
delete eventHandler;
|
||||||
|
|
||||||
texture->unload();
|
// texture, text, music son propiedad de Resource — no liberar aquí.
|
||||||
delete texture;
|
|
||||||
|
|
||||||
for (auto bitmap : bitmaps) {
|
for (auto bitmap : bitmaps) {
|
||||||
delete bitmap;
|
delete bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto text : texts) {
|
for (auto t : texts) {
|
||||||
delete text;
|
delete t;
|
||||||
}
|
|
||||||
|
|
||||||
JA_DeleteMusic(music);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Carga los recursos
|
|
||||||
bool Intro::loadMedia() {
|
|
||||||
// Musicas
|
|
||||||
music = JA_LoadMusic(asset->get("intro.ogg").c_str());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comprueba los eventos
|
|
||||||
void Intro::checkEvents() {
|
|
||||||
// Comprueba los eventos que hay en la cola
|
|
||||||
while (SDL_PollEvent(eventHandler) != 0) {
|
|
||||||
// Evento de salida de la aplicación
|
|
||||||
if (eventHandler->type == SDL_EVENT_QUIT) {
|
|
||||||
section->name = SECTION_PROG_QUIT;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comprueba las entradas
|
// Comprueba las entradas
|
||||||
void Intro::checkInput() {
|
void Intro::checkInput() {
|
||||||
if (input->checkInput(input_exit, REPEAT_FALSE)) {
|
#ifndef __EMSCRIPTEN__
|
||||||
|
if (Input::get()->checkInput(input_exit, REPEAT_FALSE)) {
|
||||||
section->name = SECTION_PROG_QUIT;
|
section->name = SECTION_PROG_QUIT;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
if (GlobalInputs::handle()) { return; }
|
||||||
|
|
||||||
else if (input->checkInput(input_window_fullscreen, REPEAT_FALSE)) {
|
if (Input::get()->checkInput(input_pause, REPEAT_FALSE) || Input::get()->checkInput(input_accept, REPEAT_FALSE) || Input::get()->checkInput(input_fire_left, REPEAT_FALSE) || Input::get()->checkInput(input_fire_center, REPEAT_FALSE) || Input::get()->checkInput(input_fire_right, REPEAT_FALSE)) {
|
||||||
screen->switchVideoMode();
|
Audio::get()->stopMusic();
|
||||||
}
|
|
||||||
|
|
||||||
else if (input->checkInput(input_window_dec_size, REPEAT_FALSE)) {
|
|
||||||
screen->decWindowSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (input->checkInput(input_window_inc_size, REPEAT_FALSE)) {
|
|
||||||
screen->incWindowSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (input->checkInput(input_pause, REPEAT_FALSE) || input->checkInput(input_accept, REPEAT_FALSE) || input->checkInput(input_fire_left, REPEAT_FALSE) || input->checkInput(input_fire_center, REPEAT_FALSE) || input->checkInput(input_fire_right, REPEAT_FALSE)) {
|
|
||||||
JA_StopMusic();
|
|
||||||
section->name = SECTION_PROG_TITLE;
|
section->name = SECTION_PROG_TITLE;
|
||||||
section->subsection = SUBSECTION_TITLE_1;
|
section->subsection = SUBSECTION_TITLE_1;
|
||||||
}
|
}
|
||||||
@@ -342,7 +307,7 @@ void Intro::updateScenes() {
|
|||||||
if (bitmaps[5]->hasFinished() && texts[8]->hasFinished()) {
|
if (bitmaps[5]->hasFinished() && texts[8]->hasFinished()) {
|
||||||
bitmaps[5]->setEnabled(false);
|
bitmaps[5]->setEnabled(false);
|
||||||
texts[8]->setEnabled(false);
|
texts[8]->setEnabled(false);
|
||||||
JA_StopMusic();
|
Audio::get()->stopMusic();
|
||||||
section->name = SECTION_PROG_TITLE;
|
section->name = SECTION_PROG_TITLE;
|
||||||
section->subsection = SUBSECTION_TITLE_1;
|
section->subsection = SUBSECTION_TITLE_1;
|
||||||
}
|
}
|
||||||
@@ -356,7 +321,7 @@ void Intro::updateScenes() {
|
|||||||
|
|
||||||
// Actualiza las variables del objeto
|
// Actualiza las variables del objeto
|
||||||
void Intro::update() {
|
void Intro::update() {
|
||||||
JA_Update();
|
Audio::update();
|
||||||
checkInput();
|
checkInput();
|
||||||
|
|
||||||
if (SDL_GetTicks() - ticks > ticksSpeed) {
|
if (SDL_GetTicks() - ticks > ticksSpeed) {
|
||||||
@@ -368,8 +333,8 @@ void Intro::update() {
|
|||||||
bitmap->update();
|
bitmap->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto text : texts) {
|
for (auto *t : texts) {
|
||||||
text->update();
|
t->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualiza las escenas de la intro
|
// Actualiza las escenas de la intro
|
||||||
@@ -380,31 +345,40 @@ void Intro::update() {
|
|||||||
// Dibuja el objeto en pantalla
|
// Dibuja el objeto en pantalla
|
||||||
void Intro::render() {
|
void Intro::render() {
|
||||||
// Prepara para empezar a dibujar en la textura de juego
|
// Prepara para empezar a dibujar en la textura de juego
|
||||||
screen->start();
|
Screen::get()->start();
|
||||||
|
|
||||||
// Limpia la pantalla
|
// Limpia la pantalla
|
||||||
screen->clean(bgColor);
|
Screen::get()->clean(bgColor);
|
||||||
|
|
||||||
// Dibuja los objetos
|
// Dibuja los objetos
|
||||||
for (auto bitmap : bitmaps) {
|
for (auto bitmap : bitmaps) {
|
||||||
bitmap->render();
|
bitmap->render();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto text : texts) {
|
for (auto *t : texts) {
|
||||||
text->render();
|
t->render();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vuelca el contenido del renderizador en pantalla
|
// Vuelca el contenido del renderizador en pantalla
|
||||||
screen->blit();
|
Screen::get()->blit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bucle principal
|
// Bucle principal
|
||||||
void Intro::run() {
|
void Intro::run() {
|
||||||
JA_PlayMusic(music, 0);
|
Audio::get()->playMusic(music, 0);
|
||||||
|
|
||||||
while (section->name == SECTION_PROG_INTRO) {
|
while (section->name == SECTION_PROG_INTRO) {
|
||||||
update();
|
iterate();
|
||||||
checkEvents();
|
|
||||||
render();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ejecuta un frame
|
||||||
|
void Intro::iterate() {
|
||||||
|
update();
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Procesa un evento individual
|
||||||
|
void Intro::handleEvent(const SDL_Event *event) {
|
||||||
|
// SDL_EVENT_QUIT ya lo maneja Director
|
||||||
|
}
|
||||||
@@ -3,10 +3,6 @@
|
|||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <vector> // for vector
|
#include <vector> // for vector
|
||||||
class Asset;
|
|
||||||
class Input;
|
|
||||||
class Lang;
|
|
||||||
class Screen;
|
|
||||||
class SmartSprite;
|
class SmartSprite;
|
||||||
class Text;
|
class Text;
|
||||||
class Texture;
|
class Texture;
|
||||||
@@ -19,12 +15,8 @@ class Intro {
|
|||||||
private:
|
private:
|
||||||
// Objetos y punteros
|
// Objetos y punteros
|
||||||
SDL_Renderer *renderer; // El renderizador de la ventana
|
SDL_Renderer *renderer; // El renderizador de la ventana
|
||||||
Screen *screen; // Objeto encargado de dibujar en pantalla
|
|
||||||
Texture *texture; // Textura con los graficos
|
Texture *texture; // Textura con los graficos
|
||||||
SDL_Event *eventHandler; // Manejador de eventos
|
SDL_Event *eventHandler; // Manejador de eventos
|
||||||
Asset *asset; // Objeto que gestiona todos los ficheros de recursos
|
|
||||||
Lang *lang; // Objeto para gestionar los textos en diferentes idiomas
|
|
||||||
Input *input; // Objeto pata gestionar la entrada
|
|
||||||
std::vector<SmartSprite *> bitmaps; // Vector con los sprites inteligentes para los dibujos de la intro
|
std::vector<SmartSprite *> bitmaps; // Vector con los sprites inteligentes para los dibujos de la intro
|
||||||
std::vector<Writer *> texts; // Textos de la intro
|
std::vector<Writer *> texts; // Textos de la intro
|
||||||
Text *text; // Textos de la intro
|
Text *text; // Textos de la intro
|
||||||
@@ -42,12 +34,6 @@ class Intro {
|
|||||||
// Dibuja el objeto en pantalla
|
// Dibuja el objeto en pantalla
|
||||||
void render();
|
void render();
|
||||||
|
|
||||||
// Carga los recursos
|
|
||||||
bool loadMedia();
|
|
||||||
|
|
||||||
// Comprueba los eventos
|
|
||||||
void checkEvents();
|
|
||||||
|
|
||||||
// Comprueba las entradas
|
// Comprueba las entradas
|
||||||
void checkInput();
|
void checkInput();
|
||||||
|
|
||||||
@@ -56,11 +42,20 @@ class Intro {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
// Constructor
|
// Constructor
|
||||||
Intro(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input, Lang *lang, section_t *section);
|
Intro(SDL_Renderer *renderer, section_t *section);
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
~Intro();
|
~Intro();
|
||||||
|
|
||||||
|
Intro(const Intro &) = delete;
|
||||||
|
Intro &operator=(const Intro &) = delete;
|
||||||
|
|
||||||
// Bucle principal
|
// Bucle principal
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
|
// Ejecuta un frame
|
||||||
|
void iterate();
|
||||||
|
|
||||||
|
// Procesa un evento
|
||||||
|
void handleEvent(const SDL_Event *event);
|
||||||
};
|
};
|
||||||
@@ -1,35 +1,34 @@
|
|||||||
#include "logo.h"
|
#include "game/scenes/logo.h"
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <algorithm> // for min
|
#include <algorithm> // for min
|
||||||
#include <string> // for basic_string
|
#include <string> // for basic_string
|
||||||
|
|
||||||
#include "asset.h" // for Asset
|
#include "core/audio/audio.hpp" // for Audio::get, Audio::update
|
||||||
#include "const.h" // for bgColor, SECTION_PROG_LOGO, SECTION_PROG...
|
#include "core/input/global_inputs.hpp" // for GlobalInputs::handle
|
||||||
#include "input.h" // for Input, REPEAT_FALSE, inputs_e
|
#include "core/input/input.h" // for Input, REPEAT_FALSE, inputs_e
|
||||||
#include "jail_audio.hpp" // for JA_StopMusic
|
#include "core/rendering/screen.h" // for Screen
|
||||||
#include "screen.h" // for Screen
|
#include "core/rendering/sprite.h" // for Sprite
|
||||||
#include "sprite.h" // for Sprite
|
#include "core/rendering/texture.h" // for Texture
|
||||||
#include "texture.h" // for Texture
|
#include "core/resources/asset.h" // for Asset
|
||||||
#include "utils.h" // for section_t, color_t
|
#include "core/resources/resource.h"
|
||||||
|
#include "game/defaults.hpp" // for bgColor, SECTION_PROG_LOGO, SECTION_PROG...
|
||||||
|
#include "utils/utils.h" // for section_t, color_t
|
||||||
|
|
||||||
// Valores de inicialización y fin
|
// Valores de inicialización y fin
|
||||||
constexpr int INIT_FADE = 100;
|
constexpr int INIT_FADE = 100;
|
||||||
constexpr int END_LOGO = 200;
|
constexpr int END_LOGO = 200;
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Logo::Logo(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input, section_t *section) {
|
Logo::Logo(SDL_Renderer *renderer, section_t *section) {
|
||||||
// Copia la dirección de los objetos
|
// Copia la dirección de los objetos
|
||||||
this->renderer = renderer;
|
this->renderer = renderer;
|
||||||
this->screen = screen;
|
|
||||||
this->asset = asset;
|
|
||||||
this->input = input;
|
|
||||||
this->section = section;
|
this->section = section;
|
||||||
|
|
||||||
// Reserva memoria para los punteros
|
// Reserva memoria para los punteros
|
||||||
eventHandler = new SDL_Event();
|
eventHandler = new SDL_Event();
|
||||||
texture = new Texture(renderer, asset->get("logo.png"));
|
texture = Resource::get()->getTexture("logo.png");
|
||||||
sprite = new Sprite(14, 75, 226, 44, texture, renderer);
|
sprite = new Sprite(14, 75, 226, 44, texture, renderer);
|
||||||
|
|
||||||
// Inicializa variables
|
// Inicializa variables
|
||||||
@@ -38,13 +37,13 @@ Logo::Logo(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input, s
|
|||||||
section->subsection = 0;
|
section->subsection = 0;
|
||||||
ticks = 0;
|
ticks = 0;
|
||||||
ticksSpeed = 15;
|
ticksSpeed = 15;
|
||||||
|
|
||||||
|
Audio::get()->stopMusic();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
Logo::~Logo() {
|
Logo::~Logo() {
|
||||||
texture->unload();
|
// texture es propiedad de Resource — no liberar aquí.
|
||||||
delete texture;
|
|
||||||
|
|
||||||
delete sprite;
|
delete sprite;
|
||||||
delete eventHandler;
|
delete eventHandler;
|
||||||
}
|
}
|
||||||
@@ -57,37 +56,17 @@ void Logo::checkLogoEnd() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comprueba los eventos
|
|
||||||
void Logo::checkEvents() {
|
|
||||||
// Comprueba los eventos que hay en la cola
|
|
||||||
while (SDL_PollEvent(eventHandler) != 0) {
|
|
||||||
// Evento de salida de la aplicación
|
|
||||||
if (eventHandler->type == SDL_EVENT_QUIT) {
|
|
||||||
section->name = SECTION_PROG_QUIT;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comprueba las entradas
|
// Comprueba las entradas
|
||||||
void Logo::checkInput() {
|
void Logo::checkInput() {
|
||||||
if (input->checkInput(input_exit, REPEAT_FALSE)) {
|
#ifndef __EMSCRIPTEN__
|
||||||
|
if (Input::get()->checkInput(input_exit, REPEAT_FALSE)) {
|
||||||
section->name = SECTION_PROG_QUIT;
|
section->name = SECTION_PROG_QUIT;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
if (GlobalInputs::handle()) { return; }
|
||||||
|
|
||||||
else if (input->checkInput(input_window_fullscreen, REPEAT_FALSE)) {
|
if (Input::get()->checkInput(input_pause, REPEAT_FALSE) || Input::get()->checkInput(input_accept, REPEAT_FALSE) || Input::get()->checkInput(input_fire_left, REPEAT_FALSE) || Input::get()->checkInput(input_fire_center, REPEAT_FALSE) || Input::get()->checkInput(input_fire_right, REPEAT_FALSE)) {
|
||||||
screen->switchVideoMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (input->checkInput(input_window_dec_size, REPEAT_FALSE)) {
|
|
||||||
screen->decWindowSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (input->checkInput(input_window_inc_size, REPEAT_FALSE)) {
|
|
||||||
screen->incWindowSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (input->checkInput(input_pause, REPEAT_FALSE) || input->checkInput(input_accept, REPEAT_FALSE) || input->checkInput(input_fire_left, REPEAT_FALSE) || input->checkInput(input_fire_center, REPEAT_FALSE) || input->checkInput(input_fire_right, REPEAT_FALSE)) {
|
|
||||||
section->name = SECTION_PROG_TITLE;
|
section->name = SECTION_PROG_TITLE;
|
||||||
section->subsection = SUBSECTION_TITLE_1;
|
section->subsection = SUBSECTION_TITLE_1;
|
||||||
}
|
}
|
||||||
@@ -106,7 +85,7 @@ void Logo::renderFade() {
|
|||||||
|
|
||||||
// Actualiza las variables del objeto
|
// Actualiza las variables del objeto
|
||||||
void Logo::update() {
|
void Logo::update() {
|
||||||
JA_Update();
|
Audio::update();
|
||||||
checkInput();
|
checkInput();
|
||||||
|
|
||||||
if (SDL_GetTicks() - ticks > ticksSpeed) {
|
if (SDL_GetTicks() - ticks > ticksSpeed) {
|
||||||
@@ -124,10 +103,10 @@ void Logo::update() {
|
|||||||
// Dibuja el objeto en pantalla
|
// Dibuja el objeto en pantalla
|
||||||
void Logo::render() {
|
void Logo::render() {
|
||||||
// Prepara para empezar a dibujar en la textura de juego
|
// Prepara para empezar a dibujar en la textura de juego
|
||||||
screen->start();
|
Screen::get()->start();
|
||||||
|
|
||||||
// Limpia la pantalla
|
// Limpia la pantalla
|
||||||
screen->clean({238, 238, 238});
|
Screen::get()->clean({238, 238, 238});
|
||||||
|
|
||||||
// Dibuja los objetos
|
// Dibuja los objetos
|
||||||
sprite->render();
|
sprite->render();
|
||||||
@@ -136,16 +115,25 @@ void Logo::render() {
|
|||||||
renderFade();
|
renderFade();
|
||||||
|
|
||||||
// Vuelca el contenido del renderizador en pantalla
|
// Vuelca el contenido del renderizador en pantalla
|
||||||
screen->blit();
|
Screen::get()->blit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bucle para el logo del juego
|
// Bucle para el logo del juego
|
||||||
void Logo::run() {
|
void Logo::run() {
|
||||||
JA_StopMusic();
|
Audio::get()->stopMusic();
|
||||||
|
|
||||||
while (section->name == SECTION_PROG_LOGO) {
|
while (section->name == SECTION_PROG_LOGO) {
|
||||||
update();
|
iterate();
|
||||||
checkEvents();
|
|
||||||
render();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ejecuta un frame
|
||||||
|
void Logo::iterate() {
|
||||||
|
update();
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Procesa un evento individual
|
||||||
|
void Logo::handleEvent(const SDL_Event *event) {
|
||||||
|
// SDL_EVENT_QUIT ya lo maneja Director
|
||||||
|
}
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
class Asset;
|
|
||||||
class Input;
|
|
||||||
class Screen;
|
|
||||||
class Sprite;
|
class Sprite;
|
||||||
class Texture;
|
class Texture;
|
||||||
struct section_t;
|
struct section_t;
|
||||||
@@ -13,9 +10,6 @@ class Logo {
|
|||||||
private:
|
private:
|
||||||
// Objetos y punteros
|
// Objetos y punteros
|
||||||
SDL_Renderer *renderer; // El renderizador de la ventana
|
SDL_Renderer *renderer; // El renderizador de la ventana
|
||||||
Screen *screen; // Objeto encargado de dibujar en pantalla
|
|
||||||
Asset *asset; // Objeto que gestiona todos los ficheros de recursos
|
|
||||||
Input *input; // Objeto pata gestionar la entrada
|
|
||||||
Texture *texture; // Textura con los graficos
|
Texture *texture; // Textura con los graficos
|
||||||
SDL_Event *eventHandler; // Manejador de eventos
|
SDL_Event *eventHandler; // Manejador de eventos
|
||||||
Sprite *sprite; // Sprite con la textura del logo
|
Sprite *sprite; // Sprite con la textura del logo
|
||||||
@@ -35,9 +29,6 @@ class Logo {
|
|||||||
// Comprueba si ha terminado el logo
|
// Comprueba si ha terminado el logo
|
||||||
void checkLogoEnd();
|
void checkLogoEnd();
|
||||||
|
|
||||||
// Comprueba los eventos
|
|
||||||
void checkEvents();
|
|
||||||
|
|
||||||
// Comprueba las entradas
|
// Comprueba las entradas
|
||||||
void checkInput();
|
void checkInput();
|
||||||
|
|
||||||
@@ -46,11 +37,20 @@ class Logo {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
// Constructor
|
// Constructor
|
||||||
Logo(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input, section_t *section);
|
Logo(SDL_Renderer *renderer, section_t *section);
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
~Logo();
|
~Logo();
|
||||||
|
|
||||||
|
Logo(const Logo &) = delete;
|
||||||
|
Logo &operator=(const Logo &) = delete;
|
||||||
|
|
||||||
// Bucle principal
|
// Bucle principal
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
|
// Ejecuta un frame
|
||||||
|
void iterate();
|
||||||
|
|
||||||
|
// Procesa un evento
|
||||||
|
void handleEvent(const SDL_Event *event);
|
||||||
};
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -4,16 +4,13 @@
|
|||||||
|
|
||||||
#include <vector> // for vector
|
#include <vector> // for vector
|
||||||
|
|
||||||
#include "instructions.h" // for mode_e
|
#include "game/options.hpp" // for Options::Video, Options::Window (per snapshot cancel)
|
||||||
#include "utils.h" // for input_t, options_t, section_t
|
#include "game/scenes/instructions.h" // for mode_e
|
||||||
|
#include "utils/utils.h" // for input_t, section_t
|
||||||
class AnimatedSprite;
|
class AnimatedSprite;
|
||||||
class Asset;
|
|
||||||
class Fade;
|
class Fade;
|
||||||
class Game;
|
class Game;
|
||||||
class Input;
|
|
||||||
class Lang;
|
|
||||||
class Menu;
|
class Menu;
|
||||||
class Screen;
|
|
||||||
class SmartSprite;
|
class SmartSprite;
|
||||||
class Sprite;
|
class Sprite;
|
||||||
class Text;
|
class Text;
|
||||||
@@ -22,7 +19,7 @@ struct JA_Music_t;
|
|||||||
struct JA_Sound_t;
|
struct JA_Sound_t;
|
||||||
|
|
||||||
// Textos
|
// Textos
|
||||||
constexpr const char *TEXT_COPYRIGHT = "@2020 JailDesigner (v2.3.3)";
|
constexpr const char *TEXT_COPYRIGHT = "@2020 JailDesigner (v2.3.4)";
|
||||||
|
|
||||||
// Contadores
|
// Contadores
|
||||||
constexpr int TITLE_COUNTER = 800;
|
constexpr int TITLE_COUNTER = 800;
|
||||||
@@ -41,15 +38,11 @@ class Title {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Objetos y punteros
|
// Objetos y punteros
|
||||||
SDL_Renderer *renderer; // El renderizador de la ventana
|
SDL_Renderer *renderer; // El renderizador de la ventana
|
||||||
Screen *screen; // Objeto encargado de dibujar en pantalla
|
Instructions *instructions{nullptr}; // Objeto para la sección de las instrucciones
|
||||||
Asset *asset; // Objeto que gestiona todos los ficheros de recursos
|
Game *demoGame{nullptr}; // Objeto para lanzar la demo del juego
|
||||||
Input *input; // Objeto para leer las entradas de teclado o mando
|
SDL_Event *eventHandler; // Manejador de eventos
|
||||||
Lang *lang; // Objeto para gestionar los textos en diferentes idiomas
|
section_t *section; // Indicador para el bucle del titulo
|
||||||
Instructions *instructions; // Objeto para la sección de las instrucciones
|
|
||||||
Game *demoGame; // Objeto para lanzar la demo del juego
|
|
||||||
SDL_Event *eventHandler; // Manejador de eventos
|
|
||||||
section_t *section; // Indicador para el bucle del titulo
|
|
||||||
|
|
||||||
Texture *dustTexture; // Textura con los graficos del polvo
|
Texture *dustTexture; // Textura con los graficos del polvo
|
||||||
Texture *coffeeTexture; // Textura con los graficos de la palabra coffee
|
Texture *coffeeTexture; // Textura con los graficos de la palabra coffee
|
||||||
@@ -72,24 +65,39 @@ class Title {
|
|||||||
Fade *fade; // Objeto para realizar fundidos en pantalla
|
Fade *fade; // Objeto para realizar fundidos en pantalla
|
||||||
|
|
||||||
// Variable
|
// Variable
|
||||||
JA_Music_t *titleMusic; // Musica para el titulo
|
JA_Music_t *titleMusic; // Musica para el titulo
|
||||||
JA_Sound_t *crashSound; // Sonido con el impacto del título
|
JA_Sound_t *crashSound; // Sonido con el impacto del título
|
||||||
int backgroundCounter; // Temporizador para el fondo de tiles de la pantalla de titulo
|
int backgroundCounter; // Temporizador para el fondo de tiles de la pantalla de titulo
|
||||||
int counter; // Temporizador para la pantalla de titulo
|
int counter; // Temporizador para la pantalla de titulo
|
||||||
Uint32 ticks; // Contador de ticks para ajustar la velocidad del programa
|
Uint32 ticks; // Contador de ticks para ajustar la velocidad del programa
|
||||||
Uint8 backgroundMode; // Variable para almacenar el tipo de efecto que hará el fondo de la pantalla de titulo
|
Uint8 backgroundMode; // Variable para almacenar el tipo de efecto que hará el fondo de la pantalla de titulo
|
||||||
float sin[360]; // Vector con los valores del seno precalculados
|
float sin[360]; // Vector con los valores del seno precalculados
|
||||||
bool menuVisible; // Indicador para saber si se muestra el menu del titulo o la frase intermitente
|
bool menuVisible; // Indicador para saber si se muestra el menu del titulo o la frase intermitente
|
||||||
bool demo; // Indica si el modo demo estará activo
|
bool demo; // Indica si el modo demo estará activo
|
||||||
section_t nextSection; // Indica cual es la siguiente sección a cargar cuando termine el contador del titulo
|
section_t nextSection; // Indica cual es la siguiente sección a cargar cuando termine el contador del titulo
|
||||||
Uint32 ticksSpeed; // Velocidad a la que se repiten los bucles del programa
|
Uint32 ticksSpeed; // Velocidad a la que se repiten los bucles del programa
|
||||||
Uint8 postFade; // Opción a realizar cuando termina el fundido
|
Uint8 postFade; // Opción a realizar cuando termina el fundido
|
||||||
menu_t menu; // Variable con todos los objetos menus y sus variables
|
menu_t menu; // Variable con todos los objetos menus y sus variables
|
||||||
struct options_t *options; // Variable con todas las variables de las opciones del programa
|
// Snapshot per a permetre CANCEL al menú d'opcions.
|
||||||
options_t optionsPrevious; // Variable de respaldo para las opciones
|
Options::Video prevVideo;
|
||||||
|
Options::Window prevWindow;
|
||||||
|
Options::Settings prevSettings;
|
||||||
|
std::vector<input_t> prevInputs;
|
||||||
std::vector<input_t> availableInputDevices; // Vector con todos los metodos de control disponibles
|
std::vector<input_t> availableInputDevices; // Vector con todos los metodos de control disponibles
|
||||||
std::vector<int> deviceIndex; // Indice para el jugador [i] del vector de dispositivos de entrada disponibles
|
std::vector<int> deviceIndex; // Indice para el jugador [i] del vector de dispositivos de entrada disponibles
|
||||||
|
|
||||||
|
// Variables para la vibración del título (SUBSECTION_TITLE_2)
|
||||||
|
int vibrationStep; // Paso actual de la vibración
|
||||||
|
int vibrationCoffeeBaseX{0}; // Posición X base del bitmap Coffee
|
||||||
|
int vibrationCrisisBaseX{0}; // Posición X base del bitmap Crisis
|
||||||
|
bool vibrationInitialized; // Indica si se han capturado las posiciones base
|
||||||
|
|
||||||
|
// Variables para sub-estados delegados (instrucciones y demo)
|
||||||
|
bool instructionsActive; // Indica si las instrucciones están activas
|
||||||
|
bool demoGameActive; // Indica si el juego demo está activo
|
||||||
|
mode_e instructionsMode{m_auto}; // Modo de las instrucciones activas
|
||||||
|
bool demoThenInstructions; // Indica si tras la demo hay que mostrar instrucciones
|
||||||
|
|
||||||
// Inicializa los valores
|
// Inicializa los valores
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
@@ -99,9 +107,6 @@ class Title {
|
|||||||
// Dibuja el objeto en pantalla
|
// Dibuja el objeto en pantalla
|
||||||
void render();
|
void render();
|
||||||
|
|
||||||
// Comprueba los eventos
|
|
||||||
void checkEvents();
|
|
||||||
|
|
||||||
// Comprueba las entradas
|
// Comprueba las entradas
|
||||||
void checkInput();
|
void checkInput();
|
||||||
|
|
||||||
@@ -137,11 +142,20 @@ class Title {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
// Constructor
|
// Constructor
|
||||||
Title(SDL_Renderer *renderer, Screen *screen, Input *input, Asset *asset, options_t *options, Lang *lang, section_t *section);
|
Title(SDL_Renderer *renderer, section_t *section);
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
~Title();
|
~Title();
|
||||||
|
|
||||||
|
Title(const Title &) = delete;
|
||||||
|
Title &operator=(const Title &) = delete;
|
||||||
|
|
||||||
// Bucle para el titulo del juego
|
// Bucle para el titulo del juego
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
|
// Ejecuta un frame
|
||||||
|
void iterate();
|
||||||
|
|
||||||
|
// Procesa un evento
|
||||||
|
void handleEvent(const SDL_Event *event);
|
||||||
};
|
};
|
||||||
@@ -1,20 +1,24 @@
|
|||||||
#include "menu.h"
|
#include "game/ui/menu.h"
|
||||||
|
|
||||||
#include <algorithm> // for max, min
|
#include <algorithm> // for max, min
|
||||||
#include <fstream> // for char_traits, basic_ifstream, basic_istream
|
#include <fstream> // for char_traits, basic_ifstream, basic_istream
|
||||||
|
#include <numeric> // for accumulate
|
||||||
#include <sstream> // for basic_stringstream
|
#include <sstream> // for basic_stringstream
|
||||||
|
|
||||||
#include "asset.h" // for Asset
|
#include "core/audio/audio.hpp" // for Audio::get (playSound)
|
||||||
#include "input.h" // for Input, REPEAT_FALSE, inputs_e
|
#include "core/audio/jail_audio.hpp" // for JA_LoadSound, JA_DeleteSound (propietat local)
|
||||||
#include "jail_audio.hpp" // for JA_LoadSound, JA_PlaySound, JA_DeleteSound
|
#include "core/input/input.h" // for Input, REPEAT_FALSE, inputs_e
|
||||||
#include "text.h" // for Text
|
#include "core/rendering/text.h" // for Text
|
||||||
|
#include "core/resources/asset.h" // for Asset
|
||||||
|
#include "core/resources/resource_helper.h"
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Menu::Menu(SDL_Renderer *renderer, Asset *asset, Input *input, std::string file) {
|
Menu::Menu(SDL_Renderer *renderer, const std::string &file)
|
||||||
|
: colorGreyed{128, 128, 128},
|
||||||
|
font_png(""),
|
||||||
|
font_txt("") {
|
||||||
// Copia punteros
|
// Copia punteros
|
||||||
this->renderer = renderer;
|
this->renderer = renderer;
|
||||||
this->asset = asset;
|
|
||||||
this->input = input;
|
|
||||||
|
|
||||||
// Inicializa punteros
|
// Inicializa punteros
|
||||||
soundMove = nullptr;
|
soundMove = nullptr;
|
||||||
@@ -22,7 +26,6 @@ Menu::Menu(SDL_Renderer *renderer, Asset *asset, Input *input, std::string file)
|
|||||||
soundCancel = nullptr;
|
soundCancel = nullptr;
|
||||||
|
|
||||||
// Inicializa variables
|
// Inicializa variables
|
||||||
name = "";
|
|
||||||
selector.index = 0;
|
selector.index = 0;
|
||||||
selector.previousIndex = 0;
|
selector.previousIndex = 0;
|
||||||
itemSelected = MENU_NO_OPTION;
|
itemSelected = MENU_NO_OPTION;
|
||||||
@@ -39,10 +42,7 @@ Menu::Menu(SDL_Renderer *renderer, Asset *asset, Input *input, std::string file)
|
|||||||
centerX = 0;
|
centerX = 0;
|
||||||
centerY = 0;
|
centerY = 0;
|
||||||
widestItem = 0;
|
widestItem = 0;
|
||||||
colorGreyed = {128, 128, 128};
|
|
||||||
defaultActionWhenCancel = 0;
|
defaultActionWhenCancel = 0;
|
||||||
font_png = "";
|
|
||||||
font_txt = "";
|
|
||||||
|
|
||||||
// Selector
|
// Selector
|
||||||
selector.originY = 0;
|
selector.originY = 0;
|
||||||
@@ -61,22 +61,18 @@ Menu::Menu(SDL_Renderer *renderer, Asset *asset, Input *input, std::string file)
|
|||||||
selector.itemColor = {0, 0, 0};
|
selector.itemColor = {0, 0, 0};
|
||||||
selector.a = 255;
|
selector.a = 255;
|
||||||
|
|
||||||
// Inicializa las variables desde un fichero
|
// Inicializa las variables desde un fichero. Si no se pasa fichero, el
|
||||||
if (file != "") {
|
// llamante (p.ej. Resource::preloadAll) usará loadFromBytes después —
|
||||||
|
// y ese método ya llama a setSelectorItemColors() y reset() al final.
|
||||||
|
if (!file.empty()) {
|
||||||
load(file);
|
load(file);
|
||||||
|
setSelectorItemColors();
|
||||||
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calcula los colores del selector para el degradado
|
|
||||||
setSelectorItemColors();
|
|
||||||
|
|
||||||
// Deja el cursor en el primer elemento
|
|
||||||
reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu::~Menu() {
|
Menu::~Menu() {
|
||||||
renderer = nullptr;
|
renderer = nullptr;
|
||||||
asset = nullptr;
|
|
||||||
input = nullptr;
|
|
||||||
|
|
||||||
if (soundMove) {
|
if (soundMove) {
|
||||||
JA_DeleteSound(soundMove);
|
JA_DeleteSound(soundMove);
|
||||||
@@ -95,84 +91,75 @@ Menu::~Menu() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carga la configuración del menu desde un archivo de texto
|
// Parser compartido (recibe cualquier istream)
|
||||||
bool Menu::load(std::string file_path) {
|
bool Menu::parseFromStream(std::istream &file, const std::string &filename) {
|
||||||
// Indicador de éxito en la carga
|
|
||||||
bool success = true;
|
bool success = true;
|
||||||
|
|
||||||
// Indica si se ha creado ya el objeto de texto
|
|
||||||
bool textAllocated = false;
|
bool textAllocated = false;
|
||||||
|
|
||||||
const std::string filename = file_path.substr(file_path.find_last_of("\\/") + 1);
|
|
||||||
std::string line;
|
std::string line;
|
||||||
std::ifstream file(file_path);
|
(void)filename;
|
||||||
|
|
||||||
// El fichero se puede abrir
|
while (std::getline(file, line)) {
|
||||||
if (file.good()) {
|
if (line == "[item]") {
|
||||||
// Procesa el fichero linea a linea
|
item_t newItem;
|
||||||
// std::cout << "Reading file " << filename.c_str() << std::endl;
|
newItem.label = "";
|
||||||
while (std::getline(file, line)) {
|
newItem.hPaddingDown = 1;
|
||||||
if (line == "[item]") {
|
newItem.selectable = true;
|
||||||
item_t item;
|
newItem.greyed = false;
|
||||||
item.label = "";
|
newItem.linkedDown = false;
|
||||||
item.hPaddingDown = 1;
|
newItem.visible = true;
|
||||||
item.selectable = true;
|
newItem.line = false;
|
||||||
item.greyed = false;
|
|
||||||
item.linkedDown = false;
|
|
||||||
item.visible = true;
|
|
||||||
item.line = false;
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
// Lee la siguiente linea
|
std::getline(file, line);
|
||||||
std::getline(file, line);
|
|
||||||
|
|
||||||
// Encuentra la posición del caracter '='
|
|
||||||
int pos = line.find("=");
|
|
||||||
|
|
||||||
// Procesa las dos subcadenas
|
|
||||||
if (!setItem(&item, line.substr(0, pos), line.substr(pos + 1, line.length()))) {
|
|
||||||
// std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} while (line != "[/item]");
|
|
||||||
|
|
||||||
addItem(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
// En caso contrario se parsea el fichero para buscar las variables y los valores
|
|
||||||
else {
|
|
||||||
// Encuentra la posición del caracter '='
|
|
||||||
int pos = line.find("=");
|
int pos = line.find("=");
|
||||||
// Procesa las dos subcadenas
|
if (!setItem(&newItem, line.substr(0, pos), line.substr(pos + 1, line.length()))) {
|
||||||
if (!setVars(line.substr(0, pos), line.substr(pos + 1, line.length()))) {
|
|
||||||
// std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
|
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
} while (line != "[/item]");
|
||||||
|
|
||||||
// Crea el objeto text tan pronto como se pueda. Necesario para añadir items
|
addItem(newItem);
|
||||||
if (font_png != "" && font_txt != "" && !textAllocated) {
|
} else {
|
||||||
text = new Text(asset->get(font_png), asset->get(font_txt), renderer);
|
int pos = line.find("=");
|
||||||
textAllocated = true;
|
if (!setVars(line.substr(0, pos), line.substr(pos + 1, line.length()))) {
|
||||||
}
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crea el objeto text tan pronto como se pueda. Necesario para añadir items.
|
||||||
|
// Carga via ResourceHelper para que funcione tanto con pack como con filesystem.
|
||||||
|
if (font_png != "" && font_txt != "" && !textAllocated) {
|
||||||
|
auto pngBytes = ResourceHelper::loadFile(Asset::get()->get(font_png));
|
||||||
|
auto txtBytes = ResourceHelper::loadFile(Asset::get()->get(font_txt));
|
||||||
|
text = new Text(pngBytes, txtBytes, renderer);
|
||||||
|
textAllocated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cierra el fichero
|
|
||||||
// std::cout << "Closing file " << filename.c_str() << std::endl;
|
|
||||||
file.close();
|
|
||||||
}
|
}
|
||||||
// El fichero no se puede abrir
|
|
||||||
else {
|
|
||||||
// std::cout << "Warning: Unable to open " << filename.c_str() << " file" << std::endl;
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Carga la configuración del menu desde un archivo de texto
|
||||||
|
bool Menu::load(const std::string &file_path) {
|
||||||
|
const std::string filename = file_path.substr(file_path.find_last_of("\\/") + 1);
|
||||||
|
std::ifstream file(file_path);
|
||||||
|
if (!file.good()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return parseFromStream(file, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carga el menu desde bytes en memoria
|
||||||
|
bool Menu::loadFromBytes(const std::vector<uint8_t> &bytes, const std::string &nameForLogs) {
|
||||||
|
if (bytes.empty()) return false;
|
||||||
|
std::string content(reinterpret_cast<const char *>(bytes.data()), bytes.size());
|
||||||
|
std::stringstream ss(content);
|
||||||
|
bool ok = parseFromStream(ss, nameForLogs);
|
||||||
|
setSelectorItemColors();
|
||||||
|
reset();
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
// Asigna variables a partir de dos cadenas
|
// Asigna variables a partir de dos cadenas
|
||||||
bool Menu::setItem(item_t *item, std::string var, std::string value) {
|
bool Menu::setItem(item_t *item, const std::string &var, const std::string &value) {
|
||||||
// Indicador de éxito en la asignación
|
// Indicador de éxito en la asignación
|
||||||
bool success = true;
|
bool success = true;
|
||||||
|
|
||||||
@@ -215,7 +202,7 @@ bool Menu::setItem(item_t *item, std::string var, std::string value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Asigna variables a partir de dos cadenas
|
// Asigna variables a partir de dos cadenas
|
||||||
bool Menu::setVars(std::string var, std::string value) {
|
bool Menu::setVars(const std::string &var, const std::string &value) {
|
||||||
// Indicador de éxito en la asignación
|
// Indicador de éxito en la asignación
|
||||||
bool success = true;
|
bool success = true;
|
||||||
|
|
||||||
@@ -228,15 +215,18 @@ bool Menu::setVars(std::string var, std::string value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
else if (var == "sound_cancel") {
|
else if (var == "sound_cancel") {
|
||||||
soundCancel = JA_LoadSound(asset->get(value).c_str());
|
auto bytes = ResourceHelper::loadFile(Asset::get()->get(value));
|
||||||
|
if (!bytes.empty()) soundCancel = JA_LoadSound(bytes.data(), (uint32_t)bytes.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (var == "sound_accept") {
|
else if (var == "sound_accept") {
|
||||||
soundAccept = JA_LoadSound(asset->get(value).c_str());
|
auto bytes = ResourceHelper::loadFile(Asset::get()->get(value));
|
||||||
|
if (!bytes.empty()) soundAccept = JA_LoadSound(bytes.data(), (uint32_t)bytes.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (var == "sound_move") {
|
else if (var == "sound_move") {
|
||||||
soundMove = JA_LoadSound(asset->get(value).c_str());
|
auto bytes = ResourceHelper::loadFile(Asset::get()->get(value));
|
||||||
|
if (!bytes.empty()) soundMove = JA_LoadSound(bytes.data(), (uint32_t)bytes.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (var == "name") {
|
else if (var == "name") {
|
||||||
@@ -330,7 +320,7 @@ bool Menu::setVars(std::string var, std::string value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Carga los ficheros de audio
|
// Carga los ficheros de audio
|
||||||
void Menu::loadAudioFile(std::string file, int sound) {
|
void Menu::loadAudioFile(const std::string &file, int sound) {
|
||||||
switch (sound) {
|
switch (sound) {
|
||||||
case SOUND_ACCEPT:
|
case SOUND_ACCEPT:
|
||||||
soundAccept = JA_LoadSound(file.c_str());
|
soundAccept = JA_LoadSound(file.c_str());
|
||||||
@@ -350,7 +340,7 @@ void Menu::loadAudioFile(std::string file, int sound) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Obtiene el nombre del menu
|
// Obtiene el nombre del menu
|
||||||
std::string Menu::getName() {
|
const std::string &Menu::getName() const {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,14 +431,7 @@ void Menu::setSelectorPos(int index) {
|
|||||||
|
|
||||||
// Obtiene la anchura del elemento más ancho del menu
|
// Obtiene la anchura del elemento más ancho del menu
|
||||||
int Menu::getWidestItem() {
|
int Menu::getWidestItem() {
|
||||||
int result = 0;
|
return std::accumulate(item.begin(), item.end(), 0, [](int acc, const item_t &i) { return std::max(acc, i.rect.w); });
|
||||||
|
|
||||||
// Obtenemos la anchura del item mas ancho
|
|
||||||
for (auto &i : item) {
|
|
||||||
result = std::max(result, i.rect.w);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deja el menu apuntando al primer elemento
|
// Deja el menu apuntando al primer elemento
|
||||||
@@ -485,7 +468,7 @@ void Menu::reorganize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Deja el menu apuntando al siguiente elemento
|
// Deja el menu apuntando al siguiente elemento
|
||||||
bool Menu::increaseSelectorIndex() {
|
void Menu::increaseSelectorIndex() {
|
||||||
// Guarda el indice actual antes de modificarlo
|
// Guarda el indice actual antes de modificarlo
|
||||||
selector.previousIndex = selector.index;
|
selector.previousIndex = selector.index;
|
||||||
|
|
||||||
@@ -510,12 +493,10 @@ bool Menu::increaseSelectorIndex() {
|
|||||||
if (selector.incH != 0) {
|
if (selector.incH != 0) {
|
||||||
selector.resizing = true;
|
selector.resizing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deja el menu apuntando al elemento anterior
|
// Deja el menu apuntando al elemento anterior
|
||||||
bool Menu::decreaseSelectorIndex() {
|
void Menu::decreaseSelectorIndex() {
|
||||||
// Guarda el indice actual antes de modificarlo
|
// Guarda el indice actual antes de modificarlo
|
||||||
selector.previousIndex = selector.index;
|
selector.previousIndex = selector.index;
|
||||||
|
|
||||||
@@ -549,8 +530,6 @@ bool Menu::decreaseSelectorIndex() {
|
|||||||
if (selector.incH != 0) {
|
if (selector.incH != 0) {
|
||||||
selector.resizing = true;
|
selector.resizing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualiza la logica del menu
|
// Actualiza la logica del menu
|
||||||
@@ -763,7 +742,7 @@ void Menu::addItem(item_t temp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cambia el texto de un item
|
// Cambia el texto de un item
|
||||||
void Menu::setItemCaption(int index, std::string text) {
|
void Menu::setItemCaption(int index, const std::string &text) {
|
||||||
item[index].label = text;
|
item[index].label = text;
|
||||||
item[index].rect.w = this->text->lenght(item[index].label);
|
item[index].rect.w = this->text->lenght(item[index].label);
|
||||||
item[index].rect.h = this->text->getCharacterSize();
|
item[index].rect.h = this->text->getCharacterSize();
|
||||||
@@ -777,33 +756,31 @@ void Menu::setDefaultActionWhenCancel(int item) {
|
|||||||
|
|
||||||
// Gestiona la entrada de teclado y mando durante el menu
|
// Gestiona la entrada de teclado y mando durante el menu
|
||||||
void Menu::checkInput() {
|
void Menu::checkInput() {
|
||||||
if (input->checkInput(input_up, REPEAT_FALSE)) {
|
if (Input::get()->checkInput(input_up, REPEAT_FALSE)) {
|
||||||
if (decreaseSelectorIndex()) {
|
decreaseSelectorIndex();
|
||||||
if (soundMove) {
|
if (soundMove) {
|
||||||
JA_PlaySound(soundMove);
|
Audio::get()->playSound(soundMove);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input->checkInput(input_down, REPEAT_FALSE)) {
|
if (Input::get()->checkInput(input_down, REPEAT_FALSE)) {
|
||||||
if (increaseSelectorIndex()) {
|
increaseSelectorIndex();
|
||||||
if (soundMove) {
|
if (soundMove) {
|
||||||
JA_PlaySound(soundMove);
|
Audio::get()->playSound(soundMove);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input->checkInput(input_accept, REPEAT_FALSE)) {
|
if (Input::get()->checkInput(input_accept, REPEAT_FALSE)) {
|
||||||
itemSelected = selector.index;
|
itemSelected = selector.index;
|
||||||
if (soundAccept) {
|
if (soundAccept) {
|
||||||
JA_PlaySound(soundAccept);
|
Audio::get()->playSound(soundAccept);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input->checkInput(input_cancel, REPEAT_FALSE)) {
|
if (Input::get()->checkInput(input_cancel, REPEAT_FALSE)) {
|
||||||
itemSelected = defaultActionWhenCancel;
|
itemSelected = defaultActionWhenCancel;
|
||||||
if (soundCancel) {
|
if (soundCancel) {
|
||||||
JA_PlaySound(soundCancel);
|
Audio::get()->playSound(soundCancel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -815,12 +792,7 @@ int Menu::findWidth() {
|
|||||||
|
|
||||||
// Calcula el alto del menu
|
// Calcula el alto del menu
|
||||||
int Menu::findHeight() {
|
int Menu::findHeight() {
|
||||||
int height = 0;
|
const int height = std::accumulate(item.begin(), item.end(), 0, [](int acc, const item_t &i) { return acc + i.rect.h + i.hPaddingDown; });
|
||||||
|
|
||||||
// Obtenemos la altura de la suma de alturas de los items
|
|
||||||
for (auto &i : item) {
|
|
||||||
height += i.rect.h + i.hPaddingDown;
|
|
||||||
}
|
|
||||||
|
|
||||||
return height - item.back().hPaddingDown;
|
return height - item.back().hPaddingDown;
|
||||||
}
|
}
|
||||||
@@ -864,7 +836,7 @@ int Menu::getSelectorHeight(int value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Establece el nombre del menu
|
// Establece el nombre del menu
|
||||||
void Menu::setName(std::string name) {
|
void Menu::setName(const std::string &name) {
|
||||||
this->name = name;
|
this->name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -880,9 +852,9 @@ void Menu::setBackgroundType(int value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Establece la fuente de texto que se utilizará
|
// Establece la fuente de texto que se utilizará
|
||||||
void Menu::setText(std::string font_png, std::string font_txt) {
|
void Menu::setText(const std::string &font_png, const std::string &font_txt) {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
text = new Text(asset->get(font_png), asset->get(font_txt), renderer);
|
text = new Text(Asset::get()->get(font_png), Asset::get()->get(font_txt), renderer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user