Compare commits

..

13 Commits

Author SHA1 Message Date
8538a1047f corregit makefile per a macos 2026-04-03 21:48:04 +02:00
e150097edc actualitzat makefile 2026-04-03 21:27:00 +02:00
5f0d1f9577 afegit changelog,md 2026-04-03 21:08:46 +02:00
6d8d02f0e4 corregit el bug introduit en c35889a840 2026-04-03 21:01:44 +02:00
5f7fb8625d intro acabada 2026-04-03 20:40:54 +02:00
ce2fcefd71 almost ... 2026-04-03 18:50:50 +02:00
6f31751d42 aproximant-se 2026-04-03 18:24:58 +02:00
43de2c0b35 treballant en la visió original de la intro 2026-04-03 18:10:27 +02:00
1ca9d0c01b arreglos menors en intro 2026-04-03 17:46:03 +02:00
90d5e6c3cc afegit debug.yaml 2026-04-03 17:37:51 +02:00
a653dad7b0 - modificat el hud de info de fps
- ajustats els presets per defecte
- finestra a 2x i shader off per defecte
- versió en el fitxer de configuracio
2026-04-03 17:13:00 +02:00
cf1f97a84f Afegides les opcions a Service Menu 2026-04-03 16:30:38 +02:00
93fe17c3b2 migrat, amb ajuda de claude, a sdl3gpu (postfx i crtpi) igual que el JDD 2026-04-03 15:08:06 +02:00
53 changed files with 21977 additions and 3891 deletions

200
CHANGELOG.md Normal file
View File

@@ -0,0 +1,200 @@
# CHANGELOG
Historial de canvis i novetats de Coffee Crisis Arcade Edition.
---
## 2026-04-03
- **Nova intro cinematogràfica**: les tarjetes s'llancen des dels costats de la pantalla amb zoom, rotació i rebot, simulant tirar cartes sobre una mesa. Les anteriors ixen despedides girant quan arriba la següent. Sombra amb efecte de perspectiva 2D→3D.
- **Efectes sonors i visuals en la intro**: shake de pantalla i sons configurables a cada impacte de tarjeta.
- **Migració a SDL3 GPU API**: postfx i crtpi migrats a SDL3GPU (Vulkan/Metal/D3D12).
- **Migració de configuració a YAML**: eliminat el format antic, ara tot en YAML.
- **Afegides opcions al Service Menu**.
- **HUD de FPS retocat**, presets per defecte ajustats, finestra a 2x i shader off per defecte.
- **Corregit bug d'input**: revertit un canvi que causava bucle infinit en F3 (pantalla completa) i F12 (service menu) en totes les escenes excepte Game.
- Neteja de codi: eliminades referencies a OpenGL, fitxers GLSL sobrants, normalitzada la carpeta release i el caption de la finestra.
---
## 2025-10-25
- **Migració a delta_time pur** en credits, instructions i hiscore_table. Eliminat un bug que feia que els credits no acabaren mai si no passaves a mà.
- **Corregida deformació subpixel** de textures en instructions i hiscore_table.
- **Detecció de fitxers de puntuació corruptes**.
- **Efecte de pulsos** afegit al scoreboard.
- Al posar nom, el carrusel apareix directament en el caràcter d'acabar si ja havies posat nom abans.
- Integrat jail_audio en la càrrega de resources.pack.
- Pasaeta de linter en múltiples fitxers.
- Nou icon per al joc.
- Corregida la versió release de macOS per a funcionar correctament amb resources.pack.
---
## 2025-08-21
- **Integració amb resources.pack**: textures, animacions, textos, dades de la demo i jail_audio integrats amb ResourceHelper.
- Actualitzat Makefile per a Windows, Linux i macOS.
- Neteja de temporals al acabar.
---
## 2025-08-17
- **Afegit fade RANDOM_SQUARE2** amb timings canviats a mil·lisegons.
- **Outline parametritzat** per als textos dels items.
- **Colors de camiseta parametritzats** per defecte i quan pillen café.
- Creat `defaults.h` amb els valors per defecte de Param.
- Afegit `param_red.txt` amb guardes en setParams.
- Fix: items que es quedaven engantxats a la part de dalt.
- Fix: en el modo demo, assignava cafés al jugador que no jugava.
- Fix: bug en l'estat pre del fade.
- Fix: globos apareixien un frame mal situats al crear-se des d'un pare.
- Afegit suport per a mapejar botons tipus trigger.
---
## 2025-08-10
- **Service Menu complet**: animació d'apertura/tancament, callback per a posar pausa en el joc, refresc visual al canviar mandos.
- **Mandos en calent**: es poden connectar i desconnectar mandos durant el joc, amb notificació visual.
- **PauseManager** afegit al joc.
- **Càrrega de recursos on_demand**.
- Afegit `shutdown.h` i `system_utils.h`.
- Fix: el nom apareixia duplicat en la tabla de records.
- Fix: Game no es desregistrava de ServiceMenu al destruir-se.
- Precàrrega de textures del jugador amb variants de paleta.
- Actualitzats frameworks per a macOS.
---
## 2025-03-25
- **Nova secció Intro** amb escenes seqüencials, animacions de tarjetes i text narratiu.
- **Shaders respecten l'escalat sencer** i SDL_RenderSetLogicalSize en pantalla completa.
- **Tecla per canviar l'integer scale** (F-key).
- Afegit intro03.png i intro04.png.
- Renomenat InputType a InputActions.
- Actualitzat gamecontrollerdb.txt amb mappings de la recreativa.
- Fix: al fer reset des de Game, en Intro no sonava la música.
- Fix: al acabar la partida i vore els records, torna al títol.
- Fix: amb l'àudio mutat, el fade per al soroll de boles el tornava a deixar activat.
---
## 2025-02-07
- **EnterName millorat**: si has omplit tots els slots, apretar una volta mes fixa el nom.
- **Control de repetició per als eixos del joystick**.
- **La tabla de puntuació** mostra amb altre color la puntuació acabada d'afegir i les aconseguides amb 1CC.
- Nova font per a la intro.
- Afegit efecte d'eixida a les instruccions.
- Afegit disparador per a l'aparició de l'enemic nou.
- Duplicada la font 04b_25 per a versió gris i versió negra.
---
## 2025-01-05
- **Optimitzat el circuit de render** en pantalla.
- **Atenuat de pantalla restaurat**: Fade feia dos SDL_SetRenderDrawBlendMode sense restaurar.
- Fix: es podia polsar per a jugar mentre feia el fade cap a la demo.
- Fix: error en la seqüència final de retrocedir en el temps.
- Calibrats els polsos al gust.
- Afegida una lluna i un sol al fondo.
- La powerball ja no es pot destruir fins que no ha fet un rebot.
- Modificada la cadència de foc sense autofire.
- Afegit botó per a activar o desactivar el ratolí.
---
## 2024-12-31
- **Enemic nou** complet: gràfics, comportament, àudio i veus.
- **Fade out sincronitzat** de vídeo i àudio en el títol i el joc.
- **Roidets de col·lisió** per als globos en certs moments.
- La finestra ja es pot fer tan gran com permeta la pantalla (zoom dinàmic).
- Afegides veus al jugador i efectes de so al rebotar quan mor.
- Afegit delay opcional al flash de Screen.
- Afegit botó per activar o desactivar l'autofire.
- Fix: mode demo desactivava els sons permanentment.
- Actualitzat jail_audio.
---
## 2024-12-05
- **Secció Credits acabada** a 320x240 (i per extensió, a qualsevol resolució).
- **Zoom afegit a la classe Sprite** i al subtítol ARCADE EDITION.
- Duplicats fitxers de shaders per a resolucions verticals de 256 i 240.
- Afegit globalInputs::update() a totes les seccions.
- Fix: faltava corregir el flash de destroyAllBalloons().
- Fix: si saltes el logo, talla el so a meitat sonar.
- Canvi d'idioma amb una tecla (i reinicia).
---
## 2024-11-27
- **Secció Credits**: disseny, música, globos amb play_area definida, opció de canviar la paleta al text.
- Afegides traduccions dels credits.
---
## 2024-11-20
- **Nova animació de mort del personatge**: rebots, llengua fora, ulls en X, gràfics de caure derrotat per al segon jugador.
- **Powerball redissenyada**: nous gràfics, nou comportament, ja no mata directament.
- **Globos fills** ja no ixen centrats al pare (evita apilar-se).
- Arreglos en el nom al obtindre la màxima puntuació.
- Acabat BalloonManager.
- CMakeLists.txt crea l'executable en l'arrel del projecte.
- Nova font de text gran amb el doble de definició.
- Fix: paleta verda del primer jugador ajustada a l'original.
---
## 2024-11-03
- **Teclat com a control independent**: ja pot jugar un jugador amb teclat i altre amb mando, o assignar el teclat a qualsevol jugador.
- **Implementat el final del joc** i l'Attract Mode.
- **Nou motor per a textos en pantalla** (game_text amb textures precarregades).
- **Noves animacions** per a deixar de disparar.
- Al redefinir botons, ja no pots repetir botó.
- Fix: l'animació de morir s'actualitzava dos voltes per frame.
- Fix: l'efecte de flash tenia un valor massa xicotet.
---
## 2024-10-28
- **Classe PathSprite completada**: el game_text gasta PathSprites en lloc de SmartSprites.
- **Time stopper redissenyat**.
- La partida sempre comença igual (createTwoBigBalloons).
- Revisades les classes Balloon i Bullet.
- Millorada l'aparició dels game_text.
- Fix: la paleta dels jugadors no s'iniciava correctament.
---
## 2024-10-20
- **Classe Resource creada**: precàrrega de tots els recursos (textures, música, sons, animacions).
- **Paletes de color** per a textures GIF amb shared_ptr.
- Precàrrega i assignació de paletes.
- Implementat comptador per a posar el nom al acabar la partida.
- Classe Notifier independitzada de Screen amb codis identificadors.
- Afegit codi per a apagar el sistema al eixir del joc.
- Fix: globos verds tenien setters mal assignats i velocitat incorrecta.
- Fix: no guardar el fitxer de puntuacions en el mode demo.
---
## 2024-10-14
- **Versió inicial**: clon del repositori de Coffee Crisis, adaptat per a Arcade Edition.
- Pasaeta de include-what-you-use i cppcheck.
- Estandarització de noms segons convencions (CamelCase, camelBack, snake_case).
- Herències de les classes Sprite corregides.
- Canvi a C++ modern amb smart pointers per a la càrrega de surfaces des de GIF.
- Eliminats últims defines i passats a enum class.

101
CLAUDE.md Normal file
View File

@@ -0,0 +1,101 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Coffee Crisis Arcade Edition is a 2-player cooperative arcade shooter built with C++20 and SDL3. Players defend coffee against giant balloons. The game targets Windows, Linux, macOS (Intel/Apple Silicon), Raspberry Pi, and Anbernic handhelds.
## Build Commands
The project uses both CMake and a top-level Makefile. The Makefile is the primary build interface.
### CMake (generates compile_commands.json for IDE/linter support)
```bash
cmake -B build -DCMAKE_BUILD_TYPE=Debug # configure
cmake --build build # build
```
### Makefile (delegates to CMake)
```bash
make # build Release via cmake
make debug # build Debug via cmake
make release # create release package (auto-detects OS)
make linux_release # release tar.gz with resources.pack
make windows_release # release zip for Windows
make macos_release # release dmg for macOS (Intel + Apple Silicon)
make raspi_release # release tar.gz for Raspberry Pi
```
### Tools & Resources
```bash
make pack_tool # compile resource packer
make resources.pack # pack data/ into resources.pack (required for release builds)
make spirv # compile GLSL shaders to SPIR-V headers
```
### Code Quality
```bash
make format # run clang-format on all sources (or: cmake --build build --target format)
make format-check # check formatting without modifying
make tidy # run clang-tidy static analysis (cmake --build build --target tidy)
make tidy-fix # run clang-tidy with auto-fix
```
## Architecture
### Singletons (core systems)
- **Director** (`source/director.hpp`) — Application state machine, orchestrates scene transitions (Logo → Intro → Title → Game → Credits/HiScore → Title)
- **Screen** (`source/screen.hpp`) — Window management, SDL3 GPU rendering pipeline, post-processing effects
- **Resource** (`source/resource.hpp`) — Asset loading/caching with PRELOAD and LAZY_LOAD modes, reads from `resources.pack`
- **Audio** (`source/audio.hpp`) — Music and SFX management
- **Input** (`source/input.hpp`) — Keyboard and gamepad input handling
### Scenes (source/sections/)
Each scene is a self-contained class with update/render lifecycle. Scene flow is managed by Director.
### Entity Managers
- `BalloonManager` / `BulletManager` — Object pool-based entity management
- `Player` — Two-player support (player 1: keyboard, player 2: gamepad)
### Rendering Pipeline
- SDL3 GPU API (Vulkan/Metal/D3D12 backends)
- SPIR-V shaders compiled offline from GLSL (`data/shaders/`) via `glslc`
- Compiled shader headers embedded in `source/rendering/sdl3gpu/postfx_*_spv.h`
- macOS uses Metal (no SPIR-V compilation needed)
### Configuration
- Game parameters: `config/param_320x240.txt`, `config/param_320x256.txt`
- Asset manifest: `config/assets.txt`
- Balloon formations: `config/formations.txt`
- Level definitions: `config/stages.txt`
- Gamepad mappings: `config/gamecontrollerdb.txt`
### External Libraries (header-only/vendored in source/external/)
- nlohmann/json, fkyaml (YAML), stb_image, stb_vorbis, jail_audio
## Code Style
Enforced via `.clang-format` (Google-based) and `.clang-tidy`:
- **Naming conventions**: Classes/structs `CamelCase`, methods/functions `camelBack`, variables/params `snake_case`, private/protected members `snake_case_` (trailing underscore), constants/constexpr `UPPER_CASE`, namespaces `CamelCase`, enum values `UPPER_CASE`
- 4-space indentation, no column limit, braces attach to statement
- clang-tidy treats all warnings as errors
## Conditional Compilation Defines
| Define | Purpose |
|--------|---------|
| `WINDOWS_BUILD` / `LINUX_BUILD` / `MACOS_BUILD` | Platform selection |
| `DEBUG` / `VERBOSE` | Debug output |
| `RELEASE_BUILD` | Release-specific code paths |
| `RECORDING` | Demo recording mode |
| `NO_SHADERS` | Disable shader pipeline (Anbernic) |
| `NO_AUDIO` | Build without audio |
| `ARCADE` | Arcade-specific mode |
| `MACOS_BUNDLE` | macOS .app bundle paths |
| `ANBERNIC` | Anbernic handheld build |
## Language
Code comments are in Spanish/Catalan. Game UI supports multiple languages via JSON files in `data/lang/`.

View File

@@ -81,6 +81,7 @@ set(APP_SOURCES
# --- Sprites y Gráficos --- # --- Sprites y Gráficos ---
source/animated_sprite.cpp source/animated_sprite.cpp
source/background.cpp source/background.cpp
source/card_sprite.cpp
source/fade.cpp source/fade.cpp
source/moving_sprite.cpp source/moving_sprite.cpp
source/path_sprite.cpp source/path_sprite.cpp
@@ -123,32 +124,43 @@ message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
if(NOT APPLE) if(NOT APPLE)
find_program(GLSLC_EXE NAMES glslc) find_program(GLSLC_EXE NAMES glslc)
set(SHADER_VERT_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.vert") set(SHADER_VERT_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.vert")
set(SHADER_FRAG_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.frag") set(SHADER_FRAG_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.frag")
set(SHADER_VERT_H "${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu/postfx_vert_spv.h") set(SHADER_CRTPI_SRC "${CMAKE_SOURCE_DIR}/data/shaders/crtpi_frag.glsl")
set(SHADER_FRAG_H "${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu/postfx_frag_spv.h") 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/rendering/sdl3gpu/postfx_vert_spv.h")
set(SHADER_FRAG_H "${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu/postfx_frag_spv.h")
set(SHADER_CRTPI_H "${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu/crtpi_frag_spv.h")
set(SHADER_UPSCALE_H "${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu/upscale_frag_spv.h")
set(SHADER_DOWNSCALE_H "${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu/downscale_frag_spv.h")
set(ALL_SHADER_SOURCES "${SHADER_VERT_SRC}" "${SHADER_FRAG_SRC}" "${SHADER_CRTPI_SRC}" "${SHADER_UPSCALE_SRC}" "${SHADER_DOWNSCALE_SRC}")
set(ALL_SHADER_HEADERS "${SHADER_VERT_H}" "${SHADER_FRAG_H}" "${SHADER_CRTPI_H}" "${SHADER_UPSCALE_H}" "${SHADER_DOWNSCALE_H}")
if(GLSLC_EXE) if(GLSLC_EXE)
add_custom_command( add_custom_command(
OUTPUT "${SHADER_VERT_H}" "${SHADER_FRAG_H}" OUTPUT ${ALL_SHADER_HEADERS}
COMMAND "${CMAKE_SOURCE_DIR}/tools/shaders/compile_spirv.sh" COMMAND "${CMAKE_SOURCE_DIR}/tools/shaders/compile_spirv.sh"
DEPENDS "${SHADER_VERT_SRC}" "${SHADER_FRAG_SRC}" DEPENDS ${ALL_SHADER_SOURCES}
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
COMMENT "Compilando shaders SPIR-V..." COMMENT "Compilando shaders SPIR-V..."
) )
add_custom_target(shaders DEPENDS "${SHADER_VERT_H}" "${SHADER_FRAG_H}") add_custom_target(shaders DEPENDS ${ALL_SHADER_HEADERS})
message(STATUS "glslc encontrado: shaders se compilarán automáticamente") message(STATUS "glslc encontrado: shaders se compilarán automáticamente")
else() else()
if(NOT EXISTS "${SHADER_VERT_H}" OR NOT EXISTS "${SHADER_FRAG_H}") foreach(_h IN LISTS ALL_SHADER_HEADERS)
message(FATAL_ERROR if(NOT EXISTS "${_h}")
"glslc no encontrado y headers SPIR-V no existen.\n" message(FATAL_ERROR
" Instala glslc: sudo apt install glslang-tools (Linux)\n" "glslc no encontrado y header SPIR-V no existe: ${_h}\n"
" choco install vulkan-sdk (Windows)\n" " Instala glslc: sudo apt install glslang-tools (Linux)\n"
" O genera los headers manualmente: tools/shaders/compile_spirv.sh" " choco install vulkan-sdk (Windows)\n"
) " O genera los headers manualmente: tools/shaders/compile_spirv.sh"
else() )
message(STATUS "glslc no encontrado - usando headers SPIR-V precompilados") endif()
endif() endforeach()
message(STATUS "glslc no encontrado - usando headers SPIR-V precompilados")
endif() endif()
else() else()
message(STATUS "macOS: shaders SPIR-V omitidos (usa Metal)") message(STATUS "macOS: shaders SPIR-V omitidos (usa Metal)")
@@ -192,7 +204,17 @@ if(WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE ws2_32 mingw32) target_link_libraries(${PROJECT_NAME} PRIVATE ws2_32 mingw32)
elseif(APPLE) elseif(APPLE)
target_compile_definitions(${PROJECT_NAME} PRIVATE MACOS_BUILD) target_compile_definitions(${PROJECT_NAME} PRIVATE MACOS_BUILD)
set(CMAKE_OSX_ARCHITECTURES "arm64") if(NOT CMAKE_OSX_ARCHITECTURES)
set(CMAKE_OSX_ARCHITECTURES "arm64")
endif()
if(MACOS_BUNDLE)
target_compile_definitions(${PROJECT_NAME} PRIVATE MACOS_BUNDLE)
target_link_options(${PROJECT_NAME} PRIVATE
-framework SDL3
-F ${CMAKE_SOURCE_DIR}/release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64
-rpath @executable_path/../Frameworks/
)
endif()
elseif(UNIX AND NOT APPLE) elseif(UNIX AND NOT APPLE)
target_compile_definitions(${PROJECT_NAME} PRIVATE LINUX_BUILD) target_compile_definitions(${PROJECT_NAME} PRIVATE LINUX_BUILD)
endif() endif()

415
Makefile
View File

@@ -1,233 +1,168 @@
# Directorios # ==============================================================================
# DIRECTORIES
# ==============================================================================
DIR_ROOT := $(dir $(abspath $(MAKEFILE_LIST))) DIR_ROOT := $(dir $(abspath $(MAKEFILE_LIST)))
DIR_SOURCES := $(addsuffix /, $(DIR_ROOT)source)
DIR_BIN := $(addsuffix /, $(DIR_ROOT))
DIR_BUILD := $(addsuffix /, $(DIR_ROOT)build)
DIR_TOOLS := $(addsuffix /, $(DIR_ROOT)tools) DIR_TOOLS := $(addsuffix /, $(DIR_ROOT)tools)
# Variables # ==============================================================================
# TARGET NAMES
# ==============================================================================
TARGET_NAME := coffee_crisis_arcade_edition TARGET_NAME := coffee_crisis_arcade_edition
TARGET_FILE := $(DIR_BIN)$(TARGET_NAME) TARGET_FILE := $(DIR_ROOT)$(TARGET_NAME)
APP_NAME := Coffee Crisis Arcade Edition APP_NAME := Coffee Crisis Arcade Edition
DIST_DIR := dist DIST_DIR := dist
RELEASE_FOLDER := dist/_tmp RELEASE_FOLDER := dist/_tmp
RELEASE_FILE := $(RELEASE_FOLDER)/$(TARGET_NAME) RELEASE_FILE := $(RELEASE_FOLDER)/$(TARGET_NAME)
RESOURCE_FILE := release/windows/coffee.res RESOURCE_FILE := release/windows/coffee.res
# Variables para herramienta de empaquetado # ==============================================================================
ifeq ($(OS),Windows_NT) # TOOLS
PACK_TOOL := $(DIR_TOOLS)pack_resources/pack_resources.exe # ==============================================================================
PACK_CXX := $(CXX) DIR_PACK_TOOL := $(DIR_TOOLS)pack_resources
else SHADER_SCRIPT := $(DIR_ROOT)tools/shaders/compile_spirv.sh
PACK_TOOL := $(DIR_TOOLS)pack_resources/pack_resources
PACK_CXX := $(CXX)
endif
PACK_SOURCES := $(DIR_TOOLS)pack_resources/pack_resources.cpp $(DIR_SOURCES)resource_pack.cpp
PACK_INCLUDES := -I$(DIR_ROOT) -I$(DIR_BUILD)
# Versión automática basada en la fecha actual (específica por SO) # ==============================================================================
# VERSION (fecha actual)
# ==============================================================================
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
VERSION := $(shell powershell -Command "Get-Date -Format 'yyyy-MM-dd'") VERSION := $(shell powershell -Command "Get-Date -Format 'yyyy-MM-dd'")
else else
VERSION := $(shell date +%Y-%m-%d) VERSION := $(shell date +%Y-%m-%d)
endif endif
# Variables específicas para Windows (usando APP_NAME) # ==============================================================================
# SHELL (Windows usa cmd.exe)
# ==============================================================================
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
WIN_TARGET_FILE := $(DIR_BIN)$(APP_NAME) SHELL := cmd.exe
endif
# ==============================================================================
# WINDOWS-SPECIFIC VARIABLES
# ==============================================================================
ifeq ($(OS),Windows_NT)
WIN_TARGET_FILE := $(DIR_ROOT)$(APP_NAME)
WIN_RELEASE_FILE := $(RELEASE_FOLDER)/$(APP_NAME) WIN_RELEASE_FILE := $(RELEASE_FOLDER)/$(APP_NAME)
else else
WIN_TARGET_FILE := $(TARGET_FILE) WIN_TARGET_FILE := $(TARGET_FILE)
WIN_RELEASE_FILE := $(RELEASE_FILE) WIN_RELEASE_FILE := $(RELEASE_FILE)
endif endif
# Nombres para los ficheros de lanzamiento # ==============================================================================
# RELEASE NAMES
# ==============================================================================
WINDOWS_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-win32-x64.zip WINDOWS_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-win32-x64.zip
MACOS_INTEL_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-macos-intel.dmg MACOS_INTEL_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-macos-intel.dmg
MACOS_APPLE_SILICON_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-macos-apple-silicon.dmg MACOS_APPLE_SILICON_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-macos-apple-silicon.dmg
LINUX_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-linux.tar.gz LINUX_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-linux.tar.gz
RASPI_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-raspberry.tar.gz RASPI_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-raspberry.tar.gz
# Lista completa de archivos fuente (basada en CMakeLists.txt) # ==============================================================================
APP_SOURCES := \ # PLATAFORMA
source/asset.cpp \ # ==============================================================================
source/audio.cpp \
source/director.cpp \
source/global_events.cpp \
source/global_inputs.cpp \
source/input.cpp \
source/lang.cpp \
source/main.cpp \
source/param.cpp \
source/resource.cpp \
source/resource_helper.cpp \
source/resource_loader.cpp \
source/resource_pack.cpp \
source/screen.cpp \
source/text.cpp \
source/writer.cpp \
source/ui/menu_option.cpp \
source/ui/menu_renderer.cpp \
source/ui/notifier.cpp \
source/ui/service_menu.cpp \
source/ui/ui_message.cpp \
source/ui/window_message.cpp \
source/balloon_formations.cpp \
source/balloon_manager.cpp \
source/balloon.cpp \
source/bullet.cpp \
source/bullet_manager.cpp \
source/enter_name.cpp \
source/explosions.cpp \
source/game_logo.cpp \
source/item.cpp \
source/manage_hiscore_table.cpp \
source/player.cpp \
source/scoreboard.cpp \
source/tabe.cpp \
source/sections/credits.cpp \
source/sections/game.cpp \
source/sections/hiscore_table.cpp \
source/sections/instructions.cpp \
source/sections/intro.cpp \
source/sections/logo.cpp \
source/sections/title.cpp \
source/animated_sprite.cpp \
source/background.cpp \
source/fade.cpp \
source/moving_sprite.cpp \
source/path_sprite.cpp \
source/smart_sprite.cpp \
source/sprite.cpp \
source/texture.cpp \
source/tiled_bg.cpp \
source/color.cpp \
source/demo.cpp \
source/define_buttons.cpp \
source/difficulty.cpp \
source/input_types.cpp \
source/mouse.cpp \
source/options.cpp \
source/shutdown.cpp \
source/stage.cpp \
source/system_utils.cpp \
source/utils.cpp \
source/external/jail_audio.cpp \
source/external/gif.cpp \
source/rendering/sdl3gpu/sdl3gpu_shader.cpp
# Includes
INCLUDES := -Isource -Isource/external -Isource/rendering -Isource/rendering/sdl3gpu -I$(DIR_BUILD)
# Variables según el sistema operativo
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
FixPath = $(subst /,\\,$1) FixPath = $(subst /,\\,$1)
CXXFLAGS := -std=c++20 -Wall -Os -ffunction-sections -fdata-sections -Wl,--gc-sections -static-libstdc++ -static-libgcc -Wl,-Bstatic -lpthread -Wl,-Bdynamic -Wl,-subsystem,windows -DWINDOWS_BUILD
CXXFLAGS_DEBUG := -std=c++20 -Wall -g -D_DEBUG -DWINDOWS_BUILD
LDFLAGS := -lmingw32 -lws2_32 -lSDL3
RM := del /Q RM := del /Q
MKDIR := mkdir MKDIR := mkdir
else else
FixPath = $1 FixPath = $1
CXXFLAGS := -std=c++20 -Wall -Os -ffunction-sections -fdata-sections
CXXFLAGS_DEBUG := -std=c++20 -Wall -g -D_DEBUG
LDFLAGS := -lSDL3
RMFILE := rm -f RMFILE := rm -f
RMDIR := rm -rdf RMDIR := rm -rdf
MKDIR := mkdir -p MKDIR := mkdir -p
UNAME_S := $(shell uname -s) UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
CXXFLAGS += -DLINUX_BUILD
endif
ifeq ($(UNAME_S),Darwin)
CXXFLAGS += -DMACOS_BUILD
CXXFLAGS_DEBUG += -DMACOS_BUILD
# Configurar arquitectura (por defecto arm64, como en CMake)
CXXFLAGS += -arch arm64
CXXFLAGS_DEBUG += -arch arm64
endif
endif endif
# Reglas para herramienta de empaquetado y resources.pack # ==============================================================================
$(PACK_TOOL): FORCE # COMPILACIÓN CON CMAKE
@echo "Compilando herramienta de empaquetado..." # ==============================================================================
$(PACK_CXX) -std=c++20 -Wall -Os $(PACK_INCLUDES) $(PACK_SOURCES) -o $(PACK_TOOL) all:
@echo "✓ Herramienta de empaquetado lista: $(PACK_TOOL)" @cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
@cmake --build build
pack_tool: $(PACK_TOOL) debug:
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
@cmake --build build
resources.pack: $(PACK_TOOL) # ==============================================================================
@echo "Generando resources.pack desde directorio data/..." # RELEASE AUTOMÁTICO (detecta SO)
$(PACK_TOOL) data resources.pack # ==============================================================================
@echo "✓ resources.pack generado exitosamente" release:
ifeq ($(OS),Windows_NT)
@"$(MAKE)" windows_release
else
ifeq ($(UNAME_S),Darwin)
@$(MAKE) macos_release
else
@$(MAKE) linux_release
endif
endif
# Reglas para compilación # ==============================================================================
windows: # REGLAS PARA HERRAMIENTA DE EMPAQUETADO Y RESOURCES.PACK
@echo off # ==============================================================================
@echo Compilando para Windows con nombre: "$(APP_NAME).exe" pack_tool:
windres release/windows/coffee.rc -O coff -o $(RESOURCE_FILE) @$(MAKE) -C $(DIR_PACK_TOOL)
$(CXX) $(APP_SOURCES) $(RESOURCE_FILE) $(INCLUDES) $(CXXFLAGS) $(LDFLAGS) -o "$(WIN_TARGET_FILE).exe"
strip -s -R .comment -R .gnu.version "$(WIN_TARGET_FILE).exe" --strip-unneeded
windows_rec: resources.pack:
@echo off @$(MAKE) -C $(DIR_PACK_TOOL) pack
@echo Compilando version de grabacion para Windows: "$(APP_NAME)_rec.exe"
$(CXX) $(APP_SOURCES) $(INCLUDES) -DRECORDING $(CXXFLAGS) $(LDFLAGS) -o "$(WIN_TARGET_FILE)_rec.exe"
windows_debug: # ==============================================================================
@echo off # COMPILACIÓN DE SHADERS
@echo Compilando version debug para Windows: "$(APP_NAME)_debug.exe" # ==============================================================================
$(CXX) $(APP_SOURCES) $(INCLUDES) -DDEBUG -DVERBOSE $(CXXFLAGS_DEBUG) $(LDFLAGS) -o "$(WIN_TARGET_FILE)_debug.exe" spirv:
@echo "Compilando shaders SPIR-V..."
$(SHADER_SCRIPT)
# ==============================================================================
# COMPILACIÓN PARA WINDOWS (RELEASE)
# ==============================================================================
windows_release: windows_release:
@$(MAKE) pack_tool
@$(MAKE) resources.pack @$(MAKE) resources.pack
@echo off @echo off
@echo Creando release para Windows - Version: $(VERSION) @echo Creando release para Windows - Version: $(VERSION)
# Compila con cmake
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
@cmake --build build
# Crea carpeta de distribución y carpeta temporal 'RELEASE_FOLDER' # Crea carpeta de distribución y carpeta temporal 'RELEASE_FOLDER'
powershell if (-not (Test-Path "$(DIST_DIR)")) {New-Item "$(DIST_DIR)" -ItemType Directory} @powershell -Command "if (-not (Test-Path '$(DIST_DIR)')) {New-Item '$(DIST_DIR)' -ItemType Directory}"
powershell if (Test-Path "$(RELEASE_FOLDER)") {Remove-Item "$(RELEASE_FOLDER)" -Recurse -Force} @powershell -Command "if (Test-Path '$(RELEASE_FOLDER)') {Remove-Item '$(RELEASE_FOLDER)' -Recurse -Force}"
powershell 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 la carpeta 'config' y el archivo 'resources.pack' # Copia la carpeta 'config' y el archivo 'resources.pack'
powershell Copy-Item -Path "config" -Destination "$(RELEASE_FOLDER)" -recurse -Force @powershell -Command "Copy-Item -Path 'config' -Destination '$(RELEASE_FOLDER)' -recurse -Force"
powershell Copy-Item -Path "resources.pack" -Destination "$(RELEASE_FOLDER)" @powershell -Command "Copy-Item -Path 'resources.pack' -Destination '$(RELEASE_FOLDER)'"
# Copia los ficheros que estan en la raíz del proyecto # Copia los ficheros que estan en la raíz del proyecto
powershell Copy-Item "LICENSE" -Destination "$(RELEASE_FOLDER)" @powershell -Command "Copy-Item 'LICENSE' -Destination '$(RELEASE_FOLDER)'"
powershell Copy-Item "README.md" -Destination "$(RELEASE_FOLDER)" @powershell -Command "Copy-Item 'README.md' -Destination '$(RELEASE_FOLDER)'"
powershell 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\"'"
# Compila
windres release/windows/coffee.rc -O coff -o $(RESOURCE_FILE)
$(CXX) $(APP_SOURCES) $(RESOURCE_FILE) $(INCLUDES) -DRELEASE_BUILD $(CXXFLAGS) $(LDFLAGS) -o "$(WIN_RELEASE_FILE).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
powershell if (Test-Path "$(WINDOWS_RELEASE)") {Remove-Item "$(WINDOWS_RELEASE)"} @powershell -Command "if (Test-Path '$(WINDOWS_RELEASE)') {Remove-Item '$(WINDOWS_RELEASE)'}"
powershell Compress-Archive -Path "$(RELEASE_FOLDER)"/* -DestinationPath "$(WINDOWS_RELEASE)" @powershell -Command "Compress-Archive -Path '$(RELEASE_FOLDER)/*' -DestinationPath '$(WINDOWS_RELEASE)'"
@echo Release creado: $(WINDOWS_RELEASE) @echo Release creado: $(WINDOWS_RELEASE)
# Elimina la carpeta temporal 'RELEASE_FOLDER' # Elimina la carpeta temporal 'RELEASE_FOLDER'
powershell if (Test-Path "$(RELEASE_FOLDER)") {Remove-Item "$(RELEASE_FOLDER)" -Recurse -Force} @powershell -Command "if (Test-Path '$(RELEASE_FOLDER)') {Remove-Item '$(RELEASE_FOLDER)' -Recurse -Force}"
macos:
@echo "Compilando para macOS: $(TARGET_NAME)"
$(CXX) $(APP_SOURCES) $(INCLUDES) $(CXXFLAGS) $(LDFLAGS) -o "$(TARGET_FILE)"
macos_debug:
@echo "Compilando version debug para macOS: $(TARGET_NAME)_debug"
$(CXX) $(APP_SOURCES) $(INCLUDES) -DDEBUG -DVERBOSE $(CXXFLAGS_DEBUG) $(LDFLAGS) -o "$(TARGET_FILE)_debug"
# ==============================================================================
# COMPILACIÓN PARA MACOS (RELEASE)
# ==============================================================================
macos_release: macos_release:
@$(MAKE) pack_tool
@$(MAKE) resources.pack @$(MAKE) resources.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
@cmake -S . -B build/intel -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DMACOS_BUNDLE=ON
@cmake --build build/intel
# Elimina datos de compilaciones anteriores # Elimina datos de compilaciones anteriores
$(RMDIR) "$(RELEASE_FOLDER)" $(RMDIR) "$(RELEASE_FOLDER)"
$(RMFILE) tmp.dmg $(RMFILE) tmp.dmg
@@ -250,9 +185,8 @@ macos_release:
cp LICENSE "$(RELEASE_FOLDER)" cp LICENSE "$(RELEASE_FOLDER)"
cp README.md "$(RELEASE_FOLDER)" cp README.md "$(RELEASE_FOLDER)"
# Compila la versión para procesadores Intel # Copia el ejecutable Intel al bundle
ifdef ENABLE_MACOS_X86_64 cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)"
$(CXX) $(APP_SOURCES) $(INCLUDES) -DMACOS_BUNDLE -DMACOS_BUILD -DRELEASE_BUILD -std=c++20 -Wall -Os -Wno-deprecated -framework SDL3 -F release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64 -ffunction-sections -fdata-sections -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target x86_64-apple-macos10.15
# Firma la aplicación # Firma la aplicación
codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app" codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app"
@@ -273,10 +207,11 @@ ifdef ENABLE_MACOS_X86_64
"$(MACOS_INTEL_RELEASE)" \ "$(MACOS_INTEL_RELEASE)" \
"$(RELEASE_FOLDER)" || true "$(RELEASE_FOLDER)" || true
@echo "Release Intel creado: $(MACOS_INTEL_RELEASE)" @echo "Release Intel creado: $(MACOS_INTEL_RELEASE)"
endif
# Compila la versión para procesadores Apple Silicon # Compila la versión para procesadores Apple Silicon con cmake
$(CXX) $(APP_SOURCES) $(INCLUDES) -DMACOS_BUNDLE -DMACOS_BUILD -DRELEASE_BUILD -DSDL_DISABLE_IMMINTRIN_H -std=c++20 -Wall -Os -Wno-deprecated -framework SDL3 -F release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64 -ffunction-sections -fdata-sections -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target arm64-apple-macos11 @cmake -S . -B build/arm -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 -DMACOS_BUNDLE=ON
@cmake --build build/arm
cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)"
# Firma la aplicación # Firma la aplicación
codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app" codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app"
@@ -300,22 +235,22 @@ endif
# Elimina las carpetas temporales # Elimina las carpetas temporales
$(RMDIR) "$(RELEASE_FOLDER)" $(RMDIR) "$(RELEASE_FOLDER)"
$(RMDIR) build/intel
$(RMDIR) build/arm
$(RMFILE) "$(DIST_DIR)"/rw.* $(RMFILE) "$(DIST_DIR)"/rw.*
linux: # ==============================================================================
@echo "Compilando para Linux: $(TARGET_NAME)" # COMPILACIÓN PARA LINUX (RELEASE)
$(CXX) $(APP_SOURCES) $(INCLUDES) $(CXXFLAGS) $(LDFLAGS) -o "$(TARGET_FILE)" # ==============================================================================
strip -s -R .comment -R .gnu.version "$(TARGET_FILE)" --strip-unneeded
linux_debug:
@echo "Compilando version debug para Linux: $(TARGET_NAME)_debug"
$(CXX) $(APP_SOURCES) $(INCLUDES) -DDEBUG -DVERBOSE $(CXXFLAGS_DEBUG) $(LDFLAGS) -o "$(TARGET_FILE)_debug"
linux_release: linux_release:
@$(MAKE) pack_tool
@$(MAKE) resources.pack @$(MAKE) resources.pack
@echo "Creando release para Linux - Version: $(VERSION)" @echo "Creando release para Linux - Version: $(VERSION)"
# Elimina carpetas previas y recrea (crea dist/ si no existe)
# Compila con cmake
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
@cmake --build build
# Elimina carpeta temporal previa y la recrea (crea dist/ si no existe)
$(RMDIR) "$(RELEASE_FOLDER)" $(RMDIR) "$(RELEASE_FOLDER)"
$(MKDIR) "$(RELEASE_FOLDER)" $(MKDIR) "$(RELEASE_FOLDER)"
@@ -324,9 +259,7 @@ linux_release:
cp resources.pack "$(RELEASE_FOLDER)" cp resources.pack "$(RELEASE_FOLDER)"
cp LICENSE "$(RELEASE_FOLDER)" cp LICENSE "$(RELEASE_FOLDER)"
cp README.md "$(RELEASE_FOLDER)" cp README.md "$(RELEASE_FOLDER)"
cp "$(TARGET_FILE)" "$(RELEASE_FILE)"
# Compila
$(CXX) $(APP_SOURCES) $(INCLUDES) -DRELEASE_BUILD $(CXXFLAGS) $(LDFLAGS) -o "$(RELEASE_FILE)"
strip -s -R .comment -R .gnu.version "$(RELEASE_FILE)" --strip-unneeded strip -s -R .comment -R .gnu.version "$(RELEASE_FILE)" --strip-unneeded
# Empaqueta ficheros # Empaqueta ficheros
@@ -337,10 +270,17 @@ linux_release:
# Elimina la carpeta temporal # Elimina la carpeta temporal
$(RMDIR) "$(RELEASE_FOLDER)" $(RMDIR) "$(RELEASE_FOLDER)"
# ==============================================================================
# COMPILACIÓN PARA LINUX (RELEASE CON INTEGRACIÓN DESKTOP)
# ==============================================================================
linux_release_desktop: linux_release_desktop:
@$(MAKE) pack_tool
@$(MAKE) resources.pack @$(MAKE) resources.pack
@echo "Creando release con integracion desktop para Linux - Version: $(VERSION)" @echo "Creando release con integracion desktop para Linux - Version: $(VERSION)"
# Compila con cmake
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
@cmake --build build
# Elimina carpetas previas y recrea (crea dist/ si no existe) # Elimina carpetas previas y recrea (crea dist/ si no existe)
$(RMDIR) "$(RELEASE_FOLDER)" $(RMDIR) "$(RELEASE_FOLDER)"
@@ -357,8 +297,8 @@ linux_release_desktop:
cp LICENSE "$(RELEASE_FOLDER)/$(TARGET_NAME)/" cp LICENSE "$(RELEASE_FOLDER)/$(TARGET_NAME)/"
cp README.md "$(RELEASE_FOLDER)/$(TARGET_NAME)/" cp README.md "$(RELEASE_FOLDER)/$(TARGET_NAME)/"
# Compila el ejecutable # Copia el ejecutable
$(CXX) $(APP_SOURCES) $(INCLUDES) -DRELEASE_BUILD $(CXXFLAGS) $(LDFLAGS) -o "$(RELEASE_FOLDER)/$(TARGET_NAME)/bin/$(TARGET_NAME)" cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(TARGET_NAME)/bin/$(TARGET_NAME)"
strip -s -R .comment -R .gnu.version "$(RELEASE_FOLDER)/$(TARGET_NAME)/bin/$(TARGET_NAME)" --strip-unneeded strip -s -R .comment -R .gnu.version "$(RELEASE_FOLDER)/$(TARGET_NAME)/bin/$(TARGET_NAME)" --strip-unneeded
# Crea el archivo .desktop # Crea el archivo .desktop
@@ -432,19 +372,17 @@ linux_release_desktop:
# Elimina la carpeta temporal # Elimina la carpeta temporal
$(RMDIR) "$(RELEASE_FOLDER)" $(RMDIR) "$(RELEASE_FOLDER)"
raspi: # ==============================================================================
@echo "Compilando para Raspberry Pi: $(TARGET_NAME)" # COMPILACIÓN PARA RASPBERRY PI (RELEASE)
$(CXX) $(APP_SOURCES) $(INCLUDES) -DVERBOSE $(CXXFLAGS) $(LDFLAGS) -o $(TARGET_FILE) # ==============================================================================
strip -s -R .comment -R .gnu.version $(TARGET_FILE) --strip-unneeded
raspi_debug:
@echo "Compilando version debug para Raspberry Pi: $(TARGET_NAME)_debug"
$(CXX) $(APP_SOURCES) $(INCLUDES) -DVERBOSE -DDEBUG $(CXXFLAGS_DEBUG) $(LDFLAGS) -o "$(TARGET_FILE)_debug"
raspi_release: raspi_release:
@$(MAKE) pack_tool
@$(MAKE) resources.pack @$(MAKE) resources.pack
@echo "Creando release para Raspberry Pi - Version: $(VERSION)" @echo "Creando release para Raspberry Pi - Version: $(VERSION)"
# Compila con cmake
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
@cmake --build build
# Elimina carpetas previas y recrea (crea dist/ si no existe) # Elimina carpetas previas y recrea (crea dist/ si no existe)
$(RMDIR) "$(RELEASE_FOLDER)" $(RMDIR) "$(RELEASE_FOLDER)"
$(MKDIR) "$(RELEASE_FOLDER)" $(MKDIR) "$(RELEASE_FOLDER)"
@@ -454,9 +392,7 @@ raspi_release:
cp resources.pack "$(RELEASE_FOLDER)" cp resources.pack "$(RELEASE_FOLDER)"
cp LICENSE "$(RELEASE_FOLDER)" cp LICENSE "$(RELEASE_FOLDER)"
cp README.md "$(RELEASE_FOLDER)" cp README.md "$(RELEASE_FOLDER)"
cp "$(TARGET_FILE)" "$(RELEASE_FILE)"
# Compila
$(CXX) $(APP_SOURCES) $(INCLUDES) -DRELEASE_BUILD -DVERBOSE $(CXXFLAGS) $(LDFLAGS) -o "$(RELEASE_FILE)"
strip -s -R .comment -R .gnu.version "$(RELEASE_FILE)" --strip-unneeded strip -s -R .comment -R .gnu.version "$(RELEASE_FILE)" --strip-unneeded
# Empaqueta ficheros # Empaqueta ficheros
@@ -467,59 +403,60 @@ raspi_release:
# Elimina la carpeta temporal # Elimina la carpeta temporal
$(RMDIR) "$(RELEASE_FOLDER)" $(RMDIR) "$(RELEASE_FOLDER)"
anbernic: # ==============================================================================
@$(MAKE) pack_tool # CODE QUALITY (delegados a cmake)
@$(MAKE) resources.pack # ==============================================================================
@echo "Compilando para Anbernic: $(TARGET_NAME)" format:
# Elimina carpetas previas @cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
$(RMDIR) "$(RELEASE_FOLDER)"_anbernic @cmake --build build --target format
# Crea la carpeta temporal para realizar el lanzamiento format-check:
$(MKDIR) "$(RELEASE_FOLDER)"_anbernic @cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
@cmake --build build --target format-check
# Copia ficheros tidy:
cp -R config "$(RELEASE_FOLDER)"_anbernic @cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cp resources.pack "$(RELEASE_FOLDER)"_anbernic @cmake --build build --target tidy
# Compila tidy-fix:
$(CXX) $(APP_SOURCES) $(INCLUDES) -DRELEASE_BUILD -DANBERNIC -DNO_SHADERS -DARCADE -DVERBOSE $(CXXFLAGS) $(LDFLAGS) -o $(RELEASE_FOLDER)_anbernic/$(TARGET_NAME) @cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
@cmake --build build --target tidy-fix
# Opción para deshabilitar audio (equivalente a la opción DISABLE_AUDIO de CMake) # ==============================================================================
no_audio: # REGLAS ESPECIALES
@echo "Compilando sin audio: $(TARGET_NAME)_no_audio" # ==============================================================================
$(CXX) $(filter-out source/external/jail_audio.cpp,$(APP_SOURCES)) $(INCLUDES) -DNO_AUDIO $(CXXFLAGS) $(LDFLAGS) -o "$(TARGET_FILE)_no_audio"
# Regla para mostrar la versión actual
show_version: show_version:
@echo "Version actual: $(VERSION)" @echo "Version actual: $(VERSION)"
# Regla de ayuda
help: help:
@echo "Makefile para Coffee Crisis Arcade Edition" @echo "Makefile para Coffee Crisis Arcade Edition"
@echo "Comandos disponibles:" @echo "Comandos disponibles:"
@echo " windows - Compilar para Windows" @echo ""
@echo " windows_debug - Compilar debug para Windows" @echo " Compilacion:"
@echo " windows_release - Crear release completo para Windows" @echo " make - Compilar con cmake (Release)"
@echo " linux - Compilar para Linux" @echo " make debug - Compilar con cmake (Debug)"
@echo " linux_debug - Compilar debug para Linux" @echo ""
@echo " linux_release - Crear release basico para Linux" @echo " Release:"
@echo " linux_release_desktop - Crear release con integracion desktop para Linux" @echo " make release - Crear release (detecta SO automaticamente)"
@echo " macos - Compilar para macOS" @echo " make windows_release - Crear release para Windows"
@echo " macos_debug - Compilar debug para macOS" @echo " make linux_release - Crear release basico para Linux"
@echo " macos_release - Crear release completo para macOS" @echo " make linux_release_desktop - Crear release con integracion desktop para Linux"
@echo " raspi - Compilar para Raspberry Pi" @echo " make macos_release - Crear release para macOS"
@echo " raspi_release - Crear release completo para Raspberry Pi" @echo " make raspi_release - Crear release para Raspberry Pi"
@echo " anbernic - Compilar para Anbernic" @echo ""
@echo " no_audio - Compilar sin sistema de audio" @echo " Herramientas:"
@echo " pack_tool - Compilar herramienta de empaquetado" @echo " make spirv - Compilar shaders SPIR-V"
@echo " resources.pack - Generar pack de recursos desde data/" @echo " make pack_tool - Compilar herramienta de empaquetado"
@echo " show_version - Mostrar version actual ($(VERSION))" @echo " make resources.pack - Generar pack de recursos desde data/"
@echo " help - Mostrar esta ayuda" @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 ""
@echo " Otros:"
@echo " make show_version - Mostrar version actual ($(VERSION))"
@echo " make help - Mostrar esta ayuda"
spirv: .PHONY: all debug release windows_release macos_release linux_release linux_release_desktop raspi_release pack_tool resources.pack spirv format format-check tidy tidy-fix show_version help
@echo "Compilando shaders SPIR-V..."
tools/shaders/compile_spirv.sh
.PHONY: windows windows_rec windows_debug windows_release macos macos_debug macos_release linux linux_debug linux_release linux_release_desktop raspi raspi_debug raspi_release anbernic no_audio show_version help pack_tool resources.pack spirv
FORCE:

View File

@@ -7,6 +7,7 @@
DATA|${SYSTEM_FOLDER}/config.yaml|optional,absolute DATA|${SYSTEM_FOLDER}/config.yaml|optional,absolute
DATA|${SYSTEM_FOLDER}/controllers.json|optional,absolute DATA|${SYSTEM_FOLDER}/controllers.json|optional,absolute
DATA|${SYSTEM_FOLDER}/postfx.yaml|optional,absolute DATA|${SYSTEM_FOLDER}/postfx.yaml|optional,absolute
DATA|${SYSTEM_FOLDER}/crtpi.yaml|optional,absolute
DATA|${SYSTEM_FOLDER}/score.bin|optional,absolute DATA|${SYSTEM_FOLDER}/score.bin|optional,absolute
# Archivos de configuración del juego # Archivos de configuración del juego

View File

@@ -90,7 +90,7 @@ service_menu.window_message.text_safety_margin 15.0f # Margen de segu
service_menu.window_message.animation_duration 0.3f # Duración de animaciones de ventanas (segundos) service_menu.window_message.animation_duration 0.3f # Duración de animaciones de ventanas (segundos)
# --- INTRO --- # --- INTRO ---
intro.bg_color 4664BD # Color de fondo de la intro intro.bg_color 41526F # Color de fondo de la intro
intro.card_color CBDBFC # Color de las tarjetas en la intro intro.card_color CBDBFC # Color de las tarjetas en la intro
intro.shadow_color 00000080 # Color de la sombra de las tarjetas en la intro intro.shadow_color 00000080 # Color de la sombra de las tarjetas en la intro
intro.text_distance_from_bottom 48 # Posicion del texto intro.text_distance_from_bottom 48 # Posicion del texto

View File

@@ -90,7 +90,7 @@ service_menu.window_message.text_safety_margin 15.0f # Margen de segu
service_menu.window_message.animation_duration 0.3f # Duración de animaciones de ventanas (segundos) service_menu.window_message.animation_duration 0.3f # Duración de animaciones de ventanas (segundos)
# --- INTRO --- # --- INTRO ---
intro.bg_color 4664BD # Color de fondo de la intro intro.bg_color 41526F # Color de fondo de la intro
intro.card_color CBDBFC # Color de las tarjetas en la intro intro.card_color CBDBFC # Color de las tarjetas en la intro
intro.shadow_color 00000080 # Color de la sombra de las tarjetas en la intro intro.shadow_color 00000080 # Color de la sombra de las tarjetas en la intro
intro.text_distance_from_bottom 48 # Posición del texto desde la parte inferior intro.text_distance_from_bottom 48 # Posición del texto desde la parte inferior

View File

@@ -79,8 +79,9 @@
"[SERVICE_MENU] SHUTDOWN": "Apagar el sistema", "[SERVICE_MENU] SHUTDOWN": "Apagar el sistema",
"[SERVICE_MENU] FULLSCREEN": "Pantalla completa", "[SERVICE_MENU] FULLSCREEN": "Pantalla completa",
"[SERVICE_MENU] WINDOW_SIZE": "Tamany de la finestra", "[SERVICE_MENU] WINDOW_SIZE": "Tamany de la finestra",
"[SERVICE_MENU] POSTFX": "PostFX", "[SERVICE_MENU] SHADER": "Shader",
"[SERVICE_MENU] POSTFX_PRESET": "Preset PostFX", "[SERVICE_MENU] SHADER_DISABLED": "Desactivat",
"[SERVICE_MENU] SHADER_PRESET": "Preset",
"[SERVICE_MENU] SUPERSAMPLING": "Supermostreig", "[SERVICE_MENU] SUPERSAMPLING": "Supermostreig",
"[SERVICE_MENU] VSYNC": "Sincronisme vertical", "[SERVICE_MENU] VSYNC": "Sincronisme vertical",
"[SERVICE_MENU] INTEGER_SCALE": "Escalat sencer", "[SERVICE_MENU] INTEGER_SCALE": "Escalat sencer",

View File

@@ -78,8 +78,9 @@
"[SERVICE_MENU] SHUTDOWN": "Shutdown System", "[SERVICE_MENU] SHUTDOWN": "Shutdown System",
"[SERVICE_MENU] FULLSCREEN": "Fullscreen", "[SERVICE_MENU] FULLSCREEN": "Fullscreen",
"[SERVICE_MENU] WINDOW_SIZE": "Window Zoom", "[SERVICE_MENU] WINDOW_SIZE": "Window Zoom",
"[SERVICE_MENU] POSTFX": "PostFX", "[SERVICE_MENU] SHADER": "Shader",
"[SERVICE_MENU] POSTFX_PRESET": "PostFX Preset", "[SERVICE_MENU] SHADER_DISABLED": "Disabled",
"[SERVICE_MENU] SHADER_PRESET": "Preset",
"[SERVICE_MENU] SUPERSAMPLING": "Supersampling", "[SERVICE_MENU] SUPERSAMPLING": "Supersampling",
"[SERVICE_MENU] VSYNC": "V-Sync", "[SERVICE_MENU] VSYNC": "V-Sync",
"[SERVICE_MENU] INTEGER_SCALE": "Integer Scale", "[SERVICE_MENU] INTEGER_SCALE": "Integer Scale",

View File

@@ -78,8 +78,9 @@
"[SERVICE_MENU] SHUTDOWN": "Apagar el sistema", "[SERVICE_MENU] SHUTDOWN": "Apagar el sistema",
"[SERVICE_MENU] FULLSCREEN": "Pantalla completa", "[SERVICE_MENU] FULLSCREEN": "Pantalla completa",
"[SERVICE_MENU] WINDOW_SIZE": "Zoom de ventana", "[SERVICE_MENU] WINDOW_SIZE": "Zoom de ventana",
"[SERVICE_MENU] POSTFX": "PostFX", "[SERVICE_MENU] SHADER": "Shader",
"[SERVICE_MENU] POSTFX_PRESET": "Preset PostFX", "[SERVICE_MENU] SHADER_DISABLED": "Desactivado",
"[SERVICE_MENU] SHADER_PRESET": "Preset",
"[SERVICE_MENU] SUPERSAMPLING": "Supersampling", "[SERVICE_MENU] SUPERSAMPLING": "Supersampling",
"[SERVICE_MENU] VSYNC": "Sincronismo vertical", "[SERVICE_MENU] VSYNC": "Sincronismo vertical",
"[SERVICE_MENU] INTEGER_SCALE": "Escalado proporcional", "[SERVICE_MENU] INTEGER_SCALE": "Escalado proporcional",

View 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);
}

View 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);
}

15
data/shaders/upscale.frag Normal file
View 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);
}

View File

@@ -57,13 +57,13 @@ class AnimatedSprite : public MovingSprite {
void update(float delta_time) override; // Actualiza la animación (time-based) void update(float delta_time) override; // Actualiza la animación (time-based)
// --- Control de animaciones --- // --- Control de animaciones ---
void setCurrentAnimation(const std::string& name = "default", bool reset = true); // Establece la animación por nombre void setCurrentAnimation(const std::string& name = "default", bool reset = true); // Establece la animación por nombre
void setCurrentAnimation(int index = 0, bool reset = true); // Establece la animación por índice void setCurrentAnimation(int index = 0, bool reset = true); // Establece la animación por índice
void resetAnimation(); // Reinicia la animación actual void resetAnimation(); // Reinicia la animación actual
void setAnimationSpeed(float value); // Establece la velocidad de la animación void setAnimationSpeed(float value); // Establece la velocidad de la animación
[[nodiscard]] auto getAnimationSpeed() const -> float { return animations_[current_animation_].speed; } // Obtiene la velocidad de la animación actual [[nodiscard]] auto getAnimationSpeed() const -> float { return animations_[current_animation_].speed; } // Obtiene la velocidad de la animación actual
void animtionPause() { animations_[current_animation_].paused = true; } // Detiene la animación void animtionPause() { animations_[current_animation_].paused = true; } // Detiene la animación
void animationResume() { animations_[current_animation_].paused = false; } // Reanuda la animación void animationResume() { animations_[current_animation_].paused = false; } // Reanuda la animación
[[nodiscard]] auto getCurrentAnimationFrame() const -> size_t { return animations_[current_animation_].current_frame; } // Obtiene el numero de frame de la animación actual [[nodiscard]] auto getCurrentAnimationFrame() const -> size_t { return animations_[current_animation_].current_frame; } // Obtiene el numero de frame de la animación actual
// --- Consultas --- // --- Consultas ---

View File

@@ -34,7 +34,7 @@ class Asset {
void add(const std::string& file_path, Type type, bool required = true, bool absolute = false); void add(const std::string& file_path, Type type, bool required = true, bool absolute = false);
void loadFromFile(const std::string& config_file_path, const std::string& prefix = "", const std::string& system_folder = ""); // Con soporte para variables void loadFromFile(const std::string& config_file_path, const std::string& prefix = "", const std::string& system_folder = ""); // Con soporte para variables
[[nodiscard]] auto getPath(const std::string& filename) const -> std::string; [[nodiscard]] auto getPath(const std::string& filename) const -> std::string;
[[nodiscard]] auto loadData(const std::string& filename) const -> std::vector<uint8_t>; // Carga datos del archivo [[nodiscard]] auto loadData(const std::string& filename) const -> std::vector<uint8_t>; // Carga datos del archivo
[[nodiscard]] auto check() const -> bool; [[nodiscard]] auto check() const -> bool;
[[nodiscard]] auto getListByType(Type type) const -> std::vector<std::string>; [[nodiscard]] auto getListByType(Type type) const -> std::vector<std::string>;
[[nodiscard]] auto exists(const std::string& filename) const -> bool; // Nueva función para verificar existencia [[nodiscard]] auto exists(const std::string& filename) const -> bool; // Nueva función para verificar existencia

View File

@@ -1,7 +1,6 @@
#include "balloon_formations.hpp" #include "balloon_formations.hpp"
#include <algorithm> // Para max, min, copy #include <algorithm> // Para max, min, copy
#include <utility> // Para std::cmp_less
#include <array> // Para array #include <array> // Para array
#include <cctype> // Para isdigit #include <cctype> // Para isdigit
#include <cstddef> // Para size_t #include <cstddef> // Para size_t
@@ -11,6 +10,7 @@
#include <map> // Para map, operator==, _Rb_tree_iterator #include <map> // Para map, operator==, _Rb_tree_iterator
#include <sstream> // Para basic_istringstream #include <sstream> // Para basic_istringstream
#include <string> // Para string, char_traits, allocator, operator==, stoi, getline, operator<=>, basic_string #include <string> // Para string, char_traits, allocator, operator==, stoi, getline, operator<=>, basic_string
#include <utility> // Para std::cmp_less
#include "asset.hpp" // Para Asset #include "asset.hpp" // Para Asset
#include "balloon.hpp" // Para Balloon #include "balloon.hpp" // Para Balloon

255
source/card_sprite.cpp Normal file
View File

@@ -0,0 +1,255 @@
#include "card_sprite.hpp"
#include <algorithm> // Para std::clamp
#include <functional> // Para function
#include <utility> // Para move
#include "texture.hpp" // Para Texture
#include "utils.hpp" // Para easeOutBounce, easeOutCubic
// Constructor
CardSprite::CardSprite(std::shared_ptr<Texture> texture)
: MovingSprite(std::move(texture)),
entry_easing_(easeOutBounce) {}
// Inicia la animación de entrada (solo si está en IDLE)
auto CardSprite::enable() -> bool {
if (state_ != CardState::IDLE) {
return false;
}
state_ = CardState::ENTERING;
entry_elapsed_ = 0.0F;
first_touch_ = false;
// Posición inicial (borde de pantalla)
setPos(entry_start_x_, entry_start_y_);
// Zoom inicial grande (como si estuviera cerca de la cámara)
horizontal_zoom_ = start_zoom_;
vertical_zoom_ = start_zoom_;
// Ángulo inicial
rotate_.angle = start_angle_;
rotate_.center = {pos_.w / 2.0F, pos_.h / 2.0F};
shadow_visible_ = true;
return true;
}
// Inicia la animación de salida (solo si está en LANDED)
void CardSprite::startExit() {
if (state_ != CardState::LANDED) {
return;
}
state_ = CardState::EXITING;
shadow_visible_ = true;
// Velocidad y aceleración de salida
vx_ = exit_vx_;
vy_ = exit_vy_;
ax_ = exit_ax_;
ay_ = exit_ay_;
// Rotación continua
rotate_.enabled = true;
rotate_.amount = exit_rotate_amount_;
rotate_.center = {pos_.w / 2.0F, pos_.h / 2.0F};
}
// Actualiza según el estado
void CardSprite::update(float delta_time) {
switch (state_) {
case CardState::ENTERING:
updateEntering(delta_time);
break;
case CardState::EXITING:
updateExiting(delta_time);
break;
default:
break;
}
}
// Animación de entrada: interpola posición, zoom y ángulo
void CardSprite::updateEntering(float delta_time) {
entry_elapsed_ += delta_time;
float progress = std::clamp(entry_elapsed_ / entry_duration_s_, 0.0F, 1.0F);
double eased = entry_easing_(static_cast<double>(progress));
// Zoom: de start_zoom_ a 1.0 con rebote
auto current_zoom = static_cast<float>(start_zoom_ + (1.0 - start_zoom_) * eased);
horizontal_zoom_ = current_zoom;
vertical_zoom_ = current_zoom;
// Ángulo: de start_angle_ a 0 con rebote
rotate_.angle = start_angle_ * (1.0 - eased);
// Posición: de entry_start a landing con easing suave (sin rebote)
// Usamos easeOutCubic para que el desplazamiento sea fluido
double pos_eased = easeOutCubic(static_cast<double>(progress));
auto current_x = static_cast<float>(entry_start_x_ + (landing_x_ - entry_start_x_) * pos_eased);
auto current_y = static_cast<float>(entry_start_y_ + (landing_y_ - entry_start_y_) * pos_eased);
setPos(current_x, current_y);
// Detecta el primer toque (cuando el easing alcanza ~1.0 por primera vez)
if (!first_touch_ && eased >= FIRST_TOUCH_THRESHOLD) {
first_touch_ = true;
}
// Transición a LANDED cuando termina la animación completa
if (progress >= 1.0F) {
horizontal_zoom_ = 1.0F;
vertical_zoom_ = 1.0F;
rotate_.angle = 0.0;
setPos(landing_x_, landing_y_);
state_ = CardState::LANDED;
first_touch_ = true;
}
}
// Animación de salida: movimiento + rotación continua + zoom opcional
void CardSprite::updateExiting(float delta_time) {
move(delta_time);
rotate(delta_time);
// Ganar altura gradualmente (zoom hacia el objetivo)
if (exit_zoom_speed_ > 0.0F && horizontal_zoom_ < exit_target_zoom_) {
float new_zoom = horizontal_zoom_ + exit_zoom_speed_ * delta_time;
if (new_zoom > exit_target_zoom_) {
new_zoom = exit_target_zoom_;
}
horizontal_zoom_ = new_zoom;
vertical_zoom_ = new_zoom;
}
if (isOffScreen()) {
state_ = CardState::FINISHED;
}
}
// Renderiza el sprite y su sombra
void CardSprite::render() {
if (state_ == CardState::IDLE || state_ == CardState::FINISHED) {
return;
}
// Sombra primero (debajo de la tarjeta)
if (shadow_visible_ && shadow_texture_) {
renderShadow();
}
// Tarjeta
MovingSprite::render();
}
// Renderiza la sombra con efecto de perspectiva 2D→3D (efecto helicóptero)
//
// Fuente de luz en la esquina superior izquierda (0,0).
// La sombra se mueve con la tarjeta pero desplazada en dirección opuesta a la luz
// (abajo-derecha a 45°). Cuanto más alta la tarjeta (zoom > 1.0):
// - Más separada de la tarjeta (offset grande)
// - Más pequeña (proyección lejana)
// Cuando la tarjeta está en la mesa (zoom=1.0):
// - Sombra pegada con offset base
// - Tamaño real
void CardSprite::renderShadow() {
// Altura sobre la mesa: 0.0 = en la mesa, 0.8 = alta (zoom 1.8)
float height = horizontal_zoom_ - 1.0F;
// Escala: más pequeña cuanto más alta
float shadow_zoom = 1.0F / horizontal_zoom_;
// Offset respecto a la tarjeta: base + extra proporcional a la altura
// La sombra se aleja en diagonal abajo-derecha (opuesta a la luz en 0,0)
float offset_x = shadow_offset_x_ + height * SHADOW_HEIGHT_MULTIPLIER;
float offset_y = shadow_offset_y_ + height * SHADOW_HEIGHT_MULTIPLIER;
shadow_texture_->render(
pos_.x + offset_x,
pos_.y + offset_y,
&sprite_clip_,
shadow_zoom,
shadow_zoom,
rotate_.angle,
&rotate_.center,
flip_);
}
// Comprueba si el sprite está fuera de pantalla
auto CardSprite::isOffScreen() const -> bool {
float effective_width = pos_.w * horizontal_zoom_;
float effective_height = pos_.h * vertical_zoom_;
return (pos_.x + effective_width < -OFF_SCREEN_MARGIN ||
pos_.x > screen_width_ + OFF_SCREEN_MARGIN ||
pos_.y + effective_height < -OFF_SCREEN_MARGIN ||
pos_.y > screen_height_ + OFF_SCREEN_MARGIN);
}
// --- Consultas de estado ---
auto CardSprite::hasLanded() const -> bool {
return state_ == CardState::LANDED || state_ == CardState::EXITING || state_ == CardState::FINISHED;
}
auto CardSprite::hasFirstTouch() const -> bool {
return first_touch_;
}
auto CardSprite::hasFinished() const -> bool {
return state_ == CardState::FINISHED;
}
auto CardSprite::isExiting() const -> bool {
return state_ == CardState::EXITING;
}
auto CardSprite::getState() const -> CardState {
return state_;
}
// --- Configuración ---
void CardSprite::setEntryParams(float start_zoom, double start_angle, float duration_s, std::function<double(double)> easing) {
start_zoom_ = start_zoom;
start_angle_ = start_angle;
entry_duration_s_ = duration_s;
entry_easing_ = std::move(easing);
}
void CardSprite::setEntryPosition(float start_x, float start_y) {
entry_start_x_ = start_x;
entry_start_y_ = start_y;
}
void CardSprite::setLandingPosition(float x, float y) {
landing_x_ = x;
landing_y_ = y;
}
void CardSprite::setExitParams(float vx, float vy, float ax, float ay, double rotate_amount) {
exit_vx_ = vx;
exit_vy_ = vy;
exit_ax_ = ax;
exit_ay_ = ay;
exit_rotate_amount_ = rotate_amount;
}
void CardSprite::setExitLift(float target_zoom, float zoom_speed) {
exit_target_zoom_ = target_zoom;
exit_zoom_speed_ = zoom_speed;
}
void CardSprite::setShadowTexture(std::shared_ptr<Texture> texture) {
shadow_texture_ = std::move(texture);
}
void CardSprite::setShadowOffset(float offset_x, float offset_y) {
shadow_offset_x_ = offset_x;
shadow_offset_y_ = offset_y;
}
void CardSprite::setScreenBounds(float width, float height) {
screen_width_ = width;
screen_height_ = height;
}

109
source/card_sprite.hpp Normal file
View File

@@ -0,0 +1,109 @@
#pragma once
#include <SDL3/SDL.h> // Para SDL_FPoint
#include <functional> // Para function
#include <memory> // Para shared_ptr
#include "moving_sprite.hpp" // Para MovingSprite
class Texture;
// --- Estados de la tarjeta ---
enum class CardState {
IDLE, // No activada todavía
ENTERING, // Animación de entrada (zoom + rotación + desplazamiento con rebote)
LANDED, // En reposo sobre la mesa
EXITING, // Saliendo de pantalla girando
FINISHED, // Fuera de pantalla
};
// --- Clase CardSprite: tarjeta animada con zoom, rotación y sombra integrada ---
//
// Simula una tarjeta lanzada sobre una mesa desde un borde de la pantalla.
// Durante la entrada, interpola posición, zoom y rotación con easing (rebote).
// Durante la salida, se desplaza fuera de pantalla girando, sin sombra.
class CardSprite : public MovingSprite {
public:
explicit CardSprite(std::shared_ptr<Texture> texture);
~CardSprite() override = default;
// --- Ciclo principal ---
void update(float delta_time) override;
void render() override;
// --- Control de estado ---
auto enable() -> bool; // Inicia la animación de entrada (true si se activó)
void startExit(); // Inicia la animación de salida
// --- Consultas de estado ---
[[nodiscard]] auto hasLanded() const -> bool; // ¿Ha aterrizado definitivamente?
[[nodiscard]] auto hasFirstTouch() const -> bool; // ¿Ha tocado la mesa por primera vez? (primer rebote)
[[nodiscard]] auto hasFinished() const -> bool; // ¿Ha terminado completamente?
[[nodiscard]] auto isExiting() const -> bool; // ¿Está saliendo de pantalla?
[[nodiscard]] auto getState() const -> CardState; // Estado actual
// --- Configuración de entrada ---
void setEntryParams(float start_zoom, double start_angle, float duration_s, std::function<double(double)> easing);
void setEntryPosition(float start_x, float start_y); // Posición inicial (borde de pantalla)
void setLandingPosition(float x, float y); // Posición final centrada
// --- Configuración de salida ---
void setExitParams(float vx, float vy, float ax, float ay, double rotate_amount);
void setExitLift(float target_zoom, float zoom_speed); // Ganar altura al salir (zoom > 1.0)
// --- Sombra ---
void setShadowTexture(std::shared_ptr<Texture> texture);
void setShadowOffset(float offset_x, float offset_y);
// --- Limites de pantalla (para detectar salida) ---
void setScreenBounds(float width, float height);
private:
// --- Estado ---
CardState state_ = CardState::IDLE;
bool first_touch_ = false; // Primer contacto con la mesa (eased >= umbral)
// --- Umbral para detectar el primer toque ---
static constexpr double FIRST_TOUCH_THRESHOLD = 0.98;
// --- Parámetros de entrada ---
float start_zoom_ = 1.8F;
double start_angle_ = 15.0;
float entry_duration_s_ = 1.5F;
float entry_elapsed_ = 0.0F;
std::function<double(double)> entry_easing_;
float entry_start_x_ = 0.0F; // Posición inicial X (borde)
float entry_start_y_ = 0.0F; // Posición inicial Y (borde)
float landing_x_ = 0.0F;
float landing_y_ = 0.0F;
// --- Parámetros de salida ---
float exit_vx_ = 0.0F;
float exit_vy_ = 0.0F;
float exit_ax_ = 0.0F;
float exit_ay_ = 0.0F;
double exit_rotate_amount_ = 0.0;
float exit_target_zoom_ = 1.0F; // Zoom objetivo al salir (>1.0 = se eleva)
float exit_zoom_speed_ = 0.0F; // Velocidad de cambio de zoom por segundo
// --- Sombra ---
std::shared_ptr<Texture> shadow_texture_;
float shadow_offset_x_ = 8.0F;
float shadow_offset_y_ = 8.0F;
bool shadow_visible_ = true;
// --- Límites de pantalla ---
float screen_width_ = 320.0F;
float screen_height_ = 240.0F;
// --- Constantes ---
static constexpr float OFF_SCREEN_MARGIN = 50.0F; // Margen fuera de pantalla para considerar FINISHED
static constexpr float SHADOW_HEIGHT_MULTIPLIER = 400.0F; // Pixels de separación de sombra por unidad de altura
// --- Métodos internos ---
void updateEntering(float delta_time);
void updateExiting(float delta_time);
void renderShadow();
[[nodiscard]] auto isOffScreen() const -> bool;
};

View File

@@ -186,8 +186,11 @@ namespace Defaults::Video {
constexpr bool FULLSCREEN = false; constexpr bool FULLSCREEN = false;
constexpr bool VSYNC = true; constexpr bool VSYNC = true;
constexpr bool INTEGER_SCALE = true; constexpr bool INTEGER_SCALE = true;
constexpr bool POSTFX = false; constexpr bool GPU_ACCELERATION = true;
constexpr int SUPERSAMPLING = 1; constexpr bool SHADER_ENABLED = false;
constexpr bool SUPERSAMPLING = false;
constexpr bool LINEAR_UPSCALE = false;
constexpr int DOWNSCALE_ALGO = 1;
} // namespace Defaults::Video } // namespace Defaults::Video
namespace Defaults::Music { namespace Defaults::Music {

View File

@@ -6,6 +6,7 @@
#include <cstdlib> // Para srand, exit, rand, EXIT_FAILURE #include <cstdlib> // Para srand, exit, rand, EXIT_FAILURE
#include <ctime> // Para time #include <ctime> // Para time
#include <filesystem> // Para path, absolute #include <filesystem> // Para path, absolute
#include <fstream> // Para ifstream, ofstream
#include <iostream> // Para basic_ostream, operator<<, cerr #include <iostream> // Para basic_ostream, operator<<, cerr
#include <memory> // Para make_unique, unique_ptr #include <memory> // Para make_unique, unique_ptr
#include <span> // Para span #include <span> // Para span
@@ -14,6 +15,7 @@
#include "asset.hpp" // Para Asset #include "asset.hpp" // Para Asset
#include "audio.hpp" // Para Audio #include "audio.hpp" // Para Audio
#include "external/fkyaml_node.hpp" // Para fkyaml::node
#include "input.hpp" // Para Input #include "input.hpp" // Para Input
#include "lang.hpp" // Para setLanguage #include "lang.hpp" // Para setLanguage
#include "manage_hiscore_table.hpp" // Para ManageHiScoreTable #include "manage_hiscore_table.hpp" // Para ManageHiScoreTable
@@ -40,16 +42,6 @@
// Constructor // Constructor
Director::Director(int argc, std::span<char*> argv) { Director::Director(int argc, std::span<char*> argv) {
#ifdef RECORDING
Section::name = Section::Name::GAME;
Section::options = Section::Options::GAME_PLAY_1P;
#elif _DEBUG
Section::name = Section::Name::GAME;
Section::options = Section::Options::GAME_PLAY_1P;
#else // NORMAL GAME
Section::name = Section::Name::LOGO;
Section::options = Section::Options::NONE;
#endif
Section::attract_mode = Section::AttractMode::TITLE_TO_DEMO; Section::attract_mode = Section::AttractMode::TITLE_TO_DEMO;
// Establece el nivel de prioridad de la categoría de registro // Establece el nivel de prioridad de la categoría de registro
@@ -68,6 +60,17 @@ Director::Director(int argc, std::span<char*> argv) {
createSystemFolder("jailgames"); createSystemFolder("jailgames");
createSystemFolder("jailgames/coffee_crisis_arcade_edition"); createSystemFolder("jailgames/coffee_crisis_arcade_edition");
// Establecer sección inicial según modo de compilación
#ifdef RECORDING
Section::name = Section::Name::GAME;
Section::options = Section::Options::GAME_PLAY_1P;
#elif _DEBUG
loadDebugConfig();
#else
Section::name = Section::Name::LOGO;
Section::options = Section::Options::NONE;
#endif
init(); init();
} }
@@ -103,13 +106,15 @@ void Director::init() {
Input::init(Asset::get()->getPath("gamecontrollerdb.txt"), Asset::get()->getPath("controllers.json")); // Carga configuración de controles Input::init(Asset::get()->getPath("gamecontrollerdb.txt"), Asset::get()->getPath("controllers.json")); // Carga configuración de controles
Logger::section("INIT CONFIG"); Logger::section("INIT CONFIG");
Options::setConfigFile(Asset::get()->getPath("config.yaml")); // Establece el fichero de configuración Options::setConfigFile(Asset::get()->getPath("config.yaml")); // Establece el fichero de configuración
Options::setControllersFile(Asset::get()->getPath("controllers.json")); // Establece el fichero de configuración de mandos Options::setControllersFile(Asset::get()->getPath("controllers.json")); // Establece el fichero de configuración de mandos
Options::setPostFXFile(Asset::get()->getPath("postfx.yaml")); // Establece el fichero de presets PostFX Options::setPostFXFile(Asset::get()->getPath("postfx.yaml")); // Establece el fichero de presets PostFX
Options::loadFromFile(); // Carga el archivo de configuración Options::setCrtPiFile(Asset::get()->getPath("crtpi.yaml")); // Establece el fichero de presets CrtPi
Options::loadPostFXFromFile(); // Carga los presets PostFX Options::loadFromFile(); // Carga el archivo de configuración
loadParams(); // Carga los parámetros del programa Options::loadPostFXFromFile(); // Carga los presets PostFX
loadScoreFile(); // Carga el archivo de puntuaciones Options::loadCrtPiFromFile(); // Carga los presets CrtPi
loadParams(); // Carga los parámetros del programa
loadScoreFile(); // Carga el archivo de puntuaciones
// Inicialización de subsistemas principales // Inicialización de subsistemas principales
Lang::setLanguage(Options::settings.language); // Carga el archivo de idioma Lang::setLanguage(Options::settings.language); // Carga el archivo de idioma
@@ -122,9 +127,9 @@ void Director::init() {
Logger::section("INIT RESOURCES"); Logger::section("INIT RESOURCES");
#ifdef _DEBUG #ifdef _DEBUG
Resource::init(Resource::LoadingMode::PRELOAD); // Inicializa el sistema de gestión de recursos Resource::init(debug_config.resource_loading == "lazy" ? Resource::LoadingMode::LAZY_LOAD : Resource::LoadingMode::PRELOAD);
#else #else
Resource::init(Resource::LoadingMode::PRELOAD); // Inicializa el sistema de gestión de recursos Resource::init(Resource::LoadingMode::PRELOAD);
#endif #endif
ServiceMenu::init(); // Inicializa el menú de servicio ServiceMenu::init(); // Inicializa el menú de servicio
Notifier::init(std::string(), Resource::get()->getText("8bithud")); // Inicialización del sistema de notificaciones Notifier::init(std::string(), Resource::get()->getText("8bithud")); // Inicialización del sistema de notificaciones
@@ -222,6 +227,103 @@ void Director::checkProgramArguments(int argc, std::span<char*> argv) {
} }
} }
// Carga debug.yaml desde la carpeta del sistema (solo en _DEBUG)
void Director::loadDebugConfig() {
const std::string DEBUG_FILE = system_folder_ + "/debug.yaml";
std::ifstream file(DEBUG_FILE);
if (!file.good()) {
// Crear fichero por defecto
std::ofstream out(DEBUG_FILE);
if (out.is_open()) {
out << "# Coffee Crisis Arcade Edition - Debug Configuration\n";
out << "# This file is only read in DEBUG builds.\n";
out << "#\n";
out << "# initial_section: logo, intro, title, game, credits, instructions, hiscore\n";
out << "# initial_options: none, 1p, 2p, both\n";
out << "# initial_stage: 0-based stage index (only when section is game)\n";
out << "# show_render_info: show FPS/driver/preset overlay\n";
out << "# resource_loading: preload, lazy\n";
out << "\n";
out << "initial_section: game\n";
out << "initial_options: 1p\n";
out << "initial_stage: 0\n";
out << "show_render_info: true\n";
out << "resource_loading: preload\n";
out.close();
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Debug config created: %s", DEBUG_FILE.c_str());
}
// Usar defaults de DebugConfig
} else {
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
file.close();
try {
auto yaml = fkyaml::node::deserialize(content);
if (yaml.contains("initial_section")) {
try {
debug_config.initial_section = yaml["initial_section"].get_value<std::string>();
} catch (...) {}
}
if (yaml.contains("initial_options")) {
try {
debug_config.initial_options = yaml["initial_options"].get_value<std::string>();
} catch (...) {}
}
if (yaml.contains("initial_stage")) {
try {
debug_config.initial_stage = yaml["initial_stage"].get_value<int>();
} catch (...) {}
}
if (yaml.contains("show_render_info")) {
try {
debug_config.show_render_info = yaml["show_render_info"].get_value<bool>();
} catch (...) {}
}
if (yaml.contains("resource_loading")) {
try {
debug_config.resource_loading = yaml["resource_loading"].get_value<std::string>();
} catch (...) {}
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Debug config loaded: section=%s options=%s stage=%d", debug_config.initial_section.c_str(), debug_config.initial_options.c_str(), debug_config.initial_stage);
} catch (...) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Error parsing debug.yaml, using defaults");
}
}
// Mapear strings a enums
const auto& sec = debug_config.initial_section;
if (sec == "logo") {
Section::name = Section::Name::LOGO;
} else if (sec == "intro") {
Section::name = Section::Name::INTRO;
} else if (sec == "title") {
Section::name = Section::Name::TITLE;
} else if (sec == "game") {
Section::name = Section::Name::GAME;
} else if (sec == "credits") {
Section::name = Section::Name::CREDITS;
} else if (sec == "instructions") {
Section::name = Section::Name::INSTRUCTIONS;
} else if (sec == "hiscore") {
Section::name = Section::Name::HI_SCORE_TABLE;
} else {
Section::name = Section::Name::GAME;
}
const auto& opt = debug_config.initial_options;
if (opt == "none") {
Section::options = Section::Options::NONE;
} else if (opt == "1p") {
Section::options = Section::Options::GAME_PLAY_1P;
} else if (opt == "2p") {
Section::options = Section::Options::GAME_PLAY_2P;
} else if (opt == "both") {
Section::options = Section::Options::GAME_PLAY_BOTH;
} else {
Section::options = Section::Options::GAME_PLAY_1P;
}
}
// Crea la carpeta del sistema donde guardar datos // Crea la carpeta del sistema donde guardar datos
void Director::createSystemFolder(const std::string& folder) { void Director::createSystemFolder(const std::string& folder) {
auto result = SystemUtils::createApplicationFolder(folder, system_folder_); auto result = SystemUtils::createApplicationFolder(folder, system_folder_);
@@ -270,7 +372,7 @@ void Director::runGame() {
} }
#ifdef _DEBUG #ifdef _DEBUG
constexpr int CURRENT_STAGE = 0; const int CURRENT_STAGE = debug_config.initial_stage;
#else #else
constexpr int CURRENT_STAGE = 0; constexpr int CURRENT_STAGE = 0;
#endif #endif

View File

@@ -17,6 +17,21 @@ class Director {
// --- Bucle principal --- // --- Bucle principal ---
static auto run() -> int; static auto run() -> int;
// --- Debug config (accesible desde otras clases) ---
struct DebugConfig {
std::string initial_section;
std::string initial_options;
int initial_stage = 0;
bool show_render_info = true;
std::string resource_loading;
DebugConfig()
: initial_section("game"),
initial_options("1p"),
resource_loading("preload") {}
};
static inline DebugConfig debug_config;
private: private:
// --- Variables internas --- // --- Variables internas ---
std::string executable_path_; // Ruta del ejecutable std::string executable_path_; // Ruta del ejecutable
@@ -30,6 +45,7 @@ class Director {
static void loadParams(); // Carga los parámetros del programa static void loadParams(); // Carga los parámetros del programa
static void loadScoreFile(); // Carga el fichero de puntuaciones static void loadScoreFile(); // Carga el fichero de puntuaciones
void createSystemFolder(const std::string& folder); // Crea la carpeta del sistema void createSystemFolder(const std::string& folder); // Crea la carpeta del sistema
void loadDebugConfig(); // Carga debug.yaml (solo en _DEBUG)
// --- Gestión de entrada y archivos --- // --- Gestión de entrada y archivos ---
void loadAssets(); // Crea el índice de archivos disponibles void loadAssets(); // Crea el índice de archivos disponibles

View File

@@ -33,8 +33,8 @@ class EnterName {
private: private:
// --- Variables de estado --- // --- Variables de estado ---
std::string character_list_{"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{"}; // Lista de caracteres permitidos std::string character_list_{"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{"}; // Lista de caracteres permitidos
std::string name_; // Nombre en proceso std::string name_; // Nombre en proceso
size_t selected_index_ = 0; // Índice del carácter seleccionado en "character_list_" size_t selected_index_ = 0; // Índice del carácter seleccionado en "character_list_"
[[nodiscard]] auto sanitizeName(const std::string& name) const -> std::string; // Valida y limpia el nombre [[nodiscard]] auto sanitizeName(const std::string& name) const -> std::string; // Valida y limpia el nombre
static auto getRandomName() -> std::string; // Devuelve un nombre al azar static auto getRandomName() -> std::string; // Devuelve un nombre al azar

View File

@@ -72,7 +72,7 @@ class Fade {
int num_squares_width_; // Cuadrados en horizontal int num_squares_width_; // Cuadrados en horizontal
int num_squares_height_; // Cuadrados en vertical int num_squares_height_; // Cuadrados en vertical
int square_transition_duration_; // Duración de transición de cada cuadrado en ms int square_transition_duration_; // Duración de transición de cada cuadrado en ms
int fading_duration_{0}; // Duración del estado FADING en milisegundos int fading_duration_{0}; // Duración del estado FADING en milisegundos
Uint32 fading_start_time_ = 0; // Tiempo de inicio del estado FADING Uint32 fading_start_time_ = 0; // Tiempo de inicio del estado FADING
int post_duration_ = 0; // Duración posterior en milisegundos int post_duration_ = 0; // Duración posterior en milisegundos
Uint32 post_start_time_ = 0; // Tiempo de inicio del estado POST Uint32 post_start_time_ = 0; // Tiempo de inicio del estado POST

View File

@@ -54,7 +54,7 @@ namespace GlobalEvents {
break; break;
case SDL_EVENT_WINDOW_RESIZED: case SDL_EVENT_WINDOW_RESIZED:
Screen::initPostFX(); Screen::initShaders();
break; break;
default: default:

View File

@@ -62,25 +62,36 @@ namespace GlobalInputs {
Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 14") + " " + boolToOnOff(Options::video.vsync)}); Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 14") + " " + boolToOnOff(Options::video.vsync)});
} }
// Activa o desactiva los efectos PostFX // Activa o desactiva los shaders
void togglePostFX() { void toggleShaders() {
Screen::togglePostFX(); Screen::toggleShaders();
Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 13") + " " + boolToOnOff(Options::video.postfx)}); Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 13") + " " + boolToOnOff(Options::video.shader.enabled)});
} }
// Avanza al siguiente preset PostFX // Cambia entre PostFX y CrtPi
void nextPostFXPreset() { void nextShader() {
Screen::nextPostFXPreset(); Screen::nextShader();
const std::string name = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::current_postfx_preset)).name; const std::string SHADER_NAME = (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) ? "CrtPi" : "PostFX";
Notifier::get()->show({"PostFX: " + name}); Notifier::get()->show({"Shader: " + SHADER_NAME});
} }
// Activa o desactiva el supersampling 3x // Avanza al siguiente preset PostFX o CrtPi según shader activo
void nextPreset() {
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
Screen::nextCrtPiPreset();
const std::string name = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset)).name;
Notifier::get()->show({"CrtPi: " + name});
} else {
Screen::nextPostFXPreset();
const std::string name = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset)).name;
Notifier::get()->show({"PostFX: " + name});
}
}
// Activa o desactiva el supersampling
void toggleSupersampling() { void toggleSupersampling() {
Screen::toggleSupersampling(); Screen::toggleSupersampling();
const int SS = Options::video.supersampling; Notifier::get()->show({"SS: " + std::string(Options::video.supersampling.enabled ? "ON" : "OFF")});
const std::string SS_LABEL = (SS <= 1) ? "OFF" : (std::to_string(SS) + "\xC3\x97");
Notifier::get()->show({"SS: " + SS_LABEL});
} }
// Cambia al siguiente idioma // Cambia al siguiente idioma
@@ -202,11 +213,15 @@ namespace GlobalInputs {
} }
if (Input::get()->checkAction(Input::Action::TOGGLE_VIDEO_POSTFX, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) { if (Input::get()->checkAction(Input::Action::TOGGLE_VIDEO_POSTFX, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
togglePostFX(); toggleShaders();
return true;
}
if (Input::get()->checkAction(Input::Action::NEXT_SHADER, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
nextShader();
return true; return true;
} }
if (Input::get()->checkAction(Input::Action::NEXT_POSTFX_PRESET, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) { if (Input::get()->checkAction(Input::Action::NEXT_POSTFX_PRESET, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
nextPostFXPreset(); nextPreset();
return true; return true;
} }
if (Input::get()->checkAction(Input::Action::TOGGLE_SUPERSAMPLING, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) { if (Input::get()->checkAction(Input::Action::TOGGLE_SUPERSAMPLING, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {

View File

@@ -325,17 +325,6 @@ void Input::initSDLGamePad() {
} }
} }
void Input::resetJustPressed() {
for (auto& key : keyboard_.bindings) {
key.second.just_pressed = false;
}
for (auto& gamepad : gamepads_) {
for (auto& binding : gamepad->bindings) {
binding.second.just_pressed = false;
}
}
}
void Input::resetInputStates() { void Input::resetInputStates() {
// Resetear todos los KeyBindings.active a false // Resetear todos los KeyBindings.active a false
for (auto& key : keyboard_.bindings) { for (auto& key : keyboard_.bindings) {
@@ -360,7 +349,7 @@ void Input::update() {
bool key_is_down_now = key_states[binding.second.scancode]; bool key_is_down_now = key_states[binding.second.scancode];
// El estado .is_held del fotograma anterior nos sirve para saber si es un pulso nuevo // El estado .is_held del fotograma anterior nos sirve para saber si es un pulso nuevo
binding.second.just_pressed = binding.second.just_pressed || (key_is_down_now && !binding.second.is_held); binding.second.just_pressed = key_is_down_now && !binding.second.is_held;
binding.second.is_held = key_is_down_now; binding.second.is_held = key_is_down_now;
} }
@@ -378,16 +367,6 @@ void Input::update() {
auto Input::handleEvent(const SDL_Event& event) -> std::string { auto Input::handleEvent(const SDL_Event& event) -> std::string {
switch (event.type) { switch (event.type) {
case SDL_EVENT_KEY_DOWN:
if (!event.key.repeat) {
for (auto& [action, binding] : keyboard_.bindings) {
if (binding.scancode == event.key.scancode) {
binding.just_pressed = true;
break;
}
}
}
break;
case SDL_EVENT_GAMEPAD_ADDED: case SDL_EVENT_GAMEPAD_ADDED:
return addGamepad(event.gdevice.which); return addGamepad(event.gdevice.which);
case SDL_EVENT_GAMEPAD_REMOVED: case SDL_EVENT_GAMEPAD_REMOVED:

View File

@@ -82,6 +82,7 @@ class Input {
{Action::WINDOW_INC_SIZE, KeyState(SDL_SCANCODE_F2)}, {Action::WINDOW_INC_SIZE, KeyState(SDL_SCANCODE_F2)},
{Action::WINDOW_FULLSCREEN, KeyState(SDL_SCANCODE_F3)}, {Action::WINDOW_FULLSCREEN, KeyState(SDL_SCANCODE_F3)},
{Action::TOGGLE_VIDEO_POSTFX, KeyState(SDL_SCANCODE_F4)}, {Action::TOGGLE_VIDEO_POSTFX, KeyState(SDL_SCANCODE_F4)},
{Action::NEXT_SHADER, KeyState(SDL_SCANCODE_8)},
{Action::NEXT_POSTFX_PRESET, KeyState(SDL_SCANCODE_9)}, {Action::NEXT_POSTFX_PRESET, KeyState(SDL_SCANCODE_9)},
{Action::TOGGLE_SUPERSAMPLING, KeyState(SDL_SCANCODE_0)}, {Action::TOGGLE_SUPERSAMPLING, KeyState(SDL_SCANCODE_0)},
{Action::TOGGLE_VIDEO_INTEGER_SCALE, KeyState(SDL_SCANCODE_F5)}, {Action::TOGGLE_VIDEO_INTEGER_SCALE, KeyState(SDL_SCANCODE_F5)},
@@ -176,7 +177,6 @@ class Input {
// --- Métodos de reseteo de estado de entrada --- // --- Métodos de reseteo de estado de entrada ---
void resetInputStates(); void resetInputStates();
void resetJustPressed();
// --- Eventos --- // --- Eventos ---
auto handleEvent(const SDL_Event& event) -> std::string; auto handleEvent(const SDL_Event& event) -> std::string;

View File

@@ -22,6 +22,7 @@ const std::unordered_map<InputAction, std::string> ACTION_TO_STRING = {
{InputAction::WINDOW_INC_SIZE, "WINDOW_INC_SIZE"}, {InputAction::WINDOW_INC_SIZE, "WINDOW_INC_SIZE"},
{InputAction::WINDOW_DEC_SIZE, "WINDOW_DEC_SIZE"}, {InputAction::WINDOW_DEC_SIZE, "WINDOW_DEC_SIZE"},
{InputAction::TOGGLE_VIDEO_POSTFX, "TOGGLE_VIDEO_POSTFX"}, {InputAction::TOGGLE_VIDEO_POSTFX, "TOGGLE_VIDEO_POSTFX"},
{InputAction::NEXT_SHADER, "NEXT_SHADER"},
{InputAction::NEXT_POSTFX_PRESET, "NEXT_POSTFX_PRESET"}, {InputAction::NEXT_POSTFX_PRESET, "NEXT_POSTFX_PRESET"},
{InputAction::TOGGLE_SUPERSAMPLING, "TOGGLE_SUPERSAMPLING"}, {InputAction::TOGGLE_SUPERSAMPLING, "TOGGLE_SUPERSAMPLING"},
{InputAction::TOGGLE_VIDEO_INTEGER_SCALE, "TOGGLE_VIDEO_INTEGER_SCALE"}, {InputAction::TOGGLE_VIDEO_INTEGER_SCALE, "TOGGLE_VIDEO_INTEGER_SCALE"},
@@ -54,6 +55,7 @@ const std::unordered_map<std::string, InputAction> STRING_TO_ACTION = {
{"WINDOW_INC_SIZE", InputAction::WINDOW_INC_SIZE}, {"WINDOW_INC_SIZE", InputAction::WINDOW_INC_SIZE},
{"WINDOW_DEC_SIZE", InputAction::WINDOW_DEC_SIZE}, {"WINDOW_DEC_SIZE", InputAction::WINDOW_DEC_SIZE},
{"TOGGLE_VIDEO_POSTFX", InputAction::TOGGLE_VIDEO_POSTFX}, {"TOGGLE_VIDEO_POSTFX", InputAction::TOGGLE_VIDEO_POSTFX},
{"NEXT_SHADER", InputAction::NEXT_SHADER},
{"NEXT_POSTFX_PRESET", InputAction::NEXT_POSTFX_PRESET}, {"NEXT_POSTFX_PRESET", InputAction::NEXT_POSTFX_PRESET},
{"TOGGLE_SUPERSAMPLING", InputAction::TOGGLE_SUPERSAMPLING}, {"TOGGLE_SUPERSAMPLING", InputAction::TOGGLE_SUPERSAMPLING},
{"TOGGLE_VIDEO_INTEGER_SCALE", InputAction::TOGGLE_VIDEO_INTEGER_SCALE}, {"TOGGLE_VIDEO_INTEGER_SCALE", InputAction::TOGGLE_VIDEO_INTEGER_SCALE},

View File

@@ -32,6 +32,7 @@ enum class InputAction : int { // Acciones de entrada posibles en el juego
WINDOW_INC_SIZE, WINDOW_INC_SIZE,
WINDOW_DEC_SIZE, WINDOW_DEC_SIZE,
TOGGLE_VIDEO_POSTFX, TOGGLE_VIDEO_POSTFX,
NEXT_SHADER,
NEXT_POSTFX_PRESET, NEXT_POSTFX_PRESET,
TOGGLE_SUPERSAMPLING, TOGGLE_SUPERSAMPLING,
TOGGLE_VIDEO_INTEGER_SCALE, TOGGLE_VIDEO_INTEGER_SCALE,

View File

@@ -8,7 +8,7 @@
#include <string> // Para string #include <string> // Para string
#include <vector> // Para vector #include <vector> // Para vector
#include "difficulty.hpp" // Para Code, init #include "difficulty.hpp" // Para Code, init
#include "external/fkyaml_node.hpp" // Para fkyaml::node #include "external/fkyaml_node.hpp" // Para fkyaml::node
#include "input.hpp" // Para Input #include "input.hpp" // Para Input
#include "lang.hpp" // Para getText, Code #include "lang.hpp" // Para getText, Code
@@ -24,16 +24,17 @@ namespace Options {
GamepadManager gamepad_manager; // Opciones de mando para cada jugador GamepadManager gamepad_manager; // Opciones de mando para cada jugador
Keyboard keyboard; // Opciones para el teclado Keyboard keyboard; // Opciones para el teclado
PendingChanges pending_changes; // Opciones que se aplican al cerrar PendingChanges pending_changes; // Opciones que se aplican al cerrar
std::vector<PostFXPreset> postfx_presets = { // Lista de presets de PostFX std::vector<PostFXPreset> postfx_presets = {
{"CRT", 0.6F, 0.7F, 0.15F, 0.5F, 0.5F, 0.0F, 0.0F, 0.0F}, {"CRT", 0.15F, 0.7F, 0.2F, 0.5F, 0.1F, 0.0F, 0.0F, 0.0F},
{"NTSC", 0.4F, 0.5F, 0.2F, 0.3F, 0.3F, 0.0F, 0.6F, 0.0F}, {"NTSC", 0.4F, 0.5F, 0.2F, 0.3F, 0.3F, 0.0F, 0.6F, 0.0F},
{"CURVED", 0.5F, 0.6F, 0.1F, 0.4F, 0.4F, 0.8F, 0.0F, 0.0F}, {"Curved", 0.5F, 0.6F, 0.1F, 0.4F, 0.4F, 0.8F, 0.0F, 0.0F},
{"SCANLINES", 0.0F, 0.8F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F}, {"Scanlines", 0.0F, 0.8F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F},
{"SUBTLE", 0.3F, 0.4F, 0.05F, 0.0F, 0.2F, 0.0F, 0.0F, 0.0F}, {"Subtle", 0.3F, 0.4F, 0.05F, 0.0F, 0.2F, 0.0F, 0.0F, 0.0F},
{"CRT LIVE", 0.5F, 0.6F, 0.3F, 0.3F, 0.4F, 0.3F, 0.4F, 0.8F}, {"CRT Live", 0.15F, 0.6F, 0.3F, 0.3F, 0.1F, 0.0F, 0.4F, 0.8F},
}; };
int current_postfx_preset = 0; // Índice del preset PostFX activo std::string postfx_file_path;
std::string postfx_file_path; // Ruta al fichero de presets PostFX std::vector<CrtPiPreset> crtpi_presets;
std::string crtpi_file_path;
// Establece el fichero de configuración // Establece el fichero de configuración
void setConfigFile(const std::string& file_path) { settings.config_file = file_path; } void setConfigFile(const std::string& file_path) { settings.config_file = file_path; }
@@ -44,6 +45,9 @@ namespace Options {
// Establece la ruta del fichero de PostFX // Establece la ruta del fichero de PostFX
void setPostFXFile(const std::string& path) { postfx_file_path = path; } void setPostFXFile(const std::string& path) { postfx_file_path = path; }
// Establece la ruta del fichero de CrtPi
void setCrtPiFile(const std::string& path) { crtpi_file_path = path; }
// Helper: extrae un campo float de un nodo YAML si existe, ignorando errores de conversión // Helper: extrae un campo float de un nodo YAML si existe, ignorando errores de conversión
static void parseFloatField(const fkyaml::node& node, const std::string& key, float& target) { static void parseFloatField(const fkyaml::node& node, const std::string& key, float& target) {
if (node.contains(key)) { if (node.contains(key)) {
@@ -89,11 +93,21 @@ namespace Options {
} }
if (!postfx_presets.empty()) { if (!postfx_presets.empty()) {
current_postfx_preset = std::clamp( // Resolver nombre → índice
current_postfx_preset, 0, if (!video.shader.current_postfx_preset_name.empty()) {
for (int i = 0; i < static_cast<int>(postfx_presets.size()); ++i) {
if (postfx_presets[static_cast<size_t>(i)].name == video.shader.current_postfx_preset_name) {
video.shader.current_postfx_preset = i;
break;
}
}
}
video.shader.current_postfx_preset = std::clamp(
video.shader.current_postfx_preset,
0,
static_cast<int>(postfx_presets.size()) - 1); static_cast<int>(postfx_presets.size()) - 1);
} else { } else {
current_postfx_preset = 0; video.shader.current_postfx_preset = 0;
} }
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "PostFX file loaded: %zu preset(s)", postfx_presets.size()); SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "PostFX file loaded: %zu preset(s)", postfx_presets.size());
@@ -130,11 +144,11 @@ namespace Options {
file << "\n"; file << "\n";
file << "presets:\n"; file << "presets:\n";
file << " - name: \"CRT\"\n"; file << " - name: \"CRT\"\n";
file << " vignette: 0.6\n"; file << " vignette: 0.15\n";
file << " scanlines: 0.7\n"; file << " scanlines: 0.7\n";
file << " chroma: 0.15\n"; file << " chroma: 0.2\n";
file << " mask: 0.5\n"; file << " mask: 0.5\n";
file << " gamma: 0.5\n"; file << " gamma: 0.1\n";
file << " curvature: 0.0\n"; file << " curvature: 0.0\n";
file << " bleeding: 0.0\n"; file << " bleeding: 0.0\n";
file << " flicker: 0.0\n"; file << " flicker: 0.0\n";
@@ -147,7 +161,7 @@ namespace Options {
file << " curvature: 0.0\n"; file << " curvature: 0.0\n";
file << " bleeding: 0.6\n"; file << " bleeding: 0.6\n";
file << " flicker: 0.0\n"; file << " flicker: 0.0\n";
file << " - name: \"CURVED\"\n"; file << " - name: \"Curved\"\n";
file << " vignette: 0.5\n"; file << " vignette: 0.5\n";
file << " scanlines: 0.6\n"; file << " scanlines: 0.6\n";
file << " chroma: 0.1\n"; file << " chroma: 0.1\n";
@@ -156,7 +170,7 @@ namespace Options {
file << " curvature: 0.8\n"; file << " curvature: 0.8\n";
file << " bleeding: 0.0\n"; file << " bleeding: 0.0\n";
file << " flicker: 0.0\n"; file << " flicker: 0.0\n";
file << " - name: \"SCANLINES\"\n"; file << " - name: \"Scanlines\"\n";
file << " vignette: 0.0\n"; file << " vignette: 0.0\n";
file << " scanlines: 0.8\n"; file << " scanlines: 0.8\n";
file << " chroma: 0.0\n"; file << " chroma: 0.0\n";
@@ -165,7 +179,7 @@ namespace Options {
file << " curvature: 0.0\n"; file << " curvature: 0.0\n";
file << " bleeding: 0.0\n"; file << " bleeding: 0.0\n";
file << " flicker: 0.0\n"; file << " flicker: 0.0\n";
file << " - name: \"SUBTLE\"\n"; file << " - name: \"Subtle\"\n";
file << " vignette: 0.3\n"; file << " vignette: 0.3\n";
file << " scanlines: 0.4\n"; file << " scanlines: 0.4\n";
file << " chroma: 0.05\n"; file << " chroma: 0.05\n";
@@ -174,13 +188,13 @@ namespace Options {
file << " curvature: 0.0\n"; file << " curvature: 0.0\n";
file << " bleeding: 0.0\n"; file << " bleeding: 0.0\n";
file << " flicker: 0.0\n"; file << " flicker: 0.0\n";
file << " - name: \"CRT LIVE\"\n"; file << " - name: \"CRT Live\"\n";
file << " vignette: 0.5\n"; file << " vignette: 0.15\n";
file << " scanlines: 0.6\n"; file << " scanlines: 0.6\n";
file << " chroma: 0.3\n"; file << " chroma: 0.3\n";
file << " mask: 0.3\n"; file << " mask: 0.3\n";
file << " gamma: 0.4\n"; file << " gamma: 0.1\n";
file << " curvature: 0.3\n"; file << " curvature: 0.0\n";
file << " bleeding: 0.4\n"; file << " bleeding: 0.4\n";
file << " flicker: 0.8\n"; file << " flicker: 0.8\n";
@@ -190,17 +204,141 @@ namespace Options {
// Cargar los presets recién escritos // Cargar los presets recién escritos
postfx_presets.clear(); postfx_presets.clear();
postfx_presets.push_back({"CRT", 0.6F, 0.7F, 0.15F, 0.5F, 0.5F, 0.0F, 0.0F, 0.0F}); postfx_presets.push_back({"CRT", 0.15F, 0.7F, 0.2F, 0.5F, 0.1F, 0.0F, 0.0F, 0.0F});
postfx_presets.push_back({"NTSC", 0.4F, 0.5F, 0.2F, 0.3F, 0.3F, 0.0F, 0.6F, 0.0F}); postfx_presets.push_back({"NTSC", 0.4F, 0.5F, 0.2F, 0.3F, 0.3F, 0.0F, 0.6F, 0.0F});
postfx_presets.push_back({"CURVED", 0.5F, 0.6F, 0.1F, 0.4F, 0.4F, 0.8F, 0.0F, 0.0F}); postfx_presets.push_back({"Curved", 0.5F, 0.6F, 0.1F, 0.4F, 0.4F, 0.8F, 0.0F, 0.0F});
postfx_presets.push_back({"SCANLINES", 0.0F, 0.8F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F}); postfx_presets.push_back({"Scanlines", 0.0F, 0.8F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F});
postfx_presets.push_back({"SUBTLE", 0.3F, 0.4F, 0.05F, 0.0F, 0.2F, 0.0F, 0.0F, 0.0F}); postfx_presets.push_back({"Subtle", 0.3F, 0.4F, 0.05F, 0.0F, 0.2F, 0.0F, 0.0F, 0.0F});
postfx_presets.push_back({"CRT LIVE", 0.5F, 0.6F, 0.3F, 0.3F, 0.4F, 0.3F, 0.4F, 0.8F}); postfx_presets.push_back({"CRT Live", 0.15F, 0.6F, 0.3F, 0.3F, 0.1F, 0.0F, 0.4F, 0.8F});
current_postfx_preset = 0; video.shader.current_postfx_preset = 0;
return true; return true;
} }
// Helper: extrae un campo bool de un nodo YAML si existe, ignorando errores
static void parseBoolField(const fkyaml::node& node, const std::string& key, bool& target) {
if (node.contains(key)) {
try {
target = node[key].get_value<bool>();
} catch (...) {}
}
}
// Helper: extrae un campo int de un nodo YAML si existe, ignorando errores
static void parseIntField(const fkyaml::node& node, const std::string& key, int& target) {
if (node.contains(key)) {
try {
target = node[key].get_value<int>();
} catch (...) {}
}
}
// Rellena los presets CrtPi por defecto
static void populateDefaultCrtPiPresets() {
crtpi_presets.clear();
crtpi_presets.push_back({"Default", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, false, false});
crtpi_presets.push_back({"Curved", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, true, false});
crtpi_presets.push_back({"Sharp", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, false, true, false, true});
crtpi_presets.push_back({"Minimal", 8.0F, 0.05F, 2.0F, 2.4F, 2.2F, 1.00F, 0.0F, 0.0F, 0, true, false, false, false, false});
}
// Escribe los presets CrtPi por defecto al fichero
static auto saveCrtPiDefaults() -> bool {
if (crtpi_file_path.empty()) { return false; }
std::ofstream file(crtpi_file_path);
if (!file.is_open()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: %s can't be opened for writing", crtpi_file_path.c_str());
return false;
}
file << "# Coffee Crisis Arcade Edition - CrtPi Shader Presets\n";
file << "# scanline_weight: gaussian adjustment (higher = narrower scanlines, default 6.0)\n";
file << "# scanline_gap_brightness: min brightness between scanlines (0.0-1.0, default 0.12)\n";
file << "# bloom_factor: brightness for bright areas (default 3.5)\n";
file << "# input_gamma: input gamma - linearization (default 2.4)\n";
file << "# output_gamma: output gamma - encoding (default 2.2)\n";
file << "# mask_brightness: sub-pixel brightness (default 0.80)\n";
file << "# curvature_x/y: barrel CRT distortion (0.0 = flat)\n";
file << "# mask_type: 0=none, 1=green/magenta, 2=RGB phosphor\n";
file << "# enable_scanlines/multisample/gamma/curvature/sharper: true/false\n";
file << "\npresets:\n";
file << " - name: \"Default\"\n scanline_weight: 6.0\n scanline_gap_brightness: 0.12\n bloom_factor: 3.5\n input_gamma: 2.4\n output_gamma: 2.2\n mask_brightness: 0.80\n curvature_x: 0.05\n curvature_y: 0.10\n mask_type: 2\n enable_scanlines: true\n enable_multisample: true\n enable_gamma: true\n enable_curvature: false\n enable_sharper: false\n";
file << " - name: \"Curved\"\n scanline_weight: 6.0\n scanline_gap_brightness: 0.12\n bloom_factor: 3.5\n input_gamma: 2.4\n output_gamma: 2.2\n mask_brightness: 0.80\n curvature_x: 0.05\n curvature_y: 0.10\n mask_type: 2\n enable_scanlines: true\n enable_multisample: true\n enable_gamma: true\n enable_curvature: true\n enable_sharper: false\n";
file << " - name: \"Sharp\"\n scanline_weight: 6.0\n scanline_gap_brightness: 0.12\n bloom_factor: 3.5\n input_gamma: 2.4\n output_gamma: 2.2\n mask_brightness: 0.80\n curvature_x: 0.05\n curvature_y: 0.10\n mask_type: 2\n enable_scanlines: true\n enable_multisample: false\n enable_gamma: true\n enable_curvature: false\n enable_sharper: true\n";
file << " - name: \"Minimal\"\n scanline_weight: 8.0\n scanline_gap_brightness: 0.05\n bloom_factor: 2.0\n input_gamma: 2.4\n output_gamma: 2.2\n mask_brightness: 1.00\n curvature_x: 0.0\n curvature_y: 0.0\n mask_type: 0\n enable_scanlines: true\n enable_multisample: false\n enable_gamma: false\n enable_curvature: false\n enable_sharper: false\n";
file.close();
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "CrtPi file created with defaults: %s", crtpi_file_path.c_str());
populateDefaultCrtPiPresets();
return true;
}
// Carga los presets de CrtPi desde el fichero
auto loadCrtPiFromFile() -> bool {
crtpi_presets.clear();
std::ifstream file(crtpi_file_path);
if (!file.good()) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "CrtPi file not found, creating default: %s", crtpi_file_path.c_str());
return saveCrtPiDefaults();
}
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")) {
const auto& presets = yaml["presets"];
for (const auto& p : presets) {
CrtPiPreset preset;
if (p.contains("name")) {
preset.name = p["name"].get_value<std::string>();
}
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);
parseIntField(p, "mask_type", preset.mask_type);
parseBoolField(p, "enable_scanlines", preset.enable_scanlines);
parseBoolField(p, "enable_multisample", preset.enable_multisample);
parseBoolField(p, "enable_gamma", preset.enable_gamma);
parseBoolField(p, "enable_curvature", preset.enable_curvature);
parseBoolField(p, "enable_sharper", preset.enable_sharper);
crtpi_presets.push_back(preset);
}
}
if (!crtpi_presets.empty()) {
// Resolver nombre → índice
if (!video.shader.current_crtpi_preset_name.empty()) {
for (int i = 0; i < static_cast<int>(crtpi_presets.size()); ++i) {
if (crtpi_presets[static_cast<size_t>(i)].name == video.shader.current_crtpi_preset_name) {
video.shader.current_crtpi_preset = i;
break;
}
}
}
video.shader.current_crtpi_preset = std::clamp(
video.shader.current_crtpi_preset,
0,
static_cast<int>(crtpi_presets.size()) - 1);
} else {
video.shader.current_crtpi_preset = 0;
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "CrtPi file loaded: %zu preset(s)", crtpi_presets.size());
return true;
} catch (const fkyaml::exception& e) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Error parsing CrtPi YAML: %s. Recreating defaults.", e.what());
return saveCrtPiDefaults();
}
}
// Inicializa las opciones del programa // Inicializa las opciones del programa
void init() { void init() {
// Dificultades // Dificultades
@@ -234,56 +372,86 @@ namespace Options {
const auto& vid = yaml["video"]; const auto& vid = yaml["video"];
if (vid.contains("fullscreen")) { if (vid.contains("fullscreen")) {
try { video.fullscreen = vid["fullscreen"].get_value<bool>(); } catch (...) {} try {
video.fullscreen = vid["fullscreen"].get_value<bool>();
} catch (...) {}
} }
if (vid.contains("scale_mode")) { if (vid.contains("scale_mode")) {
try { video.scale_mode = static_cast<SDL_ScaleMode>(vid["scale_mode"].get_value<int>()); } catch (...) {} try {
video.scale_mode = static_cast<SDL_ScaleMode>(vid["scale_mode"].get_value<int>());
} catch (...) {}
} }
if (vid.contains("vsync")) { if (vid.contains("vsync")) {
try { video.vsync = vid["vsync"].get_value<bool>(); } catch (...) {} try {
video.vsync = vid["vsync"].get_value<bool>();
} catch (...) {}
} }
if (vid.contains("integer_scale")) { if (vid.contains("integer_scale")) {
try { video.integer_scale = vid["integer_scale"].get_value<bool>(); } catch (...) {}
}
if (vid.contains("postfx")) {
try { video.postfx = vid["postfx"].get_value<bool>(); } catch (...) {}
}
// Nuevo formato: supersampling (bool) + supersampling_amount (int)
// Backward compat: si solo existe supersampling como int, también funciona
{
bool ss_enabled = false;
int ss_amount = 3;
if (vid.contains("supersampling")) {
try {
const auto& node = vid["supersampling"];
if (node.is_boolean()) {
ss_enabled = node.get_value<bool>();
} else {
// Formato antiguo: int directamente
int factor = node.get_value<int>();
ss_enabled = factor >= 2;
ss_amount = (factor >= 2) ? factor : 3;
}
} catch (...) {}
}
if (vid.contains("supersampling_amount")) {
try {
int amount = vid["supersampling_amount"].get_value<int>();
if (amount >= 2) { ss_amount = amount; }
} catch (...) {}
}
video.supersampling = ss_enabled ? ss_amount : 1;
}
if (vid.contains("postfx_preset")) {
try { try {
int preset = vid["postfx_preset"].get_value<int>(); video.integer_scale = vid["integer_scale"].get_value<bool>();
// No validamos contra postfx_presets.size() aquí porque postfx.yaml
// aún no se ha cargado. El clamp se hace en loadPostFXFromFile().
if (preset >= 0) {
current_postfx_preset = preset;
}
} catch (...) {} } catch (...) {}
} }
// --- GPU ---
if (vid.contains("gpu")) {
const auto& gpu_node = vid["gpu"];
if (gpu_node.contains("acceleration")) {
try {
video.gpu.acceleration = gpu_node["acceleration"].get_value<bool>();
} catch (...) {}
}
if (gpu_node.contains("preferred_driver")) {
try {
video.gpu.preferred_driver = gpu_node["preferred_driver"].get_value<std::string>();
} catch (...) {}
}
}
// --- Shader config ---
if (vid.contains("shader")) {
const auto& sh = vid["shader"];
if (sh.contains("enabled")) {
try {
video.shader.enabled = sh["enabled"].get_value<bool>();
} catch (...) {}
}
if (sh.contains("current_shader")) {
try {
auto s = sh["current_shader"].get_value<std::string>();
video.shader.current_shader = (s == "crtpi") ? Rendering::ShaderType::CRTPI : Rendering::ShaderType::POSTFX;
} catch (...) {}
}
if (sh.contains("postfx_preset")) {
try {
video.shader.current_postfx_preset_name = sh["postfx_preset"].get_value<std::string>();
} catch (...) {}
}
if (sh.contains("crtpi_preset")) {
try {
video.shader.current_crtpi_preset_name = sh["crtpi_preset"].get_value<std::string>();
} catch (...) {}
}
}
// --- Supersampling ---
if (vid.contains("supersampling")) {
const auto& ss_node = vid["supersampling"];
if (ss_node.contains("enabled")) {
try {
video.supersampling.enabled = ss_node["enabled"].get_value<bool>();
} catch (...) {}
}
if (ss_node.contains("linear_upscale")) {
try {
video.supersampling.linear_upscale = ss_node["linear_upscale"].get_value<bool>();
} catch (...) {}
}
if (ss_node.contains("downscale_algo")) {
try {
video.supersampling.downscale_algo = ss_node["downscale_algo"].get_value<int>();
} catch (...) {}
}
}
} }
void loadAudioFromYaml(const fkyaml::node& yaml) { void loadAudioFromYaml(const fkyaml::node& yaml) {
@@ -291,27 +459,39 @@ namespace Options {
const auto& aud = yaml["audio"]; const auto& aud = yaml["audio"];
if (aud.contains("enabled")) { if (aud.contains("enabled")) {
try { audio.enabled = aud["enabled"].get_value<bool>(); } catch (...) {} try {
audio.enabled = aud["enabled"].get_value<bool>();
} catch (...) {}
} }
if (aud.contains("volume")) { if (aud.contains("volume")) {
try { audio.volume = std::clamp(aud["volume"].get_value<int>(), 0, 100); } catch (...) {} try {
audio.volume = std::clamp(aud["volume"].get_value<int>(), 0, 100);
} catch (...) {}
} }
if (aud.contains("music")) { if (aud.contains("music")) {
const auto& mus = aud["music"]; const auto& mus = aud["music"];
if (mus.contains("enabled")) { if (mus.contains("enabled")) {
try { audio.music.enabled = mus["enabled"].get_value<bool>(); } catch (...) {} try {
audio.music.enabled = mus["enabled"].get_value<bool>();
} catch (...) {}
} }
if (mus.contains("volume")) { if (mus.contains("volume")) {
try { audio.music.volume = std::clamp(mus["volume"].get_value<int>(), 0, 100); } catch (...) {} try {
audio.music.volume = std::clamp(mus["volume"].get_value<int>(), 0, 100);
} catch (...) {}
} }
} }
if (aud.contains("sound")) { if (aud.contains("sound")) {
const auto& snd = aud["sound"]; const auto& snd = aud["sound"];
if (snd.contains("enabled")) { if (snd.contains("enabled")) {
try { audio.sound.enabled = snd["enabled"].get_value<bool>(); } catch (...) {} try {
audio.sound.enabled = snd["enabled"].get_value<bool>();
} catch (...) {}
} }
if (snd.contains("volume")) { if (snd.contains("volume")) {
try { audio.sound.volume = std::clamp(snd["volume"].get_value<int>(), 0, 100); } catch (...) {} try {
audio.sound.volume = std::clamp(snd["volume"].get_value<int>(), 0, 100);
} catch (...) {}
} }
} }
} }
@@ -338,13 +518,19 @@ namespace Options {
} catch (...) {} } catch (...) {}
} }
if (game.contains("autofire")) { if (game.contains("autofire")) {
try { settings.autofire = game["autofire"].get_value<bool>(); } catch (...) {} try {
settings.autofire = game["autofire"].get_value<bool>();
} catch (...) {}
} }
if (game.contains("shutdown_enabled")) { if (game.contains("shutdown_enabled")) {
try { settings.shutdown_enabled = game["shutdown_enabled"].get_value<bool>(); } catch (...) {} try {
settings.shutdown_enabled = game["shutdown_enabled"].get_value<bool>();
} catch (...) {}
} }
if (game.contains("params_file")) { if (game.contains("params_file")) {
try { settings.params_file = game["params_file"].get_value<std::string>(); } catch (...) {} try {
settings.params_file = game["params_file"].get_value<std::string>();
} catch (...) {}
} }
} }
@@ -356,10 +542,14 @@ namespace Options {
for (const auto& ctrl : controllers) { for (const auto& ctrl : controllers) {
if (i >= GamepadManager::size()) { break; } if (i >= GamepadManager::size()) { break; }
if (ctrl.contains("name")) { if (ctrl.contains("name")) {
try { gamepad_manager[i].name = ctrl["name"].get_value<std::string>(); } catch (...) {} try {
gamepad_manager[i].name = ctrl["name"].get_value<std::string>();
} catch (...) {}
} }
if (ctrl.contains("path")) { if (ctrl.contains("path")) {
try { gamepad_manager[i].path = ctrl["path"].get_value<std::string>(); } catch (...) {} try {
gamepad_manager[i].path = ctrl["path"].get_value<std::string>();
} catch (...) {}
} }
if (ctrl.contains("player")) { if (ctrl.contains("player")) {
try { try {
@@ -379,7 +569,9 @@ namespace Options {
if (!yaml.contains("keyboard")) { return; } if (!yaml.contains("keyboard")) { return; }
const auto& kb = yaml["keyboard"]; const auto& kb = yaml["keyboard"];
if (kb.contains("player")) { if (kb.contains("player")) {
try { keyboard.player_id = static_cast<Player::Id>(kb["player"].get_value<int>()); } catch (...) {} try {
keyboard.player_id = static_cast<Player::Id>(kb["player"].get_value<int>());
} catch (...) {}
} }
} }
@@ -401,6 +593,20 @@ namespace Options {
try { try {
auto yaml = fkyaml::node::deserialize(content); auto yaml = fkyaml::node::deserialize(content);
// Comprobar versión: si no coincide, regenerar config por defecto
int file_version = 0;
if (yaml.contains("version")) {
try {
file_version = yaml["version"].get_value<int>();
} catch (...) {}
}
if (file_version != Settings::CURRENT_CONFIG_VERSION) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Config version %d != expected %d. Recreating defaults.", file_version, Settings::CURRENT_CONFIG_VERSION);
init();
saveToFile();
return true;
}
loadWindowFromYaml(yaml); loadWindowFromYaml(yaml);
loadVideoFromYaml(yaml); loadVideoFromYaml(yaml);
loadAudioFromYaml(yaml); loadAudioFromYaml(yaml);
@@ -452,10 +658,26 @@ namespace Options {
file << " scale_mode: " << static_cast<int>(video.scale_mode) << " # " << static_cast<int>(SDL_ScaleMode::SDL_SCALEMODE_NEAREST) << ": nearest, " << static_cast<int>(SDL_ScaleMode::SDL_SCALEMODE_LINEAR) << ": linear\n"; file << " scale_mode: " << static_cast<int>(video.scale_mode) << " # " << static_cast<int>(SDL_ScaleMode::SDL_SCALEMODE_NEAREST) << ": nearest, " << static_cast<int>(SDL_ScaleMode::SDL_SCALEMODE_LINEAR) << ": linear\n";
file << " vsync: " << boolToString(video.vsync) << "\n"; file << " vsync: " << boolToString(video.vsync) << "\n";
file << " integer_scale: " << boolToString(video.integer_scale) << "\n"; file << " integer_scale: " << boolToString(video.integer_scale) << "\n";
file << " postfx: " << boolToString(video.postfx) << "\n"; file << " gpu:\n";
file << " postfx_preset: " << current_postfx_preset << "\n"; file << " acceleration: " << boolToString(video.gpu.acceleration) << "\n";
file << " supersampling: " << boolToString(video.supersampling > 1) << "\n"; file << " preferred_driver: \"" << video.gpu.preferred_driver << "\"\n";
file << " supersampling_amount: " << std::max(2, video.supersampling) << "\n"; file << " shader:\n";
file << " enabled: " << boolToString(video.shader.enabled) << "\n";
file << " current_shader: " << (video.shader.current_shader == Rendering::ShaderType::CRTPI ? "crtpi" : "postfx") << "\n";
{
std::string postfx_name = (!postfx_presets.empty() && video.shader.current_postfx_preset < static_cast<int>(postfx_presets.size()))
? postfx_presets[static_cast<size_t>(video.shader.current_postfx_preset)].name
: "";
std::string crtpi_name = (!crtpi_presets.empty() && video.shader.current_crtpi_preset < static_cast<int>(crtpi_presets.size()))
? crtpi_presets[static_cast<size_t>(video.shader.current_crtpi_preset)].name
: "";
file << " postfx_preset: \"" << postfx_name << "\"\n";
file << " crtpi_preset: \"" << crtpi_name << "\"\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 << "\n"; file << "\n";
// AUDIO // AUDIO

View File

@@ -14,11 +14,12 @@
#include <vector> // Para vector #include <vector> // Para vector
#include "defaults.hpp" #include "defaults.hpp"
#include "difficulty.hpp" // for Code #include "difficulty.hpp" // for Code
#include "input.hpp" // for Input #include "input.hpp" // for Input
#include "lang.hpp" // for Code #include "lang.hpp" // for Code
#include "manage_hiscore_table.hpp" // for ManageHiScoreTable, Table #include "manage_hiscore_table.hpp" // for ManageHiScoreTable, Table
#include "player.hpp" // for Player #include "player.hpp" // for Player
#include "rendering/shader_backend.hpp" // for Rendering::ShaderType
// --- Namespace Options: gestión de configuración y opciones del juego --- // --- Namespace Options: gestión de configuración y opciones del juego ---
namespace Options { namespace Options {
@@ -36,20 +37,59 @@ namespace Options {
float flicker{0.0F}; float flicker{0.0F};
}; };
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};
};
struct Window { struct Window {
std::string caption = Defaults::Window::CAPTION; // Texto que aparece en la barra de título de la ventana std::string caption = Defaults::Window::CAPTION;
int zoom = Defaults::Window::ZOOM; // Valor por el que se multiplica el tamaño de la ventana int zoom = Defaults::Window::ZOOM;
int max_zoom = Defaults::Window::MAX_ZOOM; // Tamaño máximo para que la ventana no sea mayor que la pantalla int max_zoom = Defaults::Window::MAX_ZOOM;
};
struct GPU {
bool acceleration{Defaults::Video::GPU_ACCELERATION};
std::string 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;
std::string current_crtpi_preset_name;
int current_postfx_preset{0};
int current_crtpi_preset{0};
}; };
struct Video { struct Video {
SDL_ScaleMode scale_mode = Defaults::Video::SCALE_MODE; // Filtro usado para el escalado de la imagen SDL_ScaleMode scale_mode = Defaults::Video::SCALE_MODE;
bool fullscreen = Defaults::Video::FULLSCREEN; // Indica si se usa pantalla completa bool fullscreen = Defaults::Video::FULLSCREEN;
bool vsync = Defaults::Video::VSYNC; // Indica si se usa vsync bool vsync = Defaults::Video::VSYNC;
bool integer_scale = Defaults::Video::INTEGER_SCALE; // Indica si se usa escalado entero bool integer_scale = Defaults::Video::INTEGER_SCALE;
bool postfx = Defaults::Video::POSTFX; // Indica si se usan efectos PostFX std::string info;
int supersampling = Defaults::Video::SUPERSAMPLING; // Factor de supersampling: 1=off, 2=2×, 3=3× GPU gpu{};
std::string info; // Información sobre el modo de vídeo Supersampling supersampling{};
ShaderConfig shader{};
}; };
struct Music { struct Music {
@@ -63,14 +103,15 @@ namespace Options {
}; };
struct Audio { struct Audio {
Music music; // Opciones para la música Music music; // Opciones para la música
Sound sound; // Opciones para los efectos de sonido Sound sound; // Opciones para los efectos de sonido
bool enabled = Defaults::Audio::ENABLED; // Indica si el audio está activo o no bool enabled = Defaults::Audio::ENABLED; // Indica si el audio está activo o no
int volume = Defaults::Audio::VOLUME; // Volumen general del audio int volume = Defaults::Audio::VOLUME; // Volumen general del audio
}; };
struct Settings { struct Settings {
int config_version = 2; // Versión del archivo de configuración static constexpr int CURRENT_CONFIG_VERSION = 3; // Versión esperada del fichero
int config_version = CURRENT_CONFIG_VERSION; // Versión del archivo de configuración
Difficulty::Code difficulty = Difficulty::Code::NORMAL; // Dificultad del juego Difficulty::Code difficulty = Difficulty::Code::NORMAL; // Dificultad del juego
Lang::Code language = Lang::Code::VALENCIAN; // Idioma usado en el juego Lang::Code language = Lang::Code::VALENCIAN; // Idioma usado en el juego
bool autofire = Defaults::Settings::AUTOFIRE; // Indicador de autofire bool autofire = Defaults::Settings::AUTOFIRE; // Indicador de autofire
@@ -292,16 +333,19 @@ namespace Options {
extern Keyboard keyboard; // Opciones para el teclado extern Keyboard keyboard; // Opciones para el teclado
extern PendingChanges pending_changes; // Opciones que se aplican al cerrar extern PendingChanges pending_changes; // Opciones que se aplican al cerrar
extern std::vector<PostFXPreset> postfx_presets; // Lista de presets de PostFX extern std::vector<PostFXPreset> postfx_presets; // Lista de presets de PostFX
extern int current_postfx_preset; // Índice del preset PostFX activo
extern std::string postfx_file_path; // Ruta al fichero de presets PostFX extern std::string postfx_file_path; // Ruta al fichero de presets PostFX
extern std::vector<CrtPiPreset> crtpi_presets; // Lista de presets de CrtPi
extern std::string crtpi_file_path; // Ruta al fichero de presets CrtPi
// --- Funciones --- // --- Funciones ---
void init(); // Inicializa las opciones del programa void init(); // Inicializa las opciones del programa
void setConfigFile(const std::string& file_path); // Establece el fichero de configuración void setConfigFile(const std::string& file_path); // Establece el fichero de configuración
void setControllersFile(const std::string& file_path); // Establece el fichero de configuración de mandos void setControllersFile(const std::string& file_path); // Establece el fichero de configuración de mandos
void setPostFXFile(const std::string& path); // Establece el fichero de presets PostFX void setPostFXFile(const std::string& path); // Establece el fichero de presets PostFX
void setCrtPiFile(const std::string& path); // Establece el fichero de presets CrtPi
auto loadPostFXFromFile() -> bool; // Carga los presets PostFX desde fichero auto loadPostFXFromFile() -> bool; // Carga los presets PostFX desde fichero
auto savePostFXToFile() -> bool; // Guarda los presets PostFX por defecto al fichero auto savePostFXToFile() -> bool; // Guarda los presets PostFX por defecto al fichero
auto loadCrtPiFromFile() -> bool; // Carga los presets CrtPi desde fichero
auto loadFromFile() -> bool; // Carga el fichero de configuración auto loadFromFile() -> bool; // Carga el fichero de configuración
auto saveToFile() -> bool; // Guarda el fichero de configuración auto saveToFile() -> bool; // Guarda el fichero de configuración
void setKeyboardToPlayer(Player::Id player_id); // Asigna el teclado al jugador void setKeyboardToPlayer(Player::Id player_id); // Asigna el teclado al jugador

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -3,24 +3,56 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <SDL3/SDL_gpu.h> #include <SDL3/SDL_gpu.h>
#include <string>
#include <utility>
#include "rendering/shader_backend.hpp" #include "rendering/shader_backend.hpp"
// PostFX uniforms pushed to fragment stage each frame. // 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. // 12 floats = 48 bytes — meets Metal/Vulkan 16-byte alignment requirement.
struct PostFXUniforms { struct PostFXUniforms {
float vignette_strength; // 0 = none, ~0.8 = subtle float vignette_strength;
float chroma_strength; // 0 = off, ~0.2 = subtle chromatic aberration float chroma_strength;
float scanline_strength; // 0 = off, 1 = full float scanline_strength;
float screen_height; // logical height in pixels (used by bleeding effect) float screen_height;
float mask_strength; // 0 = off, 1 = full phosphor dot mask float mask_strength;
float gamma_strength; // 0 = off, 1 = full gamma 2.4/2.2 correction float gamma_strength;
float curvature; // 0 = flat, 1 = max barrel distortion float curvature;
float bleeding; // 0 = off, 1 = max NTSC chrominance bleeding float bleeding;
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_) float pixel_scale;
float time; // seconds since SDL init (SDL_GetTicks() / 1000.0f) float time;
float oversample; // supersampling factor (1.0 = off, 3.0 = 3×SS) float oversample;
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz — keep struct at 48 bytes (3 × 16) float flicker;
};
// CrtPi uniforms pushed to fragment stage each frame.
// 16 fields = 64 bytes — 4 × 16-byte alignment.
struct CrtPiUniforms {
float scanline_weight;
float scanline_gap_brightness;
float bloom_factor;
float input_gamma;
float output_gamma;
float mask_brightness;
float curvature_x;
float curvature_y;
int mask_type;
int enable_scanlines;
int enable_multisample;
int enable_gamma;
int enable_curvature;
int enable_sharper;
float texture_width;
float texture_height;
};
// Downscale uniforms for Lanczos downscale fragment stage.
// 1 int + 3 floats = 16 bytes.
struct DownscaleUniforms {
int algorithm;
float pad0;
float pad1;
float pad2;
}; };
namespace Rendering { namespace Rendering {
@@ -28,9 +60,8 @@ namespace Rendering {
/** /**
* @brief Backend de shaders usando SDL3 GPU API (Metal en macOS, Vulkan/SPIR-V en Win/Linux) * @brief Backend de shaders usando SDL3 GPU API (Metal en macOS, Vulkan/SPIR-V en Win/Linux)
* *
* Backend de shaders PostFX para macOS (Metal) y Win/Linux (Vulkan/SPIR-V).
* Pipeline: Surface pixels (CPU) → SDL_GPUTransferBuffer → SDL_GPUTexture (scene) * Pipeline: Surface pixels (CPU) → SDL_GPUTransferBuffer → SDL_GPUTexture (scene)
* → PostFX render pass → swapchain → present * → [Upscale →] PostFX/CrtPi render pass → [Lanczos downscale →] swapchain → present
*/ */
class SDL3GPUShader : public ShaderBackend { class SDL3GPUShader : public ShaderBackend {
public: public:
@@ -44,25 +75,28 @@ namespace Rendering {
void render() override; void render() override;
void setTextureSize(float width, float height) override {} void setTextureSize(float width, float height) override {}
void cleanup() final; // Libera pipeline/texturas pero mantiene el device vivo void cleanup() final;
void destroy(); // Limpieza completa (device + swapchain); llamar solo al cerrar void destroy();
[[nodiscard]] auto isHardwareAccelerated() const -> bool override { return is_initialized_; } [[nodiscard]] auto isHardwareAccelerated() const -> bool override { return is_initialized_; }
[[nodiscard]] auto getDriverName() const -> std::string override { return driver_name_; }
// Sube píxeles ARGB8888 desde CPU; llamado antes de render() void setPreferredDriver(const std::string& driver) override { preferred_driver_ = driver; }
void uploadPixels(const Uint32* pixels, int width, int height) override; 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; void setPostFXParams(const PostFXParams& p) override;
// Activa/desactiva VSync en el swapchain
void setVSync(bool vsync) override; void setVSync(bool vsync) override;
// Activa/desactiva escalado entero (integer scale)
void setScaleMode(bool integer_scale) override; void setScaleMode(bool integer_scale) override;
// Establece factor de supersampling (1 = off, 3 = 3×SS)
void setOversample(int factor) override; void setOversample(int factor) override;
void setLinearUpscale(bool linear) override;
[[nodiscard]] auto isLinearUpscale() const -> bool override { return linear_upscale_; }
void setDownscaleAlgo(int algo) override;
[[nodiscard]] auto getDownscaleAlgo() const -> int override { return downscale_algo_; }
[[nodiscard]] auto getSsTextureSize() const -> std::pair<int, int> override;
void setActiveShader(ShaderType type) override;
void setCrtPiParams(const CrtPiParams& p) override;
[[nodiscard]] auto getActiveShader() const -> ShaderType override { return active_shader_; }
private: private:
static auto createShaderMSL(SDL_GPUDevice* device, static auto createShaderMSL(SDL_GPUDevice* device,
const char* msl_source, const char* msl_source,
@@ -80,27 +114,41 @@ namespace Rendering {
Uint32 num_uniform_buffers) -> SDL_GPUShader*; Uint32 num_uniform_buffers) -> SDL_GPUShader*;
auto createPipeline() -> bool; auto createPipeline() -> bool;
auto reinitTexturesAndBuffer() -> bool; // Recrea textura y buffer con oversample actual auto createCrtPiPipeline() -> bool;
auto reinitTexturesAndBuffer() -> bool;
auto recreateScaledTexture(int factor) -> bool;
static auto calcSsFactor(float zoom) -> int;
[[nodiscard]] auto bestPresentMode(bool vsync) const -> SDL_GPUPresentMode;
SDL_Window* window_ = nullptr; SDL_Window* window_ = nullptr;
SDL_GPUDevice* device_ = nullptr; SDL_GPUDevice* device_ = nullptr;
SDL_GPUGraphicsPipeline* pipeline_ = nullptr; SDL_GPUGraphicsPipeline* pipeline_ = nullptr;
SDL_GPUGraphicsPipeline* crtpi_pipeline_ = nullptr;
SDL_GPUGraphicsPipeline* postfx_offscreen_pipeline_ = nullptr;
SDL_GPUGraphicsPipeline* upscale_pipeline_ = nullptr;
SDL_GPUGraphicsPipeline* downscale_pipeline_ = nullptr;
SDL_GPUTexture* scene_texture_ = nullptr; SDL_GPUTexture* scene_texture_ = nullptr;
SDL_GPUTexture* scaled_texture_ = nullptr;
SDL_GPUTexture* postfx_texture_ = nullptr;
SDL_GPUTransferBuffer* upload_buffer_ = nullptr; SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
SDL_GPUSampler* sampler_ = nullptr; // NEAREST — para path sin supersampling SDL_GPUSampler* sampler_ = nullptr;
SDL_GPUSampler* linear_sampler_ = nullptr; // LINEAR — para path con supersampling SDL_GPUSampler* linear_sampler_ = nullptr;
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}; 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;
int game_width_ = 0; // Dimensiones originales del canvas (sin SS) int game_width_ = 0;
int game_height_ = 0; int game_height_ = 0;
int tex_width_ = 0; // Dimensiones de la textura GPU (game × oversample_) int ss_factor_ = 0;
int tex_height_ = 0; int oversample_ = 1;
int oversample_ = 1; // Factor SS actual (1 o 3) int downscale_algo_ = 1;
float baked_scanline_strength_ = 0.0F; // Guardado para hornear en CPU std::string driver_name_;
std::string preferred_driver_;
bool is_initialized_ = false; bool is_initialized_ = false;
bool vsync_ = true; bool vsync_ = true;
bool integer_scale_ = false; bool integer_scale_ = false;
bool linear_upscale_ = false;
}; };
} // namespace Rendering } // namespace Rendering

View 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;

View File

@@ -3,9 +3,14 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <string> #include <string>
#include <utility>
namespace Rendering { namespace Rendering {
/** @brief Identificador del shader de post-procesado activo */
enum class ShaderType { POSTFX,
CRTPI };
/** /**
* @brief Parámetros de intensidad de los efectos PostFX * @brief Parámetros de intensidad de los efectos PostFX
*/ */
@@ -20,57 +25,64 @@ namespace Rendering {
float flicker = 0.0F; float flicker = 0.0F;
}; };
/**
* @brief Parámetros del shader CRT-Pi (algoritmo de scanlines continuas)
*/
struct CrtPiParams {
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};
};
/** /**
* @brief Interfaz abstracta para backends de renderizado con shaders * @brief Interfaz abstracta para backends de renderizado con shaders
*
* Esta interfaz define el contrato que todos los backends de shaders
* deben cumplir (Metal, Vulkan, etc.)
*/ */
class ShaderBackend { class ShaderBackend {
public: public:
virtual ~ShaderBackend() = default; 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, virtual auto init(SDL_Window* window,
SDL_Texture* texture, SDL_Texture* texture,
const std::string& vertex_source, const std::string& vertex_source,
const std::string& fragment_source) -> bool = 0; const std::string& fragment_source) -> bool = 0;
/**
* @brief Renderiza la textura con los shaders aplicados
*/
virtual void render() = 0; 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; virtual void setTextureSize(float width, float height) = 0;
/**
* @brief Limpia y libera recursos del backend
*/
virtual void cleanup() = 0; virtual void cleanup() = 0;
/**
* @brief Verifica si el backend está usando aceleración por hardware
* @return true si usa aceleración por hardware
*/
[[nodiscard]] virtual auto isHardwareAccelerated() const -> bool = 0;
virtual void uploadPixels(const Uint32* /*pixels*/, int /*width*/, int /*height*/) {} virtual void uploadPixels(const Uint32* /*pixels*/, int /*width*/, int /*height*/) {}
virtual void setPostFXParams(const PostFXParams& /*p*/) {} virtual void setPostFXParams(const PostFXParams& /*p*/) {}
virtual void setVSync(bool /*vsync*/) {} virtual void setVSync(bool /*vsync*/) {}
virtual void setScaleMode(bool /*integer_scale*/) {} virtual void setScaleMode(bool /*integer_scale*/) {}
virtual void setOversample(int /*factor*/) {} virtual void setOversample(int /*factor*/) {}
virtual void setLinearUpscale(bool /*linear*/) {}
[[nodiscard]] virtual auto isLinearUpscale() const -> bool { return false; }
virtual void setDownscaleAlgo(int /*algo*/) {}
[[nodiscard]] virtual auto getDownscaleAlgo() const -> int { return 0; }
[[nodiscard]] virtual auto getSsTextureSize() const -> std::pair<int, int> { return {0, 0}; }
[[nodiscard]] virtual auto isHardwareAccelerated() const -> bool = 0;
[[nodiscard]] virtual auto getDriverName() const -> std::string { return {}; }
virtual void setPreferredDriver(const std::string& /*driver*/) {}
virtual void setActiveShader(ShaderType /*type*/) {}
virtual void setCrtPiParams(const CrtPiParams& /*p*/) {}
[[nodiscard]] virtual auto getActiveShader() const -> ShaderType { return ShaderType::POSTFX; }
}; };
} // namespace Rendering } // namespace Rendering

View File

@@ -8,16 +8,19 @@
#include <string> // Para basic_string, operator+, char_traits, to_string, string #include <string> // Para basic_string, operator+, char_traits, to_string, string
#include <vector> // Para vector #include <vector> // Para vector
#include "asset.hpp" // Para Asset #include "asset.hpp" // Para Asset
#include "mouse.hpp" // Para updateCursorVisibility #include "director.hpp" // Para Director::debug_config
#include "options.hpp" // Para Video, video, Window, window #include "mouse.hpp" // Para updateCursorVisibility
#include "param.hpp" // Para Param, param, ParamGame, ParamDebug #include "options.hpp" // Para Video, video, Window, window
#include "param.hpp" // Para Param, param, ParamGame, ParamDebug
#include "rendering/sdl3gpu/sdl3gpu_shader.hpp" // Para SDL3GPUShader #include "rendering/sdl3gpu/sdl3gpu_shader.hpp" // Para SDL3GPUShader
#include "text.hpp" // Para Text #include "resource.hpp" // Para Resource
#include "texture.hpp" // Para Texture #include "text.hpp" // Para Text
#include "ui/logger.hpp" // Para info #include "texture.hpp" // Para Texture
#include "ui/notifier.hpp" // Para Notifier #include "ui/logger.hpp" // Para info
#include "ui/service_menu.hpp" // Para ServiceMenu #include "ui/notifier.hpp" // Para Notifier
#include "ui/service_menu.hpp" // Para ServiceMenu
#include "utils.hpp" // Para toLower
// Singleton // Singleton
Screen* Screen::instance = nullptr; Screen* Screen::instance = nullptr;
@@ -25,7 +28,7 @@ Screen* Screen::instance = nullptr;
// Inicializa la instancia única del singleton // Inicializa la instancia única del singleton
void Screen::init() { void Screen::init() {
Screen::instance = new Screen(); Screen::instance = new Screen();
Screen::initPostFX(); // Llamar aquí para que Screen::get() ya devuelva la instancia Screen::initShaders(); // Llamar aquí para que Screen::get() ya devuelva la instancia
} }
// Libera la instancia // Libera la instancia
@@ -58,7 +61,7 @@ Screen::Screen()
#ifdef _DEBUG #ifdef _DEBUG
debug_info_.text = text_; debug_info_.text = text_;
setDebugInfoEnabled(true); setDebugInfoEnabled(Director::debug_config.show_render_info);
#endif #endif
// Renderizar una vez la textura vacía para que tenga contenido válido antes de inicializar los shaders (evita pantalla negra) // Renderizar una vez la textura vacía para que tenga contenido válido antes de inicializar los shaders (evita pantalla negra)
@@ -68,7 +71,6 @@ Screen::Screen()
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255); SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
SDL_RenderClear(renderer_); SDL_RenderClear(renderer_);
SDL_RenderPresent(renderer_); SDL_RenderPresent(renderer_);
} }
// Destructor // Destructor
@@ -112,13 +114,11 @@ void Screen::renderPresent() {
SDL_Surface* surface = SDL_RenderReadPixels(renderer_, nullptr); SDL_Surface* surface = SDL_RenderReadPixels(renderer_, nullptr);
if (surface != nullptr) { if (surface != nullptr) {
if (surface->format == SDL_PIXELFORMAT_ARGB8888) { if (surface->format == SDL_PIXELFORMAT_ARGB8888) {
std::memcpy(pixel_buffer_.data(), surface->pixels, std::memcpy(pixel_buffer_.data(), surface->pixels, pixel_buffer_.size() * sizeof(Uint32));
pixel_buffer_.size() * sizeof(Uint32));
} else { } else {
SDL_Surface* converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888); SDL_Surface* converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888);
if (converted != nullptr) { if (converted != nullptr) {
std::memcpy(pixel_buffer_.data(), converted->pixels, std::memcpy(pixel_buffer_.data(), converted->pixels, pixel_buffer_.size() * sizeof(Uint32));
pixel_buffer_.size() * sizeof(Uint32));
SDL_DestroySurface(converted); SDL_DestroySurface(converted);
} }
} }
@@ -243,39 +243,83 @@ void Screen::renderShake() {
// Muestra información por pantalla // Muestra información por pantalla
void Screen::renderInfo() const { void Screen::renderInfo() const {
if (debug_info_.show) { if (debug_info_.show) {
// Resolution const Color GOLD(0xFF, 0xD7, 0x00);
debug_info_.text->writeDX(Text::COLOR | Text::STROKE, param.game.width - debug_info_.text->length(Options::video.info) - 2, 1, Options::video.info, 1, param.debug.color, 1, param.debug.color.DARKEN(150)); const Color GOLD_SHADOW = GOLD.DARKEN(150);
// Construir texto: fps - driver - preset
std::string info_text = std::to_string(fps_.last_value) + " fps";
// Driver GPU
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
const std::string DRIVER = shader_backend_->getDriverName();
if (!DRIVER.empty()) {
info_text += " - " + toLower(DRIVER);
}
} else {
info_text += " - sdl";
}
// Shader + preset
if (Options::video.shader.enabled) {
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
const std::string PRESET_NAME = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset)).name;
info_text += " - crtpi " + toLower(PRESET_NAME);
} else {
const std::string PRESET_NAME = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset)).name;
info_text += " - postfx " + toLower(PRESET_NAME);
if (Options::video.supersampling.enabled) { info_text += " (ss)"; }
}
}
// Centrado arriba
const int TEXT_WIDTH = debug_info_.text->length(info_text);
const int X_POS = (static_cast<int>(param.game.width) - TEXT_WIDTH) / 2;
debug_info_.text->writeDX(Text::COLOR | Text::STROKE, X_POS, 1, info_text, 1, GOLD, 1, GOLD_SHADOW);
// FPS
const std::string FPS_TEXT = std::to_string(fps_.last_value) + " FPS";
debug_info_.text->writeDX(Text::COLOR | Text::STROKE, param.game.width - debug_info_.text->length(FPS_TEXT) - 2, 1 + debug_info_.text->getCharacterSize(), FPS_TEXT, 1, param.debug.color, 1, param.debug.color.DARKEN(150));
#ifdef RECORDING #ifdef RECORDING
// RECORDING const std::string REC_TEXT = "recording";
debug_info_.text->writeDX(Text::COLOR | Text::STROKE, param.game.width - debug_info_.text->length("RECORDING"), 2 * (1 + debug_info_.text->getCharacterSize()), "RECORDING", 1, param.debug.color, 1, param.debug.color.DARKEN(150)); const int REC_WIDTH = debug_info_.text->length(REC_TEXT);
const int REC_X = (static_cast<int>(param.game.width) - REC_WIDTH) / 2;
debug_info_.text->writeDX(Text::COLOR | Text::STROKE, REC_X, 1 + debug_info_.text->getCharacterSize(), REC_TEXT, 1, GOLD, 1, GOLD_SHADOW);
#endif #endif
} }
} }
#endif #endif
// Inicializa PostFX (SDL3GPU) // Inicializa shaders (SDL3GPU)
void Screen::initPostFX() { void Screen::initShaders() {
#ifndef NO_SHADERS #ifndef NO_SHADERS
auto* self = Screen::get(); auto* self = Screen::get();
if (self == nullptr) { if (self == nullptr) {
SDL_Log("Screen::initPostFX: instance is null, skipping"); SDL_Log("Screen::initShaders: instance is null, skipping");
return; return;
} }
if (!self->shader_backend_) { if (!self->shader_backend_) {
self->shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>(); self->shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
const std::string FALLBACK_DRIVER = "none";
self->shader_backend_->setPreferredDriver(
Options::video.gpu.acceleration ? Options::video.gpu.preferred_driver : FALLBACK_DRIVER);
} }
if (!self->shader_backend_->isHardwareAccelerated()) { if (!self->shader_backend_->isHardwareAccelerated()) {
const bool ok = self->shader_backend_->init(self->window_, self->game_canvas_, "", ""); const bool ok = self->shader_backend_->init(self->window_, self->game_canvas_, "", "");
SDL_Log("Screen::initPostFX: SDL3GPUShader::init() = %s", ok ? "OK" : "FAILED"); SDL_Log("Screen::initShaders: SDL3GPUShader::init() = %s", ok ? "OK" : "FAILED");
}
if (self->shader_backend_ && self->shader_backend_->isHardwareAccelerated()) {
self->shader_backend_->setLinearUpscale(Options::video.supersampling.linear_upscale);
self->shader_backend_->setDownscaleAlgo(Options::video.supersampling.downscale_algo);
self->shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
if (!Options::video.shader.enabled) {
// Passthrough: POSTFX con parámetros a cero
self->shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
self->shader_backend_->setPostFXParams(Rendering::PostFXParams{});
} else if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
self->shader_backend_->setActiveShader(Rendering::ShaderType::CRTPI);
self->applyCurrentCrtPiPreset();
} else {
self->shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
self->applyCurrentPostFXPreset();
}
} }
SDL_Log("Screen::initPostFX: presets=%d current=%d postfx=%s",
static_cast<int>(Options::postfx_presets.size()),
Options::current_postfx_preset,
Options::video.postfx ? "ON" : "OFF");
self->applyCurrentPostFXPreset();
#endif #endif
} }
@@ -438,11 +482,34 @@ void Screen::getDisplayInfo() {
} }
} }
// Alterna entre activar y desactivar los efectos PostFX // Alterna activar/desactivar shaders
void Screen::togglePostFX() { void Screen::toggleShaders() {
Options::video.postfx = !Options::video.postfx; Options::video.shader.enabled = !Options::video.shader.enabled;
auto* self = Screen::get(); auto* self = Screen::get();
if (self != nullptr) { if (self != nullptr) {
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
self->applyCurrentCrtPiPreset();
} else {
self->applyCurrentPostFXPreset();
}
}
}
// Cambia entre PostFX y CrtPi
void Screen::nextShader() {
auto* self = Screen::get();
if (self == nullptr || !self->shader_backend_ || !self->shader_backend_->isHardwareAccelerated()) { return; }
const auto NEXT = (Options::video.shader.current_shader == Rendering::ShaderType::POSTFX)
? Rendering::ShaderType::CRTPI
: Rendering::ShaderType::POSTFX;
Options::video.shader.current_shader = NEXT;
self->shader_backend_->setActiveShader(NEXT);
if (NEXT == Rendering::ShaderType::CRTPI) {
self->applyCurrentCrtPiPreset();
} else {
self->applyCurrentPostFXPreset(); self->applyCurrentPostFXPreset();
} }
} }
@@ -450,49 +517,87 @@ void Screen::togglePostFX() {
// Avanza al siguiente preset PostFX // Avanza al siguiente preset PostFX
void Screen::nextPostFXPreset() { void Screen::nextPostFXPreset() {
if (Options::postfx_presets.empty()) { return; } if (Options::postfx_presets.empty()) { return; }
Options::current_postfx_preset = (Options::current_postfx_preset + 1) % static_cast<int>(Options::postfx_presets.size()); Options::video.shader.current_postfx_preset = (Options::video.shader.current_postfx_preset + 1) % static_cast<int>(Options::postfx_presets.size());
auto* self = Screen::get(); auto* self = Screen::get();
if (self != nullptr) { if (self != nullptr) {
self->applyCurrentPostFXPreset(); self->applyCurrentPostFXPreset();
} }
} }
// Alterna entre activar y desactivar el supersampling 3x // Avanza al siguiente preset CrtPi
void Screen::nextCrtPiPreset() {
if (Options::crtpi_presets.empty()) { return; }
Options::video.shader.current_crtpi_preset = (Options::video.shader.current_crtpi_preset + 1) % static_cast<int>(Options::crtpi_presets.size());
auto* self = Screen::get();
if (self != nullptr) {
self->applyCurrentCrtPiPreset();
}
}
// Alterna supersampling
void Screen::toggleSupersampling() { void Screen::toggleSupersampling() {
Options::video.supersampling = (Options::video.supersampling % 3) + 1; Options::video.supersampling.enabled = !Options::video.supersampling.enabled;
auto* self = Screen::get(); auto* self = Screen::get();
if (self != nullptr && self->shader_backend_ && self->shader_backend_->isHardwareAccelerated()) { if (self != nullptr && self->shader_backend_ && self->shader_backend_->isHardwareAccelerated()) {
self->applyCurrentPostFXPreset(); self->shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
self->applyCurrentCrtPiPreset();
} else {
self->applyCurrentPostFXPreset();
}
} }
} }
// Aplica el preset PostFX activo al backend // Aplica el preset PostFX activo al backend
void Screen::applyCurrentPostFXPreset() { void Screen::applyCurrentPostFXPreset() {
if (!shader_backend_) { return; } if (!shader_backend_) { return; }
// setOversample PRIMERO: puede recrear texturas antes de que setPostFXParams shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
// decida si hornear scanlines en CPU o aplicarlas en GPU.
shader_backend_->setOversample(Options::video.supersampling);
Rendering::PostFXParams p{}; Rendering::PostFXParams p{};
if (Options::video.postfx && !Options::postfx_presets.empty()) { if (Options::video.shader.enabled && !Options::postfx_presets.empty()) {
const auto& preset = Options::postfx_presets.at(static_cast<size_t>(Options::current_postfx_preset)); const auto& preset = Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset));
p.vignette = preset.vignette; p.vignette = preset.vignette;
p.scanlines = preset.scanlines; p.scanlines = preset.scanlines;
p.chroma = preset.chroma; p.chroma = preset.chroma;
p.mask = preset.mask; p.mask = preset.mask;
p.gamma = preset.gamma; p.gamma = preset.gamma;
p.curvature = preset.curvature; p.curvature = preset.curvature;
p.bleeding = preset.bleeding; p.bleeding = preset.bleeding;
p.flicker = preset.flicker; p.flicker = preset.flicker;
SDL_Log("Screen::applyCurrentPostFXPreset: preset='%s' scan=%.2f vign=%.2f chroma=%.2f ss=%d×", SDL_Log("Screen::applyCurrentPostFXPreset: preset='%s' scan=%.2f vign=%.2f chroma=%.2f",
preset.name.c_str(), p.scanlines, p.vignette, p.chroma, Options::video.supersampling); preset.name.c_str(),
} else { p.scanlines,
SDL_Log("Screen::applyCurrentPostFXPreset: PostFX=%s presets=%d → passthrough", p.vignette,
Options::video.postfx ? "ON" : "OFF", p.chroma);
static_cast<int>(Options::postfx_presets.size()));
} }
shader_backend_->setPostFXParams(p); shader_backend_->setPostFXParams(p);
} }
// Aplica el preset CrtPi activo al backend
void Screen::applyCurrentCrtPiPreset() {
if (!shader_backend_) { return; }
if (Options::video.shader.enabled && !Options::crtpi_presets.empty()) {
const auto& preset = Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset));
Rendering::CrtPiParams p{
.scanline_weight = preset.scanline_weight,
.scanline_gap_brightness = preset.scanline_gap_brightness,
.bloom_factor = preset.bloom_factor,
.input_gamma = preset.input_gamma,
.output_gamma = preset.output_gamma,
.mask_brightness = preset.mask_brightness,
.curvature_x = preset.curvature_x,
.curvature_y = preset.curvature_y,
.mask_type = preset.mask_type,
.enable_scanlines = preset.enable_scanlines,
.enable_multisample = preset.enable_multisample,
.enable_gamma = preset.enable_gamma,
.enable_curvature = preset.enable_curvature,
.enable_sharper = preset.enable_sharper,
};
shader_backend_->setCrtPiParams(p);
SDL_Log("Screen::applyCurrentCrtPiPreset: preset='%s'", preset.name.c_str());
}
}
// Alterna entre activar y desactivar el escalado entero // Alterna entre activar y desactivar el escalado entero
void Screen::toggleIntegerScale() { void Screen::toggleIntegerScale() {
Options::video.integer_scale = !Options::video.integer_scale; Options::video.integer_scale = !Options::video.integer_scale;
@@ -521,6 +626,15 @@ void Screen::setVSync(bool enabled) {
void Screen::getSingletons() { void Screen::getSingletons() {
service_menu_ = ServiceMenu::get(); service_menu_ = ServiceMenu::get();
notifier_ = Notifier::get(); notifier_ = Notifier::get();
#ifdef _DEBUG
// Actualizar la fuente de debug a 8bithud (ahora Resource está disponible)
if (Resource::get() != nullptr) {
auto hud_text = Resource::get()->getText("8bithud");
if (hud_text) {
debug_info_.text = hud_text;
}
}
#endif
} }
// Aplica los valores de las opciones // Aplica los valores de las opciones

View File

@@ -6,18 +6,15 @@
#include <string> // Para string #include <string> // Para string
#include <vector> // Para vector #include <vector> // Para vector
#include "color.hpp" // Para Color #include "color.hpp" // Para Color
#include "options.hpp" // Para VideoOptions, video #include "options.hpp" // Para VideoOptions, video
#include "rendering/shader_backend.hpp" // Para Rendering::ShaderType
// Forward declarations // Forward declarations
class Notifier; class Notifier;
class ServiceMenu; class ServiceMenu;
class Text; class Text;
namespace Rendering {
class ShaderBackend;
}
// --- Clase Screen: gestiona la ventana, el renderizador y los efectos visuales globales (singleton) --- // --- Clase Screen: gestiona la ventana, el renderizador y los efectos visuales globales (singleton) ---
class Screen { class Screen {
public: public:
@@ -41,18 +38,20 @@ class Screen {
auto decWindowSize() -> bool; // Reduce el tamaño de la ventana auto decWindowSize() -> bool; // Reduce el tamaño de la ventana
auto incWindowSize() -> bool; // Aumenta el tamaño de la ventana auto incWindowSize() -> bool; // Aumenta el tamaño de la ventana
void applySettings(); // Aplica los valores de las opciones void applySettings(); // Aplica los valores de las opciones
static void initPostFX(); // Inicializa PostFX (SDL3GPU) static void initShaders(); // Inicializa shaders (SDL3GPU)
// --- Efectos visuales --- // --- Efectos visuales ---
void shake(int desp = 2, float delay_s = 0.05F, float duration_s = 0.133F) { shake_effect_.enable(src_rect_, dst_rect_, desp, delay_s, duration_s); } // Agita la pantalla (tiempo en segundos) void shake(int desp = 2, float delay_s = 0.05F, float duration_s = 0.133F) { shake_effect_.enable(src_rect_, dst_rect_, desp, delay_s, duration_s); }
void flash(Color color, float duration_s = 0.167F, float delay_s = 0.0F) { flash_effect_ = FlashEffect(true, duration_s, delay_s, color); } // Pone la pantalla de color (tiempo en segundos) void flash(Color color, float duration_s = 0.167F, float delay_s = 0.0F) { flash_effect_ = FlashEffect(true, duration_s, delay_s, color); }
static void togglePostFX(); // Alterna entre activar y desactivar los efectos PostFX static void toggleShaders(); // Alterna activar/desactivar shaders
static void nextPostFXPreset(); // Avanza al siguiente preset PostFX static void nextShader(); // Cambia entre PostFX y CrtPi
static void toggleSupersampling(); // Alterna entre activar y desactivar el supersampling 3x static void nextPostFXPreset(); // Avanza al siguiente preset PostFX
void toggleIntegerScale(); // Alterna entre activar y desactivar el escalado entero static void nextCrtPiPreset(); // Avanza al siguiente preset CrtPi
void toggleVSync(); // Alterna entre activar y desactivar el V-Sync static void toggleSupersampling(); // Alterna supersampling
void setVSync(bool enabled); // Establece el estado del V-Sync void toggleIntegerScale();
void attenuate(bool value) { attenuate_effect_ = value; } // Atenúa la pantalla void toggleVSync(); // Alterna entre activar y desactivar el V-Sync
void setVSync(bool enabled); // Establece el estado del V-Sync
void attenuate(bool value) { attenuate_effect_ = value; } // Atenúa la pantalla
// --- Getters --- // --- Getters ---
auto getRenderer() -> SDL_Renderer* { return renderer_; } // Obtiene el renderizador auto getRenderer() -> SDL_Renderer* { return renderer_; } // Obtiene el renderizador
@@ -220,30 +219,31 @@ class Screen {
std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend de shaders (SDL3GPU) std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend de shaders (SDL3GPU)
// --- Variables de estado --- // --- Variables de estado ---
SDL_FRect src_rect_; // Coordenadas de origen para dibujar la textura del juego SDL_FRect src_rect_; // Coordenadas de origen para dibujar la textura del juego
SDL_FRect dst_rect_; // Coordenadas destino para dibujar la textura del juego SDL_FRect dst_rect_; // Coordenadas destino para dibujar la textura del juego
std::vector<Uint32> pixel_buffer_; // Buffer de píxeles para SDL_RenderReadPixels std::vector<Uint32> pixel_buffer_; // Buffer de píxeles para SDL_RenderReadPixels
FPS fps_; // Gestión de frames por segundo FPS fps_; // Gestión de frames por segundo
FlashEffect flash_effect_; // Efecto de flash en pantalla FlashEffect flash_effect_; // Efecto de flash en pantalla
ShakeEffect shake_effect_; // Efecto de agitar la pantalla ShakeEffect shake_effect_; // Efecto de agitar la pantalla
bool attenuate_effect_ = false; // Indica si la pantalla ha de estar atenuada bool attenuate_effect_ = false; // Indica si la pantalla ha de estar atenuada
DisplayMonitor display_monitor_; // Información del monitor actual DisplayMonitor display_monitor_; // Información del monitor actual
#ifdef _DEBUG #ifdef _DEBUG
Debug debug_info_; // Información de debug Debug debug_info_; // Información de debug
#endif #endif
// --- Métodos internos --- // --- Métodos internos ---
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
void renderFlash(); // Dibuja el efecto de flash en la pantalla void renderFlash(); // Dibuja el efecto de flash en la pantalla
void renderShake(); // Aplica el efecto de agitar la pantalla void renderShake(); // Aplica el efecto de agitar la pantalla
void renderInfo() const; // Muestra información por pantalla void renderInfo() const; // Muestra información por pantalla
void renderPresent(); // Selecciona y ejecuta el método de renderizado adecuado void renderPresent(); // Selecciona y ejecuta el método de renderizado adecuado
void applyCurrentPostFXPreset(); // Aplica el preset PostFX activo al backend void applyCurrentPostFXPreset(); // Aplica el preset PostFX activo al backend
void adjustWindowSize(); // Calcula el tamaño de la ventana void applyCurrentCrtPiPreset(); // Aplica el preset CrtPi activo al backend
void getDisplayInfo(); // Obtiene información sobre la pantalla void adjustWindowSize(); // Calcula el tamaño de la ventana
void renderOverlays(); // Renderiza todos los overlays y efectos void getDisplayInfo(); // Obtiene información sobre la pantalla
void renderAttenuate(); // Atenúa la pantalla void renderOverlays(); // Renderiza todos los overlays y efectos
void createText(); // Crea el objeto de texto void renderAttenuate(); // Atenúa la pantalla
void createText(); // Crea el objeto de texto
// --- Constructores y destructor privados (singleton) --- // --- Constructores y destructor privados (singleton) ---
Screen(); // Constructor privado Screen(); // Constructor privado

View File

@@ -1128,7 +1128,6 @@ auto Game::allPlayersAreNotPlaying() -> bool {
// Comprueba los eventos que hay en cola // Comprueba los eventos que hay en cola
void Game::handleEvents() { void Game::handleEvents() {
input_->resetJustPressed();
SDL_Event event; SDL_Event event;
while (SDL_PollEvent(&event)) { while (SDL_PollEvent(&event)) {
switch (event.type) { switch (event.type) {

View File

@@ -93,15 +93,15 @@ class Game {
// --- Estructuras --- // --- Estructuras ---
struct Helper { struct Helper {
bool need_coffee{false}; // Indica si se necesitan cafes bool need_coffee{false}; // Indica si se necesitan cafes
bool need_coffee_machine{false}; // Indica si se necesita PowerUp bool need_coffee_machine{false}; // Indica si se necesita PowerUp
bool need_power_ball{false}; // Indica si se necesita una PowerBall bool need_power_ball{false}; // Indica si se necesita una PowerBall
float counter{HELP_COUNTER_S * 1000}; // Contador para no dar ayudas consecutivas float counter{HELP_COUNTER_S * 1000}; // Contador para no dar ayudas consecutivas
int item_disk_odds{ITEM_POINTS_1_DISK_ODDS}; // Probabilidad de aparición del objeto int item_disk_odds{ITEM_POINTS_1_DISK_ODDS}; // Probabilidad de aparición del objeto
int item_gavina_odds{ITEM_POINTS_2_GAVINA_ODDS}; // Probabilidad de aparición del objeto int item_gavina_odds{ITEM_POINTS_2_GAVINA_ODDS}; // Probabilidad de aparición del objeto
int item_pacmar_odds{ITEM_POINTS_3_PACMAR_ODDS}; // Probabilidad de aparición del objeto int item_pacmar_odds{ITEM_POINTS_3_PACMAR_ODDS}; // Probabilidad de aparición del objeto
int item_clock_odds{ITEM_CLOCK_ODDS}; // Probabilidad de aparición del objeto int item_clock_odds{ITEM_CLOCK_ODDS}; // Probabilidad de aparición del objeto
int item_coffee_odds{ITEM_COFFEE_ODDS}; // Probabilidad de aparición del objeto int item_coffee_odds{ITEM_COFFEE_ODDS}; // Probabilidad de aparición del objeto
int item_coffee_machine_odds{ITEM_COFFEE_MACHINE_ODDS}; // Probabilidad de aparición del objeto int item_coffee_machine_odds{ITEM_COFFEE_MACHINE_ODDS}; // Probabilidad de aparición del objeto
}; };

View File

@@ -71,15 +71,15 @@ class Instructions {
std::unique_ptr<Fade> fade_; // Objeto para renderizar fades std::unique_ptr<Fade> fade_; // Objeto para renderizar fades
// --- Variables --- // --- Variables ---
float elapsed_time_ = 0.0F; // Tiempo transcurrido (segundos) float elapsed_time_ = 0.0F; // Tiempo transcurrido (segundos)
Uint64 last_time_ = 0; // Último timestamp para calcular delta-time Uint64 last_time_ = 0; // Último timestamp para calcular delta-time
SDL_FRect view_; // Vista del backbuffer que se va a mostrar por pantalla SDL_FRect view_; // Vista del backbuffer que se va a mostrar por pantalla
SDL_FPoint sprite_pos_ = {.x = 0, .y = 0}; // Posición del primer sprite en la lista SDL_FPoint sprite_pos_ = {.x = 0, .y = 0}; // Posición del primer sprite en la lista
float item_space_ = 2.0; // Espacio entre los items en pantalla float item_space_ = 2.0; // Espacio entre los items en pantalla
std::vector<Line> lines_; // Vector que contiene las líneas animadas en la pantalla std::vector<Line> lines_; // Vector que contiene las líneas animadas en la pantalla
bool all_lines_off_screen_ = false; // Indica si todas las líneas han salido de la pantalla bool all_lines_off_screen_ = false; // Indica si todas las líneas han salido de la pantalla
float start_delay_timer_ = 0.0F; // Timer para retraso antes de mover líneas (segundos) float start_delay_timer_ = 0.0F; // Timer para retraso antes de mover líneas (segundos)
bool start_delay_triggered_ = false; // Bandera para determinar si el retraso ha comenzado bool start_delay_triggered_ = false; // Bandera para determinar si el retraso ha comenzado
// --- Métodos internos --- // --- Métodos internos ---
void update(float delta_time); // Actualiza las variables void update(float delta_time); // Actualiza las variables

View File

@@ -2,26 +2,25 @@
#include <SDL3/SDL.h> // Para SDL_GetTicks, SDL_SetRenderDrawColor, SDL_FRect, SDL_RenderFillRect, SDL_GetRenderTarget, SDL_RenderClear, SDL_RenderRect, SDL_SetRenderTarget, SDL_BLENDMODE_BLEND, SDL_PixelFormat, SDL_PollEvent, SDL_RenderTexture, SDL_TextureAccess, SDL_Event, Uint64 #include <SDL3/SDL.h> // Para SDL_GetTicks, SDL_SetRenderDrawColor, SDL_FRect, SDL_RenderFillRect, SDL_GetRenderTarget, SDL_RenderClear, SDL_RenderRect, SDL_SetRenderTarget, SDL_BLENDMODE_BLEND, SDL_PixelFormat, SDL_PollEvent, SDL_RenderTexture, SDL_TextureAccess, SDL_Event, Uint64
#include <array> // Para array #include <array> // Para array
#include <functional> // Para function #include <string> // Para basic_string, string
#include <string> // Para basic_string, string #include <utility> // Para move
#include <utility> // Para move
#include "audio.hpp" // Para Audio #include "audio.hpp" // Para Audio
#include "card_sprite.hpp" // Para CardSprite
#include "color.hpp" // Para Color #include "color.hpp" // Para Color
#include "global_events.hpp" // Para handle #include "global_events.hpp" // Para handle
#include "global_inputs.hpp" // Para check #include "global_inputs.hpp" // Para check
#include "input.hpp" // Para Input #include "input.hpp" // Para Input
#include "lang.hpp" // Para getText #include "lang.hpp" // Para getText
#include "param.hpp" // Para Param, param, ParamGame, ParamIntro, ParamTitle #include "param.hpp" // Para Param, param, ParamGame, ParamIntro, ParamTitle
#include "path_sprite.hpp" // Para PathSprite, PathType
#include "resource.hpp" // Para Resource #include "resource.hpp" // Para Resource
#include "screen.hpp" // Para Screen #include "screen.hpp" // Para Screen
#include "section.hpp" // Para Name, name, Options, options #include "section.hpp" // Para Name, name, Options, options
#include "text.hpp" // Para Text #include "text.hpp" // Para Text
#include "texture.hpp" // Para Texture #include "texture.hpp" // Para Texture
#include "tiled_bg.hpp" // Para TiledBG, TiledBGMode #include "tiled_bg.hpp" // Para TiledBG, TiledBGMode
#include "utils.hpp" // Para Zone, easeInOutExpo, easeInElastic, easeOutBounce, easeOutElastic, easeOutQuad, easeOutQuint #include "utils.hpp" // Para easeOutBounce
#include "writer.hpp" // Para Writer #include "writer.hpp" // Para Writer
// Constructor // Constructor
@@ -31,7 +30,7 @@ Intro::Intro()
Section::name = Section::Name::INTRO; Section::name = Section::Name::INTRO;
Section::options = Section::Options::NONE; Section::options = Section::Options::NONE;
// Inicializa las imagens // Inicializa las tarjetas
initSprites(); initSprites();
// Inicializa los textos // Inicializa los textos
@@ -58,6 +57,22 @@ void Intro::checkInput() {
// Actualiza las escenas de la intro // Actualiza las escenas de la intro
void Intro::updateScenes() { void Intro::updateScenes() {
// Sonido al lanzar la tarjeta (enable() devuelve true solo la primera vez)
if (card_sprites_.at(scene_)->enable()) {
Audio::get()->playSound(SFX_CARD_THROW);
}
// Cuando la tarjeta actual toca la mesa por primera vez: shake + sonido + la anterior sale despedida
if (!shake_done_ && card_sprites_.at(scene_)->hasFirstTouch()) {
Screen::get()->shake();
Audio::get()->playSound(SFX_CARD_IMPACT);
shake_done_ = true;
if (scene_ > 0) {
card_sprites_.at(scene_ - 1)->startExit();
}
}
switch (scene_) { switch (scene_) {
case 0: case 0:
updateScene0(); updateScene0();
@@ -83,37 +98,32 @@ void Intro::updateScenes() {
} }
void Intro::updateScene0() { void Intro::updateScene0() {
// Primera imagen - UPV // Primer texto cuando aterriza
enableCardAndShadow(0); if (card_sprites_.at(0)->hasLanded() && !texts_.at(0)->hasFinished()) {
// Primer texto de la primera imagen
if (card_sprites_.at(0)->hasFinished() && !texts_.at(0)->hasFinished()) {
texts_.at(0)->setEnabled(true); texts_.at(0)->setEnabled(true);
} }
// Segundo texto de la primera imagen // Segundo texto
if (texts_.at(0)->hasFinished() && !texts_.at(1)->hasFinished()) { if (texts_.at(0)->hasFinished() && !texts_.at(1)->hasFinished()) {
switchText(0, 1); switchText(0, 1);
} }
// Tercer texto de la primera imagen // Tercer texto
if (texts_.at(1)->hasFinished() && !texts_.at(2)->hasFinished()) { if (texts_.at(1)->hasFinished() && !texts_.at(2)->hasFinished()) {
switchText(1, 2); switchText(1, 2);
} }
// Fin de la primera escena // Fin de la primera escena: la tarjeta sale despedida
if (texts_.at(2)->hasFinished()) { if (texts_.at(2)->hasFinished()) {
texts_.at(2)->setEnabled(false); texts_.at(2)->setEnabled(false);
scene_++; scene_++;
shake_done_ = false;
} }
} }
void Intro::updateScene1() { void Intro::updateScene1() {
// Segunda imagen - Máquina // Texto cuando aterriza
enableCardAndShadow(1); if (card_sprites_.at(1)->hasLanded() && !texts_.at(3)->hasFinished()) {
// Primer texto de la segunda imagen
if (card_sprites_.at(1)->hasFinished() && !texts_.at(3)->hasFinished()) {
texts_.at(3)->setEnabled(true); texts_.at(3)->setEnabled(true);
} }
@@ -121,63 +131,59 @@ void Intro::updateScene1() {
if (texts_.at(3)->hasFinished()) { if (texts_.at(3)->hasFinished()) {
texts_.at(3)->setEnabled(false); texts_.at(3)->setEnabled(false);
scene_++; scene_++;
shake_done_ = false;
} }
} }
void Intro::updateScene2() { void Intro::updateScene2() {
// Tercera imagen junto con primer texto - GRITO // Tercera imagen - GRITO: tarjeta y texto a la vez
if (!texts_.at(4)->hasFinished()) { if (!texts_.at(4)->hasFinished()) {
enableCardAndShadow(2);
texts_.at(4)->setEnabled(true); texts_.at(4)->setEnabled(true);
} }
// Fin de la tercera escena // Fin de la tercera escena
if (card_sprites_.at(2)->hasFinished() && texts_.at(4)->hasFinished()) { if (card_sprites_.at(2)->hasLanded() && texts_.at(4)->hasFinished()) {
texts_.at(4)->setEnabled(false); texts_.at(4)->setEnabled(false);
scene_++; scene_++;
shake_done_ = false;
} }
} }
void Intro::updateScene3() { void Intro::updateScene3() {
// Cuarta imagen junto con primer texto - Reflexión // Cuarta imagen - Reflexión
enableCardAndShadow(3);
if (!texts_.at(5)->hasFinished()) { if (!texts_.at(5)->hasFinished()) {
texts_.at(5)->setEnabled(true); texts_.at(5)->setEnabled(true);
} }
// Segundo texto de la cuarta imagen // Segundo texto
if (texts_.at(5)->hasFinished() && !texts_.at(6)->hasFinished()) { if (texts_.at(5)->hasFinished() && !texts_.at(6)->hasFinished()) {
switchText(5, 6); switchText(5, 6);
} }
// Fin de la cuarta escena // Fin de la cuarta escena
if (card_sprites_.at(3)->hasFinished() && texts_.at(6)->hasFinished()) { if (card_sprites_.at(3)->hasLanded() && texts_.at(6)->hasFinished()) {
texts_.at(6)->setEnabled(false); texts_.at(6)->setEnabled(false);
scene_++; scene_++;
shake_done_ = false;
} }
} }
void Intro::updateScene4() { void Intro::updateScene4() {
// Quinta imagen - Patada // Quinta imagen - Patada
enableCardAndShadow(4);
// Primer texto de la quinta imagen
if (!texts_.at(7)->hasFinished()) { if (!texts_.at(7)->hasFinished()) {
texts_.at(7)->setEnabled(true); texts_.at(7)->setEnabled(true);
} }
// Fin de la quinta escena // Fin de la quinta escena
if (card_sprites_.at(4)->hasFinished() && texts_.at(7)->hasFinished()) { if (card_sprites_.at(4)->hasLanded() && texts_.at(7)->hasFinished()) {
texts_.at(7)->setEnabled(false); texts_.at(7)->setEnabled(false);
scene_++; scene_++;
shake_done_ = false;
} }
} }
void Intro::updateScene5() { void Intro::updateScene5() {
// Sexta imagen junto con texto - Globos de café // Sexta imagen - Globos de café
enableCardAndShadow(5);
if (!texts_.at(8)->hasFinished()) { if (!texts_.at(8)->hasFinished()) {
texts_.at(8)->setEnabled(true); texts_.at(8)->setEnabled(true);
} }
@@ -187,19 +193,14 @@ void Intro::updateScene5() {
texts_.at(8)->setEnabled(false); texts_.at(8)->setEnabled(false);
} }
// Acaba la ultima imagen // Última tarjeta: sale "como si se la llevara el viento" y transición a POST
if (card_sprites_.at(5)->hasFinished() && texts_.at(8)->hasFinished()) { if (card_sprites_.at(5)->hasLanded() && texts_.at(8)->hasFinished()) {
card_sprites_.at(5)->startExit();
state_ = State::POST; state_ = State::POST;
state_start_time_ = SDL_GetTicks() / 1000.0F; state_start_time_ = SDL_GetTicks() / 1000.0F;
} }
} }
// Helper methods to reduce code duplication
void Intro::enableCardAndShadow(int index) {
card_sprites_.at(index)->enable();
shadow_sprites_.at(index)->enable();
}
void Intro::switchText(int from_index, int to_index) { void Intro::switchText(int from_index, int to_index) {
texts_.at(from_index)->setEnabled(false); texts_.at(from_index)->setEnabled(false);
texts_.at(to_index)->setEnabled(true); texts_.at(to_index)->setEnabled(true);
@@ -215,12 +216,18 @@ void Intro::update(float delta_time) {
switch (state_) { switch (state_) {
case State::SCENES: case State::SCENES:
// Pausa inicial antes de empezar
if (initial_elapsed_ < INITIAL_DELAY_S) {
initial_elapsed_ += delta_time;
break;
}
updateSprites(delta_time); updateSprites(delta_time);
updateTexts(delta_time); updateTexts(delta_time);
updateScenes(); updateScenes();
break; break;
case State::POST: case State::POST:
updateSprites(delta_time); // La última tarjeta puede estar saliendo durante POST
updatePostState(); updatePostState();
break; break;
} }
@@ -243,6 +250,7 @@ void Intro::render() {
break; break;
} }
case State::POST: case State::POST:
renderSprites(); // La última tarjeta puede estar saliendo
break; break;
} }
@@ -272,7 +280,7 @@ void Intro::run() {
} }
} }
// Inicializa las imagens // Inicializa las tarjetas
void Intro::initSprites() { void Intro::initSprites() {
// Listado de imagenes a usar // Listado de imagenes a usar
const std::array<std::string, 6> TEXTURE_LIST = { const std::array<std::string, 6> TEXTURE_LIST = {
@@ -291,24 +299,21 @@ void Intro::initSprites() {
const float CARD_WIDTH = texture->getWidth() + (BORDER * 2); const float CARD_WIDTH = texture->getWidth() + (BORDER * 2);
const float CARD_HEIGHT = texture->getHeight() + (BORDER * 2); const float CARD_HEIGHT = texture->getHeight() + (BORDER * 2);
// Crea las texturas para las tarjetas // Crea las texturas para las tarjetas (imagen con marco)
std::vector<std::shared_ptr<Texture>> card_textures; std::vector<std::shared_ptr<Texture>> card_textures;
for (int i = 0; i < TOTAL_SPRITES; ++i) { for (int i = 0; i < TOTAL_SPRITES; ++i) {
// Crea la textura
auto card_texture = std::make_unique<Texture>(Screen::get()->getRenderer()); auto card_texture = std::make_unique<Texture>(Screen::get()->getRenderer());
card_texture->createBlank(CARD_WIDTH, CARD_HEIGHT, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET); card_texture->createBlank(CARD_WIDTH, CARD_HEIGHT, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET);
card_texture->setBlendMode(SDL_BLENDMODE_BLEND); card_texture->setBlendMode(SDL_BLENDMODE_BLEND);
// Apuntamos el renderizador a la textura
auto* temp = SDL_GetRenderTarget(Screen::get()->getRenderer()); auto* temp = SDL_GetRenderTarget(Screen::get()->getRenderer());
card_texture->setAsRenderTarget(Screen::get()->getRenderer()); card_texture->setAsRenderTarget(Screen::get()->getRenderer());
// Limpia la textura
SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 0); SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 0);
SDL_RenderClear(Screen::get()->getRenderer()); SDL_RenderClear(Screen::get()->getRenderer());
// Pone color en el marco de la textura // Marco de la tarjeta
auto color = param.intro.card_color; auto color = param.intro.card_color;
SDL_SetRenderDrawColor(Screen::get()->getRenderer(), color.r, color.g, color.b, color.a); SDL_SetRenderDrawColor(Screen::get()->getRenderer(), color.r, color.g, color.b, color.a);
SDL_FRect rect1 = {.x = 1, .y = 0, .w = CARD_WIDTH - 2, .h = CARD_HEIGHT}; SDL_FRect rect1 = {.x = 1, .y = 0, .w = CARD_WIDTH - 2, .h = CARD_HEIGHT};
@@ -316,85 +321,108 @@ void Intro::initSprites() {
SDL_RenderRect(Screen::get()->getRenderer(), &rect1); SDL_RenderRect(Screen::get()->getRenderer(), &rect1);
SDL_RenderRect(Screen::get()->getRenderer(), &rect2); SDL_RenderRect(Screen::get()->getRenderer(), &rect2);
// Copia la textura con la imagen dentro del marco // Imagen dentro del marco
SDL_FRect dest = {.x = BORDER, .y = BORDER, .w = CARD_WIDTH - (BORDER * 2), .h = CARD_HEIGHT - (BORDER * 2)}; SDL_FRect dest = {.x = BORDER, .y = BORDER, .w = CARD_WIDTH - (BORDER * 2), .h = CARD_HEIGHT - (BORDER * 2)};
SDL_RenderTexture(Screen::get()->getRenderer(), Resource::get()->getTexture(TEXTURE_LIST.at(i))->getSDLTexture(), nullptr, &dest); SDL_RenderTexture(Screen::get()->getRenderer(), Resource::get()->getTexture(TEXTURE_LIST.at(i))->getSDLTexture(), nullptr, &dest);
// Deja el renderizador como estaba y añade la textura a la lista
SDL_SetRenderTarget(Screen::get()->getRenderer(), temp); SDL_SetRenderTarget(Screen::get()->getRenderer(), temp);
card_textures.push_back(std::move(card_texture)); card_textures.push_back(std::move(card_texture));
} }
// Inicializa los sprites para las tarjetas // Crea la textura de sombra (compartida entre todas las tarjetas)
for (int i = 0; i < TOTAL_SPRITES; ++i) {
auto sprite = std::make_unique<PathSprite>(card_textures.at(i));
sprite->setWidth(CARD_WIDTH);
sprite->setHeight(CARD_HEIGHT);
sprite->setSpriteClip(0, 0, CARD_WIDTH, CARD_HEIGHT);
card_sprites_.push_back(std::move(sprite));
}
const float X_DEST = param.game.game_area.center_x - (CARD_WIDTH / 2);
const float Y_DEST = param.game.game_area.first_quarter_y - (CARD_HEIGHT / 4);
card_sprites_.at(0)->addPath(-CARD_WIDTH - CARD_OFFSET_MARGIN, X_DEST, PathType::HORIZONTAL, Y_DEST, CARD_ANIM_DURATION_NORMAL, easeInOutExpo, 0.0F);
card_sprites_.at(1)->addPath(param.game.width, X_DEST, PathType::HORIZONTAL, Y_DEST, CARD_ANIM_DURATION_NORMAL, easeOutBounce, 0.0F);
card_sprites_.at(2)->addPath(-CARD_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, CARD_ANIM_DURATION_FAST, easeOutQuint, 0.0F);
card_sprites_.at(3)->addPath(param.game.height, Y_DEST, PathType::VERTICAL, X_DEST, CARD_ANIM_DURATION_VERY_SLOW, easeInOutExpo, 0.0F);
card_sprites_.at(4)->addPath(-CARD_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, CARD_ANIM_DURATION_MEDIUM, easeOutElastic, 0.0F);
card_sprites_.at(5)->addPath(-CARD_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, CARD_ANIM_DURATION_SLOW, easeOutQuad, CARD_ANIM_DELAY_LONG_S);
card_sprites_.at(5)->addPath(X_DEST, -CARD_WIDTH, PathType::HORIZONTAL, Y_DEST, CARD_ANIM_DURATION_SHORT, easeInElastic, 0.0F);
// Constantes
const float DESP = SHADOW_OFFSET;
const float SHADOW_SPRITE_WIDTH = CARD_WIDTH;
const float SHADOW_SPRITE_HEIGHT = CARD_HEIGHT;
// Crea la textura para las sombras de las tarjetas
auto shadow_texture = std::make_shared<Texture>(Screen::get()->getRenderer()); auto shadow_texture = std::make_shared<Texture>(Screen::get()->getRenderer());
shadow_texture->createBlank(SHADOW_SPRITE_WIDTH, SHADOW_SPRITE_HEIGHT, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET); shadow_texture->createBlank(CARD_WIDTH, CARD_HEIGHT, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET);
shadow_texture->setBlendMode(SDL_BLENDMODE_BLEND); shadow_texture->setBlendMode(SDL_BLENDMODE_BLEND);
// Apuntamos el renderizador a la textura
auto* temp = SDL_GetRenderTarget(Screen::get()->getRenderer()); auto* temp = SDL_GetRenderTarget(Screen::get()->getRenderer());
shadow_texture->setAsRenderTarget(Screen::get()->getRenderer()); shadow_texture->setAsRenderTarget(Screen::get()->getRenderer());
// Limpia la textura
SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 0); SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 0);
SDL_RenderClear(Screen::get()->getRenderer()); SDL_RenderClear(Screen::get()->getRenderer());
// Dibuja la sombra sobre la textura
auto shadow_color = param.intro.shadow_color; auto shadow_color = param.intro.shadow_color;
SDL_SetRenderDrawColor(Screen::get()->getRenderer(), shadow_color.r, shadow_color.g, shadow_color.b, Color::MAX_ALPHA_VALUE); SDL_SetRenderDrawColor(Screen::get()->getRenderer(), shadow_color.r, shadow_color.g, shadow_color.b, Color::MAX_ALPHA_VALUE);
SDL_FRect rect1 = {.x = 1, .y = 0, .w = SHADOW_SPRITE_WIDTH - 2, .h = SHADOW_SPRITE_HEIGHT}; SDL_FRect shadow_rect1 = {.x = 1, .y = 0, .w = CARD_WIDTH - 2, .h = CARD_HEIGHT};
SDL_FRect rect2 = {.x = 0, .y = 1, .w = SHADOW_SPRITE_WIDTH, .h = SHADOW_SPRITE_HEIGHT - 2}; SDL_FRect shadow_rect2 = {.x = 0, .y = 1, .w = CARD_WIDTH, .h = CARD_HEIGHT - 2};
SDL_RenderFillRect(Screen::get()->getRenderer(), &rect1); SDL_RenderFillRect(Screen::get()->getRenderer(), &shadow_rect1);
SDL_RenderFillRect(Screen::get()->getRenderer(), &rect2); SDL_RenderFillRect(Screen::get()->getRenderer(), &shadow_rect2);
// Deja el renderizador como estaba y añade la textura a la lista
SDL_SetRenderTarget(Screen::get()->getRenderer(), temp); SDL_SetRenderTarget(Screen::get()->getRenderer(), temp);
shadow_texture->setAlpha(shadow_color.a);
// Inicializa los sprites para la sombras usando la texturas con la sombra // Posición de aterrizaje (centro de la zona de juego)
const float X_DEST = param.game.game_area.center_x - (CARD_WIDTH / 2);
const float Y_DEST = param.game.game_area.first_quarter_y - (CARD_HEIGHT / 4);
// Configuración por tarjeta: posición de entrada, ángulo, salida
// Cada tarjeta viene de un borde diferente (gente alrededor de una mesa lanzando cartas al centro)
struct CardConfig {
float entry_x; // Posición inicial X
float entry_y; // Posición inicial Y
double entry_angle; // Ángulo de entrada
float exit_vx; // Velocidad de salida X
float exit_vy; // Velocidad de salida Y
float exit_ax; // Aceleración de salida X
float exit_ay; // Aceleración de salida Y
double exit_rotation; // Velocidad de rotación de salida
};
const float W = param.game.width;
const float H = param.game.height;
const float S = CARD_EXIT_SPEED;
const float A = CARD_EXIT_ACCEL;
const double R = CARD_EXIT_ROTATION;
const CardConfig CARD_CONFIGS[] = {
// 0: Entra desde la izquierda. La 1 entra desde la derecha → sale empujada hacia la izquierda
{-CARD_WIDTH, Y_DEST - 20.0F, CARD_ANGLE_0, -S, S * 0.1F, -A, 0.0F, -R},
// 1: Entra desde la derecha. La 2 entra desde arriba → sale empujada hacia abajo
{W + CARD_WIDTH, Y_DEST + 15.0F, CARD_ANGLE_1, S * 0.15F, S, 0.0F, A, R * 1.1},
// 2: Entra desde arriba. La 3 entra desde abajo → sale empujada hacia arriba
{X_DEST + 30.0F, -CARD_HEIGHT, CARD_ANGLE_2, -S * 0.15F, -S, 0.0F, -A, -R * 0.9},
// 3: Entra desde abajo. La 4 entra desde arriba-izquierda → sale empujada hacia abajo-derecha
{X_DEST - 25.0F, H + CARD_HEIGHT, CARD_ANGLE_3, S * 0.8F, S * 0.6F, A * 0.5F, A * 0.4F, R},
// 4: Entra desde arriba-izquierda. La 5 entra desde derecha-abajo → sale empujada hacia arriba-izquierda
{-CARD_WIDTH * 0.5F, -CARD_HEIGHT, CARD_ANGLE_4, -S * 0.7F, -S * 0.5F, -A * 0.5F, -A * 0.3F, -R * 1.2},
// 5: Entra desde la derecha-abajo. Última: sale hacia la izquierda suave (viento)
{W + CARD_WIDTH, H * 0.6F, CARD_ANGLE_5, -S * 0.6F, -S * 0.1F, -A * 0.5F, 0.0F, -R * 0.7},
};
// Inicializa los CardSprites
for (int i = 0; i < TOTAL_SPRITES; ++i) { for (int i = 0; i < TOTAL_SPRITES; ++i) {
auto shadow_color = param.intro.shadow_color; auto card = std::make_unique<CardSprite>(card_textures.at(i));
auto sprite = std::make_unique<PathSprite>(shadow_texture); card->setWidth(CARD_WIDTH);
sprite->setWidth(SHADOW_SPRITE_WIDTH); card->setHeight(CARD_HEIGHT);
sprite->setHeight(SHADOW_SPRITE_HEIGHT); card->setSpriteClip(0, 0, CARD_WIDTH, CARD_HEIGHT);
sprite->setSpriteClip(0, 0, SHADOW_SPRITE_WIDTH, SHADOW_SPRITE_HEIGHT);
sprite->getTexture()->setAlpha(shadow_color.a); const auto& cfg = CARD_CONFIGS[i];
shadow_sprites_.push_back(std::move(sprite));
// Posición de aterrizaje (centro)
card->setLandingPosition(X_DEST, Y_DEST);
// Posición de entrada (borde de pantalla)
card->setEntryPosition(cfg.entry_x, cfg.entry_y);
// Parámetros de entrada: zoom, ángulo, duración, easing
card->setEntryParams(CARD_START_ZOOM, cfg.entry_angle, CARD_ENTRY_DURATION_S, easeOutBounce);
// Parámetros de salida
card->setExitParams(cfg.exit_vx, cfg.exit_vy, cfg.exit_ax, cfg.exit_ay, cfg.exit_rotation);
// Sombra
card->setShadowTexture(shadow_texture);
card->setShadowOffset(SHADOW_OFFSET, SHADOW_OFFSET);
// Límites de pantalla
card->setScreenBounds(param.game.width, param.game.height);
// Última tarjeta: gana algo de altura al salir (se la lleva el viento)
if (i == TOTAL_SPRITES - 1) {
card->setExitLift(1.2F, 0.15F); // Hasta zoom 1.2, a 0.15/s
}
card_sprites_.push_back(std::move(card));
} }
const float S_X_DEST = X_DEST + DESP;
const float S_Y_DEST = Y_DEST + DESP;
shadow_sprites_.at(0)->addPath(param.game.height + CARD_OFFSET_MARGIN, S_Y_DEST, PathType::VERTICAL, S_X_DEST, CARD_ANIM_DURATION_NORMAL, easeInOutExpo, 0.0F);
shadow_sprites_.at(1)->addPath(-SHADOW_SPRITE_HEIGHT, S_Y_DEST, PathType::VERTICAL, S_X_DEST, CARD_ANIM_DURATION_NORMAL, easeOutBounce, 0.0F);
shadow_sprites_.at(2)->addPath(-SHADOW_SPRITE_WIDTH, S_X_DEST, PathType::HORIZONTAL, S_Y_DEST, CARD_ANIM_DURATION_FAST, easeOutQuint, 0.0F);
shadow_sprites_.at(3)->addPath(-SHADOW_SPRITE_HEIGHT, S_Y_DEST, PathType::VERTICAL, S_X_DEST, CARD_ANIM_DURATION_VERY_SLOW, easeInOutExpo, 0.0F);
shadow_sprites_.at(4)->addPath(param.game.height, S_Y_DEST, PathType::VERTICAL, S_X_DEST, CARD_ANIM_DURATION_MEDIUM, easeOutElastic, 0.0F);
shadow_sprites_.at(5)->addPath(param.game.width, S_X_DEST, PathType::HORIZONTAL, S_Y_DEST, CARD_ANIM_DURATION_SLOW, easeOutQuad, CARD_ANIM_DELAY_LONG_S);
shadow_sprites_.at(5)->addPath(S_X_DEST, param.game.width, PathType::HORIZONTAL, S_Y_DEST, CARD_ANIM_DURATION_SHORT, easeInElastic, 0.0F);
} }
// Inicializa los textos // Inicializa los textos
@@ -420,7 +448,7 @@ void Intro::initTexts() {
// Fins que un desaprensiu... // Fins que un desaprensiu...
texts_.at(2)->setCaption(Lang::getText("[INTRO] 3")); texts_.at(2)->setCaption(Lang::getText("[INTRO] 3"));
texts_.at(2)->setSpeedS(TEXT_SPEED_FAST); texts_.at(2)->setSpeedS(TEXT_SPEED_SLOW);
// HEY! ME ANE A FERME UN CORTAET... // HEY! ME ANE A FERME UN CORTAET...
texts_.at(3)->setCaption(Lang::getText("[INTRO] 4")); texts_.at(3)->setCaption(Lang::getText("[INTRO] 4"));
@@ -428,23 +456,23 @@ void Intro::initTexts() {
// UAAAAAAAAAAAAA!!! // UAAAAAAAAAAAAA!!!
texts_.at(4)->setCaption(Lang::getText("[INTRO] 5")); texts_.at(4)->setCaption(Lang::getText("[INTRO] 5"));
texts_.at(4)->setSpeedS(TEXT_SPEED_VERY_SLOW); texts_.at(4)->setSpeedS(TEXT_SPEED_ULTRA_FAST);
// Espera un moment... // Espera un moment...
texts_.at(5)->setCaption(Lang::getText("[INTRO] 6")); texts_.at(5)->setCaption(Lang::getText("[INTRO] 6"));
texts_.at(5)->setSpeedS(TEXT_SPEED_VERY_FAST); texts_.at(5)->setSpeedS(TEXT_SPEED_VERY_SLOW);
// Si resulta que no tinc solt! // Si resulta que no tinc solt!
texts_.at(6)->setCaption(Lang::getText("[INTRO] 7")); texts_.at(6)->setCaption(Lang::getText("[INTRO] 7"));
texts_.at(6)->setSpeedS(TEXT_SPEED_SLOW); texts_.at(6)->setSpeedS(TEXT_SPEED_VERY_FAST);
// MERDA DE MAQUINA! // MERDA DE MAQUINA!
texts_.at(7)->setCaption(Lang::getText("[INTRO] 8")); texts_.at(7)->setCaption(Lang::getText("[INTRO] 8"));
texts_.at(7)->setSpeedS(TEXT_SPEED_MEDIUM_SLOW); texts_.at(7)->setSpeedS(TEXT_SPEED_FAST);
// Blop... blop... blop... // Blop... blop... blop...
texts_.at(8)->setCaption(Lang::getText("[INTRO] 9")); texts_.at(8)->setCaption(Lang::getText("[INTRO] 9"));
texts_.at(8)->setSpeedS(TEXT_SPEED_ULTRA_FAST); texts_.at(8)->setSpeedS(TEXT_SPEED_ULTRA_SLOW);
for (auto& text : texts_) { for (auto& text : texts_) {
text->center(param.game.game_area.center_x); text->center(param.game.game_area.center_x);
@@ -456,10 +484,6 @@ void Intro::updateSprites(float delta_time) {
for (auto& sprite : card_sprites_) { for (auto& sprite : card_sprites_) {
sprite->update(delta_time); sprite->update(delta_time);
} }
for (auto& sprite : shadow_sprites_) {
sprite->update(delta_time);
}
} }
// Actualiza los textos // Actualiza los textos
@@ -469,10 +493,11 @@ void Intro::updateTexts(float delta_time) {
} }
} }
// Dibuja los sprites // Dibuja los sprites (todas las tarjetas activas, para que convivan la saliente y la entrante)
void Intro::renderSprites() { void Intro::renderSprites() {
shadow_sprites_.at(scene_)->render(); for (auto& card : card_sprites_) {
card_sprites_.at(scene_)->render(); card->render();
}
} }
// Dibuja los textos // Dibuja los textos
@@ -525,4 +550,4 @@ void Intro::renderTextRect() {
static SDL_FRect rect_ = {.x = 0.0F, .y = param.game.height - param.intro.text_distance_from_bottom - HEIGHT, .w = param.game.width, .h = HEIGHT * 3}; static SDL_FRect rect_ = {.x = 0.0F, .y = param.game.height - param.intro.text_distance_from_bottom - HEIGHT, .w = param.game.width, .h = HEIGHT * 3};
SDL_SetRenderDrawColor(Screen::get()->getRenderer(), param.intro.shadow_color.r, param.intro.shadow_color.g, param.intro.shadow_color.b, param.intro.shadow_color.a); SDL_SetRenderDrawColor(Screen::get()->getRenderer(), param.intro.shadow_color.r, param.intro.shadow_color.g, param.intro.shadow_color.b, param.intro.shadow_color.a);
SDL_RenderFillRect(Screen::get()->getRenderer(), &rect_); SDL_RenderFillRect(Screen::get()->getRenderer(), &rect_);
} }

View File

@@ -5,9 +5,9 @@
#include <memory> // Para unique_ptr #include <memory> // Para unique_ptr
#include <vector> // Para vector #include <vector> // Para vector
#include "card_sprite.hpp" // Para CardSprite
#include "color.hpp" // Para Color #include "color.hpp" // Para Color
#include "param.hpp" // Para Param, ParamIntro, param #include "param.hpp" // Para Param, ParamIntro, param
#include "path_sprite.hpp" // Para PathSprite
#include "tiled_bg.hpp" // Para TiledBG #include "tiled_bg.hpp" // Para TiledBG
#include "writer.hpp" // Para Writer #include "writer.hpp" // Para Writer
@@ -18,14 +18,11 @@
// //
// Funcionalidades principales: // Funcionalidades principales:
// • Sistema de escenas secuencial: 6 escenas con transiciones automáticas // • Sistema de escenas secuencial: 6 escenas con transiciones automáticas
// • Animaciones de tarjetas: efectos de entrada con diferentes tipos de easing // • Animaciones de tarjetas: efecto de lanzamiento sobre mesa con zoom, rotación y rebote
// • Texto narrativo: velocidades de escritura configurables por escena // • Texto narrativo: velocidades de escritura configurables por escena
// • Efectos visuales: sombras, bordes y transiciones de color // • Efectos visuales: sombras, bordes y transiciones de color
// • Audio sincronizado: música de fondo durante toda la secuencia // • Audio sincronizado: música de fondo durante toda la secuencia
// • Estado POST: transición suave hacia el menú principal // • Estado POST: transición suave hacia el menú principal
//
// Todas las duraciones y velocidades están configuradas mediante constantes
// para facilitar el ajuste fino de la experiencia cinematográfica.
class Intro { class Intro {
public: public:
@@ -38,9 +35,14 @@ class Intro {
private: private:
// --- Constantes de tiempo (en segundos) --- // --- Constantes de tiempo (en segundos) ---
static constexpr float TEXT_DISPLAY_DURATION_S = 3.0F; // Duración de visualización de texto (180 frames a 60fps) static constexpr float TEXT_DISPLAY_DURATION_S = 3.0F; // Duración de visualización de texto
static constexpr float POST_BG_STOP_DELAY_S = 1.0F; // Retraso antes de detener el fondo static constexpr float POST_BG_STOP_DELAY_S = 1.0F; // Retraso antes de detener el fondo
static constexpr float POST_END_DELAY_S = 1.0F; // Retraso antes de finalizar intro static constexpr float POST_END_DELAY_S = 1.0F; // Retraso antes de finalizar intro
static constexpr float INITIAL_DELAY_S = 2.0F; // Pausa antes de empezar las escenas
// --- Constantes de sonido ---
static constexpr const char* SFX_CARD_THROW = "service_menu_select.wav"; // Sonido al lanzar una tarjeta
static constexpr const char* SFX_CARD_IMPACT = "player_collision.wav"; // Sonido al impactar en la mesa
// --- Constantes de layout --- // --- Constantes de layout ---
static constexpr float CARD_BORDER_SIZE = 2.0F; // Tamaño del borde de tarjetas static constexpr float CARD_BORDER_SIZE = 2.0F; // Tamaño del borde de tarjetas
@@ -48,25 +50,29 @@ class Intro {
static constexpr float TILED_BG_SPEED = 18.0F; // Velocidad del fondo mosaico (pixels/segundo) static constexpr float TILED_BG_SPEED = 18.0F; // Velocidad del fondo mosaico (pixels/segundo)
static constexpr int TEXT_KERNING = -2; // Espaciado entre caracteres static constexpr int TEXT_KERNING = -2; // Espaciado entre caracteres
// --- Constantes de velocidades de texto (segundos entre caracteres) --- // --- Constantes de velocidades de texto (segundos entre caracteres, menor = más rápido) ---
static constexpr float TEXT_SPEED_NORMAL = 0.133F; // Velocidad normal (8 frames * 16.67ms = 133ms) static constexpr float TEXT_SPEED_ULTRA_FAST = 0.0167F; // Ultra rápida (1 frame a 60fps)
static constexpr float TEXT_SPEED_FAST = 0.2F; // Velocidad rápida (12 frames * 16.67ms = 200ms) static constexpr float TEXT_SPEED_VERY_FAST = 0.033F; // Muy rápida (2 frames a 60fps)
static constexpr float TEXT_SPEED_VERY_SLOW = 0.0167F; // Velocidad muy lenta (1 frame * 16.67ms = 16.7ms) static constexpr float TEXT_SPEED_FAST = 0.05F; // Rápida (3 frames a 60fps)
static constexpr float TEXT_SPEED_VERY_FAST = 0.267F; // Velocidad muy rápida (16 frames * 16.67ms = 267ms) static constexpr float TEXT_SPEED_NORMAL = 0.133F; // Normal (8 frames a 60fps)
static constexpr float TEXT_SPEED_SLOW = 0.033F; // Velocidad lenta (2 frames * 16.67ms = 33ms) static constexpr float TEXT_SPEED_SLOW = 0.2F; // Lenta (12 frames a 60fps)
static constexpr float TEXT_SPEED_MEDIUM_SLOW = 0.05F; // Velocidad medio-lenta (3 frames * 16.67ms = 50ms) static constexpr float TEXT_SPEED_VERY_SLOW = 0.267F; // Muy lenta (16 frames a 60fps)
static constexpr float TEXT_SPEED_ULTRA_FAST = 0.333F; // Velocidad ultra rápida (20 frames * 16.67ms = 333ms) static constexpr float TEXT_SPEED_ULTRA_SLOW = 0.333F; // Ultra lenta (20 frames a 60fps)
// --- Constantes de animaciones de tarjetas (duraciones en segundos) --- // --- Constantes de animaciones de tarjetas ---
static constexpr float CARD_ANIM_DURATION_NORMAL = 100.0F / 60.0F; // ≈ 1.6667 s static constexpr float CARD_ENTRY_DURATION_S = 1.5F; // Duración de la animación de entrada
static constexpr float CARD_ANIM_DURATION_FAST = 40.0F / 60.0F; // ≈ 0.6667 s static constexpr float CARD_START_ZOOM = 1.8F; // Zoom inicial (como si estuviera cerca)
static constexpr float CARD_ANIM_DURATION_MEDIUM = 70.0F / 60.0F; // ≈ 1.1667 s static constexpr float CARD_EXIT_SPEED = 400.0F; // Velocidad base de salida (pixels/s)
static constexpr float CARD_ANIM_DURATION_SHORT = 80.0F / 60.0F; // ≈ 1.3333 s static constexpr float CARD_EXIT_ACCEL = 200.0F; // Aceleración de salida (pixels/s²)
static constexpr float CARD_ANIM_DURATION_SLOW = 250.0F / 60.0F; // ≈ 4.1667 s static constexpr double CARD_EXIT_ROTATION = 450.0; // Velocidad de rotación en salida (grados/s)
static constexpr float CARD_ANIM_DURATION_VERY_SLOW = 300.0F / 60.0F; // ≈ 5.0000 s
static constexpr float CARD_ANIM_DELAY_LONG_S = 7.5F; // Retraso largo antes de animación // --- Ángulos iniciales de entrada por tarjeta (grados) ---
static constexpr float CARD_OFFSET_MARGIN = 10.0F; // Margen fuera de pantalla static constexpr double CARD_ANGLE_0 = 12.0;
static constexpr double CARD_ANGLE_1 = -15.0;
static constexpr double CARD_ANGLE_2 = 8.0;
static constexpr double CARD_ANGLE_3 = -10.0;
static constexpr double CARD_ANGLE_4 = 18.0;
static constexpr double CARD_ANGLE_5 = -7.0;
// --- Estados internos --- // --- Estados internos ---
enum class State { enum class State {
@@ -80,18 +86,19 @@ class Intro {
}; };
// --- Objetos --- // --- Objetos ---
std::vector<std::unique_ptr<PathSprite>> card_sprites_; // Vector con los sprites inteligentes para los dibujos de la intro std::vector<std::unique_ptr<CardSprite>> card_sprites_; // Tarjetas animadas con sombra integrada
std::vector<std::unique_ptr<PathSprite>> shadow_sprites_; // Vector con los sprites inteligentes para las sombras std::vector<std::unique_ptr<Writer>> texts_; // Textos de la intro
std::vector<std::unique_ptr<Writer>> texts_; // Textos de la intro std::unique_ptr<TiledBG> tiled_bg_; // Fondo en mosaico
std::unique_ptr<TiledBG> tiled_bg_; // Fondo en mosaico
// --- Variables --- // --- Variables ---
Uint64 last_time_ = 0; // Último timestamp para calcular delta-time Uint64 last_time_ = 0; // Último timestamp para calcular delta-time
int scene_ = 0; // Indica qué escena está activa int scene_ = 0; // Indica qué escena está activa
State state_ = State::SCENES; // Estado principal de la intro State state_ = State::SCENES; // Estado principal de la intro
PostState post_state_ = PostState::STOP_BG; // Estado POST PostState post_state_ = PostState::STOP_BG; // Estado POST
float state_start_time_; // Tiempo de inicio del estado actual (segundos) float state_start_time_ = 0.0F; // Tiempo de inicio del estado actual (segundos)
Color bg_color_ = param.intro.bg_color; // Color de fondo Color bg_color_ = param.intro.bg_color; // Color de fondo
bool shake_done_ = false; // Evita shake repetido en la misma escena
float initial_elapsed_ = 0.0F; // Tiempo acumulado antes de empezar
// --- Métodos internos --- // --- Métodos internos ---
void update(float delta_time); // Actualiza las variables del objeto void update(float delta_time); // Actualiza las variables del objeto
@@ -99,13 +106,13 @@ class Intro {
static void checkInput(); // Comprueba las entradas static void checkInput(); // Comprueba las entradas
static void checkEvents(); // Comprueba los eventos static void checkEvents(); // Comprueba los eventos
void updateScenes(); // Actualiza las escenas de la intro void updateScenes(); // Actualiza las escenas de la intro
void initSprites(); // Inicializa las imágenes void initSprites(); // Inicializa las tarjetas
void initTexts(); // Inicializa los textos void initTexts(); // Inicializa los textos
void updateSprites(float delta_time); // Actualiza los sprites void updateSprites(float delta_time); // Actualiza los sprites
void updateTexts(float delta_time); // Actualiza los textos void updateTexts(float delta_time); // Actualiza los textos
void renderSprites(); // Dibuja los sprites void renderSprites(); // Dibuja los sprites
void renderTexts(); // Dibuja los textos void renderTexts(); // Dibuja los textos
static void renderTextRect(); // Dibuja el rectangulo de fondo del texto; static void renderTextRect(); // Dibuja el rectángulo de fondo del texto
void updatePostState(); // Actualiza el estado POST void updatePostState(); // Actualiza el estado POST
auto calculateDeltaTime() -> float; // Calcula el tiempo transcurrido desde el último frame auto calculateDeltaTime() -> float; // Calcula el tiempo transcurrido desde el último frame
@@ -117,7 +124,6 @@ class Intro {
void updateScene4(); void updateScene4();
void updateScene5(); void updateScene5();
// --- Métodos auxiliares para reducir duplicación de código --- // --- Métodos auxiliares ---
void enableCardAndShadow(int index);
void switchText(int from_index, int to_index); void switchText(int from_index, int to_index);
}; };

View File

@@ -30,11 +30,6 @@
class Texture; class Texture;
#ifdef _DEBUG
#include <iomanip> // Para operator<<, setfill, setw
#include <iostream> // Para basic_ostream, basic_ostream::operator<<, operator<<, cout, hex
#endif
// Constructor // Constructor
Title::Title() Title::Title()
: text_(Resource::get()->getText("smb2_grad")), : text_(Resource::get()->getText("smb2_grad")),
@@ -43,12 +38,7 @@ Title::Title()
game_logo_(std::make_unique<GameLogo>(param.game.game_area.center_x, param.title.title_c_c_position)), game_logo_(std::make_unique<GameLogo>(param.game.game_area.center_x, param.title.title_c_c_position)),
mini_logo_sprite_(std::make_unique<Sprite>(Resource::get()->getTexture("logo_jailgames_mini.png"))), mini_logo_sprite_(std::make_unique<Sprite>(Resource::get()->getTexture("logo_jailgames_mini.png"))),
state_(State::LOGO_ANIMATING), state_(State::LOGO_ANIMATING),
num_controllers_(Input::get()->getNumGamepads()) num_controllers_(Input::get()->getNumGamepads()) {
#ifdef _DEBUG
,
debug_color_(param.title.bg_color)
#endif
{
// Configura objetos // Configura objetos
tiled_bg_->setColor(param.title.bg_color); tiled_bg_->setColor(param.title.bg_color);
tiled_bg_->setSpeed(0.0F); tiled_bg_->setSpeed(0.0F);
@@ -132,88 +122,9 @@ void Title::checkEvents() {
} }
} }
void Title::handleKeyDownEvent(const SDL_Event& event) { void Title::handleKeyDownEvent(const SDL_Event& /*event*/) {
#ifdef _DEBUG
bool is_repeat = static_cast<int>(event.key.repeat) == 1;
if (is_repeat) {
handleDebugColorKeys(event.key.key);
}
#endif
} }
#ifdef _DEBUG
void Title::handleDebugColorKeys(SDL_Keycode key) {
adjustColorComponent(key, debug_color_);
counter_time_ = 0.0F;
tiled_bg_->setColor(debug_color_);
printColorValue(debug_color_);
}
void Title::adjustColorComponent(SDL_Keycode key, Color& color) {
switch (key) {
case SDLK_A:
incrementColorComponent(color.r);
break;
case SDLK_Z:
decrementColorComponent(color.r);
break;
case SDLK_S:
incrementColorComponent(color.g);
break;
case SDLK_X:
decrementColorComponent(color.g);
break;
case SDLK_D:
incrementColorComponent(color.b);
break;
case SDLK_C:
decrementColorComponent(color.b);
break;
case SDLK_F:
incrementAllComponents(color);
break;
case SDLK_V:
decrementAllComponents(color);
break;
default:
break;
}
}
void Title::incrementColorComponent(uint8_t& component) {
if (component < 255) {
++component;
}
}
void Title::decrementColorComponent(uint8_t& component) {
if (component > 0) {
--component;
}
}
void Title::incrementAllComponents(Color& color) {
incrementColorComponent(color.r);
incrementColorComponent(color.g);
incrementColorComponent(color.b);
}
void Title::decrementAllComponents(Color& color) {
decrementColorComponent(color.r);
decrementColorComponent(color.g);
decrementColorComponent(color.b);
}
void Title::printColorValue(const Color& color) {
std::cout << "#"
<< std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(color.r)
<< std::setw(2) << std::setfill('0') << static_cast<int>(color.g)
<< std::setw(2) << std::setfill('0') << static_cast<int>(color.b)
<< '\n';
}
#endif
// Comprueba las entradas // Comprueba las entradas
void Title::checkInput() { void Title::checkInput() {
Input::get()->update(); Input::get()->update();

View File

@@ -2,12 +2,10 @@
#include <SDL3/SDL.h> // Para SDL_Keycode, SDL_Event, Uint64 #include <SDL3/SDL.h> // Para SDL_Keycode, SDL_Event, Uint64
#include <cstdint> // Para uint8_t
#include <memory> // Para shared_ptr, unique_ptr #include <memory> // Para shared_ptr, unique_ptr
#include <string_view> // Para string_view #include <string_view> // Para string_view
#include <vector> // Para vector #include <vector> // Para vector
#include "color.hpp" // for Color
#include "player.hpp" // for Player #include "player.hpp" // for Player
#include "section.hpp" // for Options, Name (ptr only) #include "section.hpp" // for Options, Name (ptr only)
@@ -33,7 +31,6 @@ namespace Options {
// • Efectos visuales: parpadeos, transiciones y efectos de fondo // • Efectos visuales: parpadeos, transiciones y efectos de fondo
// • Gestión de controles: soporte para teclado y múltiples gamepads // • Gestión de controles: soporte para teclado y múltiples gamepads
// • Timeouts automáticos: transición automática si no hay interacción // • Timeouts automáticos: transición automática si no hay interacción
// • Debug de colores: herramientas de depuración para ajustes visuales
// //
// La clase utiliza un sistema de tiempo basado en segundos para garantizar // La clase utiliza un sistema de tiempo basado en segundos para garantizar
// comportamiento consistente independientemente del framerate. // comportamiento consistente independientemente del framerate.
@@ -101,10 +98,6 @@ class Title {
bool player1_start_pressed_ = false; // Indica si se ha pulsado el botón de empezar para el jugador 1 bool player1_start_pressed_ = false; // Indica si se ha pulsado el botón de empezar para el jugador 1
bool player2_start_pressed_ = false; // Indica si se ha pulsado el botón de empezar para el jugador 2 bool player2_start_pressed_ = false; // Indica si se ha pulsado el botón de empezar para el jugador 2
#ifdef _DEBUG
Color debug_color_; // Color para depuración en modo debug
#endif
// --- Ciclo de vida del título --- // --- Ciclo de vida del título ---
void update(float delta_time); // Actualiza las variables del objeto void update(float delta_time); // Actualiza las variables del objeto
auto calculateDeltaTime() -> float; // Calcula el tiempo transcurrido desde el último frame auto calculateDeltaTime() -> float; // Calcula el tiempo transcurrido desde el último frame
@@ -142,15 +135,4 @@ class Title {
static void swapControllers(); // Intercambia la asignación de mandos a los jugadores static void swapControllers(); // Intercambia la asignación de mandos a los jugadores
static void swapKeyboard(); // Intercambia el teclado de jugador static void swapKeyboard(); // Intercambia el teclado de jugador
static void showControllers(); // Muestra información sobre los controles y los jugadores static void showControllers(); // Muestra información sobre los controles y los jugadores
// --- Depuración (solo en modo DEBUG) ---
#ifdef _DEBUG
void handleDebugColorKeys(SDL_Keycode key); // Maneja las teclas de depuración para colores
static void adjustColorComponent(SDL_Keycode key, Color& color); // Ajusta un componente del color según la tecla
static void incrementColorComponent(uint8_t& component); // Incrementa un componente de color
static void decrementColorComponent(uint8_t& component); // Decrementa un componente de color
static void incrementAllComponents(Color& color); // Incrementa todos los componentes del color
static void decrementAllComponents(Color& color); // Decrementa todos los componentes del color
static void printColorValue(const Color& color); // Imprime el valor actual del color en consola
#endif
}; };

View File

@@ -2,12 +2,12 @@
#include <SDL3/SDL.h> // Para SDL_FRect, Uint8, SDL_GetRenderTarget, SDL_RenderClear, SDL_SetRenderDrawColor, SDL_SetRenderTarget, SDL_BLENDMODE_BLEND, SDL_PixelFormat, SDL_TextureAccess, SDL_GetTextureAlphaMod #include <SDL3/SDL.h> // Para SDL_FRect, Uint8, SDL_GetRenderTarget, SDL_RenderClear, SDL_SetRenderDrawColor, SDL_SetRenderTarget, SDL_BLENDMODE_BLEND, SDL_PixelFormat, SDL_TextureAccess, SDL_GetTextureAlphaMod
#include <fstream> // Para basic_ifstream, basic_istream, basic_ostream, operator<<, istream, ifstream, istringstream #include <fstream> // Para basic_ifstream, basic_istream, basic_ostream, operator<<, istream, ifstream, istringstream
#include <utility> // Para std::cmp_less_equal
#include <iostream> // Para cerr #include <iostream> // Para cerr
#include <sstream> // Para basic_istringstream #include <sstream> // Para basic_istringstream
#include <stdexcept> // Para runtime_error #include <stdexcept> // Para runtime_error
#include <string_view> // Para string_view #include <string_view> // Para string_view
#include <utility> // Para std::cmp_less_equal
#include <vector> // Para vector #include <vector> // Para vector
#include "color.hpp" // Para Color #include "color.hpp" // Para Color

View File

@@ -216,4 +216,26 @@ class ActionListOption : public MenuOption {
void updateCurrentIndex(); void updateCurrentIndex();
[[nodiscard]] auto findCurrentIndex() const -> size_t; [[nodiscard]] auto findCurrentIndex() const -> size_t;
};
// Opción genérica con callbacks: getter para mostrar, adjuster(bool up) para cambiar valor
class CallbackOption : public MenuOption {
public:
CallbackOption(const std::string& cap, ServiceMenu::SettingsGroup grp, std::function<std::string()> getter, std::function<void(bool)> adjuster, std::function<int(Text*)> max_width_fn = nullptr)
: MenuOption(cap, grp),
getter_(std::move(getter)),
adjuster_(std::move(adjuster)),
max_width_fn_(std::move(max_width_fn)) {}
[[nodiscard]] auto getBehavior() const -> Behavior override { return Behavior::ADJUST; }
[[nodiscard]] auto getValueAsString() const -> std::string override { return getter_(); }
void adjustValue(bool adjust_up) override { adjuster_(adjust_up); }
auto getMaxValueWidth(Text* text_renderer) const -> int override {
return max_width_fn_ ? max_width_fn_(text_renderer) : 0;
}
private:
std::function<std::string()> getter_;
std::function<void(bool)> adjuster_;
std::function<int(Text*)> max_width_fn_;
}; };

View File

@@ -363,22 +363,81 @@ void ServiceMenu::initializeOptions() {
Options::window.max_zoom, Options::window.max_zoom,
1)); 1));
// Shader: Desactivat / PostFX / CrtPi
{
const std::string DISABLED_TEXT = Lang::getText("[SERVICE_MENU] SHADER_DISABLED");
std::vector<std::string> shader_values = {DISABLED_TEXT, "PostFX", "CrtPi"};
auto shader_getter = [DISABLED_TEXT]() -> std::string {
if (!Options::video.shader.enabled) { return DISABLED_TEXT; }
return (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) ? "CrtPi" : "PostFX";
};
auto shader_setter = [DISABLED_TEXT](const std::string& val) {
if (val == DISABLED_TEXT) {
Options::video.shader.enabled = false;
} else {
Options::video.shader.enabled = true;
const auto TYPE = (val == "CrtPi") ? Rendering::ShaderType::CRTPI : Rendering::ShaderType::POSTFX;
Options::video.shader.current_shader = TYPE;
auto* screen = Screen::get();
if (screen != nullptr) {
screen->applySettings();
}
}
Screen::initShaders();
};
options_.push_back(std::make_unique<ListOption>(
Lang::getText("[SERVICE_MENU] SHADER"),
SettingsGroup::VIDEO,
shader_values,
shader_getter,
shader_setter));
}
// Preset: muestra nombre, cicla circularmente entre presets del shader activo
{
auto preset_getter = []() -> std::string {
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
if (Options::crtpi_presets.empty()) { return ""; }
return Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset)).name;
}
if (Options::postfx_presets.empty()) { return ""; }
return Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset)).name;
};
auto preset_adjuster = [](bool up) {
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
if (Options::crtpi_presets.empty()) { return; }
const int SIZE = static_cast<int>(Options::crtpi_presets.size());
Options::video.shader.current_crtpi_preset = up
? (Options::video.shader.current_crtpi_preset + 1) % SIZE
: (Options::video.shader.current_crtpi_preset + SIZE - 1) % SIZE;
} else {
if (Options::postfx_presets.empty()) { return; }
const int SIZE = static_cast<int>(Options::postfx_presets.size());
Options::video.shader.current_postfx_preset = up
? (Options::video.shader.current_postfx_preset + 1) % SIZE
: (Options::video.shader.current_postfx_preset + SIZE - 1) % SIZE;
}
Screen::initShaders();
};
auto preset_max_width = [](Text* text) -> int {
int max_w = 0;
for (const auto& p : Options::postfx_presets) { max_w = std::max(max_w, text->length(p.name, -2)); }
for (const auto& p : Options::crtpi_presets) { max_w = std::max(max_w, text->length(p.name, -2)); }
return max_w;
};
options_.push_back(std::make_unique<CallbackOption>(
Lang::getText("[SERVICE_MENU] SHADER_PRESET"),
SettingsGroup::VIDEO,
preset_getter,
preset_adjuster,
preset_max_width));
}
options_.push_back(std::make_unique<BoolOption>( options_.push_back(std::make_unique<BoolOption>(
Lang::getText("[SERVICE_MENU] POSTFX"),
SettingsGroup::VIDEO,
&Options::video.postfx));
options_.push_back(std::make_unique<IntOption>(
Lang::getText("[SERVICE_MENU] POSTFX_PRESET"),
SettingsGroup::VIDEO,
&Options::current_postfx_preset,
0, static_cast<int>(Options::postfx_presets.size()) - 1, 1));
options_.push_back(std::make_unique<IntOption>(
Lang::getText("[SERVICE_MENU] SUPERSAMPLING"), Lang::getText("[SERVICE_MENU] SUPERSAMPLING"),
SettingsGroup::VIDEO, SettingsGroup::VIDEO,
&Options::video.supersampling, &Options::video.supersampling.enabled));
1, 3, 1));
options_.push_back(std::make_unique<BoolOption>( options_.push_back(std::make_unique<BoolOption>(
Lang::getText("[SERVICE_MENU] VSYNC"), Lang::getText("[SERVICE_MENU] VSYNC"),
@@ -568,6 +627,20 @@ void ServiceMenu::setHiddenOptions() {
} }
} }
{
auto* option = getOptionByCaption(Lang::getText("[SERVICE_MENU] SHADER_PRESET"));
if (option != nullptr) {
option->setHidden(!Options::video.shader.enabled);
}
}
{
auto* option = getOptionByCaption(Lang::getText("[SERVICE_MENU] SUPERSAMPLING"));
if (option != nullptr) {
option->setHidden(!Options::video.shader.enabled || Options::video.shader.current_shader != Rendering::ShaderType::POSTFX);
}
}
updateMenu(); // El menú debe refrescarse si algo se oculta updateMenu(); // El menú debe refrescarse si algo se oculta
} }

View File

@@ -3,7 +3,7 @@
# Variables # Variables
CXX := g++ CXX := g++
CXXFLAGS := -std=c++20 -Wall -Os -I../../ CXXFLAGS := -std=c++20 -Wall -Os
SOURCES := pack_resources.cpp ../../source/resource_pack.cpp SOURCES := pack_resources.cpp ../../source/resource_pack.cpp
TARGET := pack_resources TARGET := pack_resources
CLEAN_FILES := pack_resources *.pack *.o CLEAN_FILES := pack_resources *.pack *.o
@@ -21,7 +21,7 @@ else
endif endif
# Reglas principales # Reglas principales
.PHONY: all pack_tool clean help test_pack .PHONY: all pack_tool pack clean help test_pack
# Compilar herramienta de empaquetado # Compilar herramienta de empaquetado
all: pack_tool all: pack_tool
@@ -37,6 +37,14 @@ clean:
$(CLEAN_CMD) $(call FixPath,$(CLEAN_FILES)) $(CLEAN_CMD) $(call FixPath,$(CLEAN_FILES))
@echo "✓ Archivos limpiados" @echo "✓ Archivos limpiados"
# Crear pack de recursos final (invocado desde Makefile raíz)
pack: pack_tool
ifeq ($(OS),Windows_NT)
.\$(TARGET) ..\..\data ..\..\resources.pack
else
./$(TARGET) ../../data ../../resources.pack
endif
# Crear pack de recursos de prueba # Crear pack de recursos de prueba
test_pack: pack_tool test_pack: pack_tool
@echo "Creando pack de recursos de prueba..." @echo "Creando pack de recursos de prueba..."

View File

@@ -1,5 +1,5 @@
#include "../source/resource_pack.hpp" #include "../../source/resource_pack.hpp"
#include "../build/version.h" // Para Version::APP_NAME #include "../../build/version.h" // Para Version::APP_NAME
#include <iostream> #include <iostream>
#include <filesystem> #include <filesystem>

View File

@@ -20,6 +20,9 @@ echo "Compiling SPIR-V shaders..."
glslc "${SHADERS_DIR}/postfx.vert" -o /tmp/postfx.vert.spv glslc "${SHADERS_DIR}/postfx.vert" -o /tmp/postfx.vert.spv
glslc "${SHADERS_DIR}/postfx.frag" -o /tmp/postfx.frag.spv glslc "${SHADERS_DIR}/postfx.frag" -o /tmp/postfx.frag.spv
glslc -fshader-stage=fragment "${SHADERS_DIR}/crtpi_frag.glsl" -o /tmp/crtpi_frag.spv
glslc "${SHADERS_DIR}/upscale.frag" -o /tmp/upscale.frag.spv
glslc "${SHADERS_DIR}/downscale.frag" -o /tmp/downscale.frag.spv
echo "Generating C++ headers..." echo "Generating C++ headers..."
@@ -33,12 +36,30 @@ xxd -i /tmp/postfx.frag.spv | \
sed 's/unsigned int .*postfx_frag_spv_len/static const size_t kpostfx_frag_spv_size/' \ sed 's/unsigned int .*postfx_frag_spv_len/static const size_t kpostfx_frag_spv_size/' \
> "${HEADERS_DIR}/postfx_frag_spv.h" > "${HEADERS_DIR}/postfx_frag_spv.h"
xxd -i /tmp/crtpi_frag.spv | \
sed 's/unsigned char .*crtpi_frag_spv\[\]/static const uint8_t kcrtpi_frag_spv[]/' | \
sed 's/unsigned int .*crtpi_frag_spv_len/static const size_t kcrtpi_frag_spv_size/' \
> "${HEADERS_DIR}/crtpi_frag_spv.h"
xxd -i /tmp/upscale.frag.spv | \
sed 's/unsigned char .*upscale_frag_spv\[\]/static const uint8_t kupscale_frag_spv[]/' | \
sed 's/unsigned int .*upscale_frag_spv_len/static const size_t kupscale_frag_spv_size/' \
> "${HEADERS_DIR}/upscale_frag_spv.h"
xxd -i /tmp/downscale.frag.spv | \
sed 's/unsigned char .*downscale_frag_spv\[\]/static const uint8_t kdownscale_frag_spv[]/' | \
sed 's/unsigned int .*downscale_frag_spv_len/static const size_t kdownscale_frag_spv_size/' \
> "${HEADERS_DIR}/downscale_frag_spv.h"
# Prepend required includes to the headers # Prepend required includes to the headers
for f in "${HEADERS_DIR}/postfx_vert_spv.h" "${HEADERS_DIR}/postfx_frag_spv.h"; do for f in "${HEADERS_DIR}/postfx_vert_spv.h" "${HEADERS_DIR}/postfx_frag_spv.h" "${HEADERS_DIR}/crtpi_frag_spv.h" "${HEADERS_DIR}/upscale_frag_spv.h" "${HEADERS_DIR}/downscale_frag_spv.h"; do
echo -e "#pragma once\n#include <cstdint>\n#include <cstddef>\n$(cat "$f")" > "$f" echo -e "#pragma once\n#include <cstdint>\n#include <cstddef>\n$(cat "$f")" > "$f"
done done
echo "Done. Headers updated in ${HEADERS_DIR}/" echo "Done. Headers updated in ${HEADERS_DIR}/"
echo " postfx_vert_spv.h" echo " postfx_vert_spv.h"
echo " postfx_frag_spv.h" echo " postfx_frag_spv.h"
echo " crtpi_frag_spv.h"
echo " upscale_frag_spv.h"
echo " downscale_frag_spv.h"
echo "Rebuild the project to use the new shaders." echo "Rebuild the project to use the new shaders."