6 Commits

Author SHA1 Message Date
20b9a95619 cppcheck 2026-04-17 22:20:37 +02:00
513eacf356 singletons 2026-04-17 21:27:30 +02:00
5889df2a47 presets en yaml 2026-04-17 19:56:43 +02:00
7f703390f9 modernitzat el sistema d'opcions 2026-04-17 19:36:40 +02:00
1bb0ebdef8 sdl3gpu 2026-04-17 19:04:44 +02:00
5fec0110b3 reestructuració 2026-04-17 17:15:38 +02:00
90 changed files with 48559 additions and 2573 deletions

108
CLAUDE.md
View File

@@ -4,67 +4,90 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview ## Project Overview
Coffee Crisis is a C++20 arcade game built with SDL2. The player controls a character defending the UPV (university) from bouncing coffee-ball enemies across 10 stages. Supports 1-2 players, keyboard and gamepad input, and multiple languages (Spanish, Basque, English). Coffee Crisis is a C++20 arcade game built with SDL3. The player controls a character defending the UPV (university) from bouncing coffee-ball enemies across 10 stages. Supports 1-2 players, keyboard and gamepad input, and multiple languages (Spanish, Basque, English).
## Build Commands ## Build Commands
Dependencies: `libsdl2-dev` and `g++` (Linux) or `clang++` (macOS). Dependencies: `libsdl3-dev` and `g++` (Linux) or `clang++` (macOS). Build system is CMake (driven by `Makefile` wrappers).
```bash ```bash
# Linux make # Release build
make linux # Release build → ./coffee_crisis make debug # Debug build (defines DEBUG and PAUSE)
make linux_debug # Debug build (defines DEBUG and PAUSE) → ./coffee_crisis_debug make release # Empaqueta .tar.gz / .dmg / .zip segons SO
make pack # Regenera resources.pack
# macOS make compile_shaders # Compila shaders GLSL → headers SPIR-V (requereix glslc)
make macos # Release build with clang++ make controllerdb # Descarga gamecontrollerdb.txt
make macos_debug # Debug build make format # clang-format -i
make tidy # clang-tidy
# Windows (MinGW) make cppcheck # cppcheck
make windows # Release build → coffee_crisis.exe
make windows_debug # Debug build
# Release packaging
make linux_release # Builds and creates .tar.gz
make macos_release # Builds Intel + Apple Silicon .dmg files
make windows_release # Builds and creates .zip
``` ```
There is also a CMakeLists.txt available as an alternative build system.
There are no tests or linter configured.
## Architecture ## Architecture
All source code is in `source/`. The game uses a section-based architecture controlled by the **Director** class: Source layout (alineat amb la resta de projectes germans):
- **Director** (`director.h/cpp`): Top-level controller. Initializes SDL, manages the window/renderer, and runs sections in sequence: Logo → Intro → Title → Game → Quit. Owns all shared objects (Screen, Input, Lang, Asset). ```
- **Game** (`game.h/cpp`): Core gameplay logic. Manages players, balloons (enemies), bullets, items, stages, menace level, and collision detection. Contains its own update/render loop plus sub-loops for pause and game over screens. source/
- **Screen** (`screen.h/cpp`): Rendering abstraction. Manages a virtual canvas (256×192) that gets scaled to the actual window. Handles fullscreen/windowed modes, border rendering, and fade effects. ├── main.cpp
- **Input** (`input.h/cpp`): Abstracts keyboard and gamepad input. ├── core/
- **Asset** (`asset.h/cpp`): Resource file index. Files are registered with `add()` and retrieved by name with `get()`. All paths are relative to the executable. │ ├── audio/ jail_audio.hpp
- **Lang** (`lang.h/cpp`): i18n system loading text strings from files in `data/lang/`. │ ├── input/ input.*, mouse.*
│ ├── locale/ lang.*
│ ├── rendering/ screen, fade, text, writer, texture, sprite + animated/moving/smart
│ │ ├── shader_backend.hpp (interfície abstracta de post-procesado)
│ │ └── sdl3gpu/ (pipeline SDL3 GPU)
│ │ ├── sdl3gpu_shader.* (implementació del backend GPU)
│ │ └── spv/ (headers SPIR-V generats — protegits amb dummies `.clang-*`)
│ ├── resources/ asset, resource, resource_pack, resource_loader, resource_helper
│ └── system/ director
├── game/
│ ├── defaults.hpp (constants de gameplay: block size, canvas, áreas, colors)
│ ├── game.* (hub de gameplay)
│ ├── entities/ player, balloon, bullet, item
│ ├── scenes/ logo, intro, title, instructions
│ └── ui/ menu
├── utils/
│ ├── defines.hpp (macros de build)
│ └── utils.* (helpers, enum de dificultat, circle_t, ...)
└── external/ (stb_image, stb_vorbis — protegits amb dummies `.clang-*`)
```
### Sprite hierarchy Flux general controlat per la classe **Director** (`core/system/director.h`): inicialitza SDL, finestra/renderer i executa seccions en seqüència **Logo → Intro → Title → Game → Quit**. Les classes principals:
- **Sprite** → base class for drawing from a PNG spritesheet - **Game** (`game/game.h`): gameplay nuclear. Gestiona jugadors, balloons, bullets, items, stages, nivell d'amenaça i col·lisions. Té el seu bucle d'update/render i sub-bucles per pausa i game-over.
- **AnimatedSprite** → extends Sprite with frame-based animation (loaded from `.ani` files) - **Screen** (`core/rendering/screen.h`): abstracció de render. Canvas virtual 256×192 escalat a la finestra. Fullscreen/windowed, borders, fades.
- **MovingSprite** → sprite with movement - **Input** (`core/input/input.h`): abstracció de teclat i gamepad.
- **SmartSprite** → sprite with autonomous behavior (score popups, thrown items) - **Asset** (`core/resources/asset.h`): índex de fitxers de recurs (`add`/`get` per nom).
- **Lang** (`core/locale/lang.h`): i18n, carrega strings des de `data/lang/`.
### Game entities ### Sprite hierarchy (`core/rendering/`)
- **Player** (`player.h/cpp`): Player character state and rendering - **Sprite** → base per dibuixar des d'un spritesheet PNG
- **Balloon** (`balloon.h/cpp`): Enemy entities with multiple types and split-on-pop behavior - **AnimatedSprite** → afegeix animació per frames (arxius `.ani`)
- **Bullet** (`bullet.h/cpp`): Projectiles fired by the player (left/center/right) - **MovingSprite** → sprite amb posició/velocitat
- **Item** (`item.h/cpp`): Collectible items (points, clock, coffee, power-ups) - **SmartSprite** → sprite autònom (score popups, objectes llençats)
### Audio ### Audio
**jail_audio** (`jail_audio.h/cpp`): Custom audio library wrapping SDL2 audio. Uses stb_vorbis for OGG decoding. Provides `JA_*` functions for music and sound effects with channel-based mixing. **jail_audio** (`core/audio/jail_audio.hpp`): wrapper audio SDL3 first-party. Usa stb_vorbis per OGG. API `JA_*` per música i efectes amb mesclat per canals.
### Key constants ### GPU / shaders (post-procesado)
Defined in `const.h`: block size (8px), virtual canvas (256×192), play area bounds, section/subsection IDs, and color definitions. Pipeline SDL3 GPU portat de `coffee_crisis_arcade_edition`. El canvas 256×192 es pot passar per un backend GPU que aplica PostFX (vinyeta, scanlines, chroma, gamma, mask, curvatura, bleeding, flicker) o CrtPi (scanlines continues amb bloom). Fallback transparent al `SDL_Renderer` clàssic si la GPU falla o si es desactiva.
- **Interfície**: `core/rendering/shader_backend.hpp` (`Rendering::ShaderBackend`).
- **Implementació**: `core/rendering/sdl3gpu/sdl3gpu_shader.*` + shaders GLSL a `data/shaders/` compilats a `spv/*_spv.h` via `glslc` (o precompilats si no hi ha `glslc`).
- **Emscripten**: compile-time `NO_SHADERS` → sempre ruta clàssica.
- **macOS**: shaders Metal (MSL) inline dins `sdl3gpu_shader.cpp`; no SPIR-V.
- **Opcions persistents** a `config.txt` (migració a YAML pendent):
- `videoGpuAcceleration` (bool)
- `videoGpuPreferredDriver` (string, buit = auto)
- `videoShaderEnabled` (bool)
- `videoShaderType` (0=POSTFX, 1=CRTPI)
- **Hotkeys** (provisionals fins que hi hagi menú d'opcions): `F9` toggle GPU · `F10` toggle shader · `F11` alterna POSTFX ↔ CRTPI.
- **API** a `Screen`: `setGpuAcceleration`/`toggleGpuAcceleration`/`isGpuAccelerated`, `setShaderEnabled`/`toggleShaderEnabled`/`isShaderEnabled`, `setActiveShader`/`toggleActiveShader`/`getActiveShader`.
Presets PostFX/CrtPi i cicle de presets encara **no** estan implementats — arribaran amb la migració a YAML. Per ara, valors per defecte hardcoded.
## Data Directory ## Data Directory
@@ -74,6 +97,7 @@ Defined in `const.h`: block size (8px), virtual canvas (256×192), play area bou
- `data/lang/` — language files (es_ES, ba_BA, en_UK) - `data/lang/` — language files (es_ES, ba_BA, en_UK)
- `data/demo/` — demo recording data (gamecontrollerdb.txt vive en la raíz del proyecto, fuera del pack) - `data/demo/` — demo recording data (gamecontrollerdb.txt vive en la raíz del proyecto, fuera del pack)
- `data/menu/` — menu definition files - `data/menu/` — menu definition files
- `data/shaders/` — fonts GLSL per al post-procesado SDL3 GPU (no van al pack; s'empotren al binari via headers SPIR-V)
## Language ## Language

View File

@@ -26,8 +26,15 @@ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Os -ffunction-sections
# Define el directorio de los archivos fuente # Define el directorio de los archivos fuente
set(DIR_SOURCES "${CMAKE_SOURCE_DIR}/source") set(DIR_SOURCES "${CMAKE_SOURCE_DIR}/source")
# Cargar todos los archivos fuente en DIR_SOURCES # Cargar todos los archivos fuente en DIR_SOURCES (recursivo, sin external/)
file(GLOB SOURCES "${DIR_SOURCES}/*.cpp") file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "${DIR_SOURCES}/*.cpp")
list(FILTER SOURCES EXCLUDE REGEX "${DIR_SOURCES}/external/.*")
# En Emscripten no compilamos sdl3gpu_shader (SDL3 GPU no está soportado en WebGL2).
# Define NO_SHADERS más abajo y filtra el fuente aquí.
if(EMSCRIPTEN)
list(REMOVE_ITEM SOURCES "${DIR_SOURCES}/core/rendering/sdl3gpu/sdl3gpu_shader.cpp")
endif()
# Verificar si se encontraron archivos fuente # Verificar si se encontraron archivos fuente
if(NOT SOURCES) if(NOT SOURCES)
@@ -54,9 +61,81 @@ else()
message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}") message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
endif() endif()
# --- SHADER COMPILATION (Linux/Windows only - macOS uses Metal, Emscripten no soporta SDL3 GPU) ---
if(NOT APPLE AND NOT EMSCRIPTEN)
find_program(GLSLC_EXE NAMES glslc)
set(SHADER_VERT_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.vert")
set(SHADER_FRAG_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.frag")
set(SHADER_CRTPI_SRC "${CMAKE_SOURCE_DIR}/data/shaders/crtpi_frag.glsl")
set(SHADER_UPSCALE_SRC "${CMAKE_SOURCE_DIR}/data/shaders/upscale.frag")
set(SHADER_DOWNSCALE_SRC "${CMAKE_SOURCE_DIR}/data/shaders/downscale.frag")
set(SHADER_VERT_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv/postfx_vert_spv.h")
set(SHADER_FRAG_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv/postfx_frag_spv.h")
set(SHADER_CRTPI_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv/crtpi_frag_spv.h")
set(SHADER_UPSCALE_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv/upscale_frag_spv.h")
set(SHADER_DOWNSCALE_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv/downscale_frag_spv.h")
set(ALL_SHADER_HEADERS "${SHADER_VERT_H}" "${SHADER_FRAG_H}" "${SHADER_CRTPI_H}" "${SHADER_UPSCALE_H}" "${SHADER_DOWNSCALE_H}")
if(GLSLC_EXE)
set(COMPILE_SHADER_SCRIPT "${CMAKE_SOURCE_DIR}/tools/shaders/compile_shader.cmake")
macro(add_shader SRC_FILE OUT_H VAR_NAME)
cmake_parse_arguments(S "" "STAGE" "" ${ARGN})
add_custom_command(
OUTPUT "${OUT_H}"
COMMAND ${CMAKE_COMMAND}
"-DGLSLC=${GLSLC_EXE}"
"-DSRC=${SRC_FILE}"
"-DOUT_H=${OUT_H}"
"-DVAR=${VAR_NAME}"
"-DSTAGE=${S_STAGE}"
-P "${COMPILE_SHADER_SCRIPT}"
DEPENDS "${SRC_FILE}" "${COMPILE_SHADER_SCRIPT}"
COMMENT "Compilando shader: ${VAR_NAME}"
)
endmacro()
add_shader("${SHADER_VERT_SRC}" "${SHADER_VERT_H}" "postfx_vert_spv")
add_shader("${SHADER_FRAG_SRC}" "${SHADER_FRAG_H}" "postfx_frag_spv")
add_shader("${SHADER_CRTPI_SRC}" "${SHADER_CRTPI_H}" "crtpi_frag_spv" STAGE fragment)
add_shader("${SHADER_UPSCALE_SRC}" "${SHADER_UPSCALE_H}" "upscale_frag_spv")
add_shader("${SHADER_DOWNSCALE_SRC}" "${SHADER_DOWNSCALE_H}" "downscale_frag_spv")
add_custom_target(shaders DEPENDS ${ALL_SHADER_HEADERS})
message(STATUS "glslc encontrado: shaders se compilarán automáticamente")
else()
foreach(_h IN LISTS ALL_SHADER_HEADERS)
if(NOT EXISTS "${_h}")
message(FATAL_ERROR
"glslc no encontrado y header SPIR-V no existe: ${_h}\n"
" Instala glslc: sudo apt install glslang-tools (Linux)\n"
" choco install vulkan-sdk (Windows)"
)
endif()
endforeach()
message(STATUS "glslc no encontrado - usando headers SPIR-V precompilados")
endif()
else()
if(EMSCRIPTEN)
message(STATUS "Emscripten: shaders SPIR-V omitidos (SDL3 GPU no soportado en WebGL2)")
else()
message(STATUS "macOS: shaders SPIR-V omitidos (usa Metal inline)")
endif()
endif()
# Añadir ejecutable principal # Añadir ejecutable principal
add_executable(${PROJECT_NAME} ${SOURCES}) add_executable(${PROJECT_NAME} ${SOURCES})
if(NOT APPLE AND NOT EMSCRIPTEN AND GLSLC_EXE)
add_dependencies(${PROJECT_NAME} shaders)
endif()
# Includes relatius a source/ (p.e. `#include "core/rendering/texture.h"`)
target_include_directories(${PROJECT_NAME} PRIVATE ${DIR_SOURCES})
# Configuración de salida: el ejecutable principal va a la raíz del proyecto. # Configuración de salida: el ejecutable principal va a la raíz del proyecto.
# Per-target (no global) perquè `pack_resources` acabe a `build/` com la resta # Per-target (no global) perquè `pack_resources` acabe a `build/` com la resta
# de projectes. # de projectes.
@@ -92,7 +171,7 @@ elseif(APPLE)
) )
endif() endif()
elseif(EMSCRIPTEN) elseif(EMSCRIPTEN)
target_compile_definitions(${PROJECT_NAME} PRIVATE EMSCRIPTEN_BUILD) target_compile_definitions(${PROJECT_NAME} PRIVATE EMSCRIPTEN_BUILD NO_SHADERS)
# En wasm NO empaquetamos un resources.pack: el propio --preload-file de # En wasm NO empaquetamos un resources.pack: el propio --preload-file de
# emscripten ya hace el mismo trabajo (bundle del directorio en un .data), # emscripten ya hace el mismo trabajo (bundle del directorio en un .data),
# así que metemos directamente 'data' y dejamos que el Resource lea por # así que metemos directamente 'data' y dejamos que el Resource lea por
@@ -204,7 +283,7 @@ endif()
if(NOT EMSCRIPTEN) if(NOT EMSCRIPTEN)
add_executable(pack_resources EXCLUDE_FROM_ALL add_executable(pack_resources EXCLUDE_FROM_ALL
tools/pack_resources/pack_resources.cpp tools/pack_resources/pack_resources.cpp
source/resource_pack.cpp source/core/resources/resource_pack.cpp
) )
target_include_directories(pack_resources PRIVATE "${CMAKE_SOURCE_DIR}/source") target_include_directories(pack_resources PRIVATE "${CMAKE_SOURCE_DIR}/source")
target_compile_options(pack_resources PRIVATE -Wall) target_compile_options(pack_resources PRIVATE -Wall)

View File

@@ -19,9 +19,9 @@ RESOURCE_FILE := release/windows/coffee.res
# VERSION (extracted from defines.hpp) # VERSION (extracted from defines.hpp)
# ============================================================================== # ==============================================================================
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
VERSION := $(shell powershell -Command "(Select-String -Path 'source/defines.hpp' -Pattern 'constexpr const char\* VERSION = \"(.+?)\"').Matches.Groups[1].Value") VERSION := $(shell powershell -Command "(Select-String -Path 'source/utils/defines.hpp' -Pattern 'constexpr const char\* VERSION = \"(.+?)\"').Matches.Groups[1].Value")
else else
VERSION := $(shell grep 'constexpr const char\* VERSION' source/defines.hpp | sed -E 's/.*VERSION = "([^"]+)".*/\1/') VERSION := $(shell grep 'constexpr const char\* VERSION' source/utils/defines.hpp | sed -E 's/.*VERSION = "([^"]+)".*/\1/')
endif endif
# ============================================================================== # ==============================================================================
@@ -349,6 +349,11 @@ cppcheck:
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH) @cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
@cmake --build build --target cppcheck @cmake --build build --target cppcheck
# SHADERS (SPIR-V) — sólo Linux/Windows. Requiere glslc en el PATH.
compile_shaders:
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
@cmake --build build --target shaders
# DESCARGA DE GAMECONTROLLERDB # DESCARGA DE GAMECONTROLLERDB
# ============================================================================== # ==============================================================================
controllerdb: controllerdb:
@@ -378,6 +383,7 @@ help:
@echo "" @echo ""
@echo " Herramientas:" @echo " Herramientas:"
@echo " make pack - Empaquetar recursos a resources.pack" @echo " make pack - Empaquetar recursos a resources.pack"
@echo " make compile_shaders - Compilar shaders GLSL a headers SPIR-V (requiere glslc)"
@echo " make controllerdb - Descargar gamecontrollerdb.txt actualizado" @echo " make controllerdb - Descargar gamecontrollerdb.txt actualizado"
@echo "" @echo ""
@echo " Calidad de codigo:" @echo " Calidad de codigo:"
@@ -391,4 +397,4 @@ help:
@echo " make show_version - Mostrar version actual ($(VERSION))" @echo " make show_version - Mostrar version actual ($(VERSION))"
@echo " make help - Mostrar esta ayuda" @echo " make help - Mostrar esta ayuda"
.PHONY: all debug release _windows_release _macos_release _linux_release wasm wasm_debug controllerdb pack format format-check tidy tidy-fix cppcheck show_version help .PHONY: all debug release _windows_release _macos_release _linux_release wasm wasm_debug controllerdb pack format format-check tidy tidy-fix cppcheck compile_shaders show_version help

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

142
data/shaders/postfx.frag Normal file
View File

@@ -0,0 +1,142 @@
#version 450
// Vulkan GLSL fragment shader — PostFX effects
// Used for SDL3 GPU API (SPIR-V path, Win/Linux).
// Compile: glslc postfx.frag -o postfx.frag.spv
// xxd -i postfx.frag.spv > ../../source/core/rendering/sdl3gpu/postfx_frag_spv.h
//
// PostFXUniforms must match exactly the C++ struct in sdl3gpu_shader.hpp
// (8 floats, 32 bytes, std140/scalar layout).
layout(location = 0) in vec2 v_uv;
layout(location = 0) out vec4 out_color;
layout(set = 2, binding = 0) uniform sampler2D scene;
layout(set = 3, binding = 0) uniform PostFXUniforms {
float vignette_strength;
float chroma_strength;
float scanline_strength;
float screen_height;
float mask_strength;
float gamma_strength;
float curvature;
float bleeding;
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
float time; // seconds since SDL init
float oversample; // supersampling factor (1.0 = off, 3.0 = 3×SS)
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz — 48 bytes total (3 × 16)
} u;
// YCbCr helpers for NTSC bleeding
vec3 rgb_to_ycc(vec3 rgb) {
return vec3(
0.299*rgb.r + 0.587*rgb.g + 0.114*rgb.b,
-0.169*rgb.r - 0.331*rgb.g + 0.500*rgb.b + 0.5,
0.500*rgb.r - 0.419*rgb.g - 0.081*rgb.b + 0.5
);
}
vec3 ycc_to_rgb(vec3 ycc) {
float y = ycc.x;
float cb = ycc.y - 0.5;
float cr = ycc.z - 0.5;
return clamp(vec3(
y + 1.402*cr,
y - 0.344*cb - 0.714*cr,
y + 1.772*cb
), 0.0, 1.0);
}
void main() {
vec2 uv = v_uv;
// Curvatura barrel CRT
if (u.curvature > 0.0) {
vec2 c = uv - 0.5;
float rsq = dot(c, c);
vec2 dist = vec2(0.05, 0.1) * u.curvature;
vec2 barrelScale = vec2(1.0) - 0.23 * dist;
c += c * (dist * rsq);
c *= barrelScale;
if (abs(c.x) >= 0.5 || abs(c.y) >= 0.5) {
out_color = vec4(0.0, 0.0, 0.0, 1.0);
return;
}
uv = c + 0.5;
}
// Muestra base
vec3 base = texture(scene, uv).rgb;
// Sangrado NTSC — difuminado horizontal de crominancia.
// step = 1 pixel lógico de juego en UV (corrige SS: textureSize.x = game_w * oversample).
vec3 colour;
if (u.bleeding > 0.0) {
float tw = float(textureSize(scene, 0).x);
float step = u.oversample / tw; // 1 pixel lógico en UV
vec3 ycc = rgb_to_ycc(base);
vec3 ycc_l2 = rgb_to_ycc(texture(scene, uv - vec2(2.0*step, 0.0)).rgb);
vec3 ycc_l1 = rgb_to_ycc(texture(scene, uv - vec2(1.0*step, 0.0)).rgb);
vec3 ycc_r1 = rgb_to_ycc(texture(scene, uv + vec2(1.0*step, 0.0)).rgb);
vec3 ycc_r2 = rgb_to_ycc(texture(scene, uv + vec2(2.0*step, 0.0)).rgb);
ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0 + ycc.yz*2.0 + ycc_r1.yz*2.0 + ycc_r2.yz) / 8.0;
colour = mix(base, ycc_to_rgb(ycc), u.bleeding);
} else {
colour = base;
}
// Aberración cromática (drift animado con time para efecto NTSC real)
float ca = u.chroma_strength * 0.005 * (1.0 + 0.15 * sin(u.time * 7.3));
colour.r = texture(scene, uv + vec2(ca, 0.0)).r;
colour.b = texture(scene, uv - vec2(ca, 0.0)).b;
// Corrección gamma (linealizar antes de scanlines, codificar después)
if (u.gamma_strength > 0.0) {
vec3 lin = pow(colour, vec3(2.4));
colour = mix(colour, lin, u.gamma_strength);
}
// Scanlines — 1 pixel físico oscuro por fila lógica.
// Modelo sustractivo: las filas de scanline se oscurecen, las demás no cambian.
// Esto evita el efecto de sobrebrillo en contenido con colores vivos.
if (u.scanline_strength > 0.0) {
float ps = max(1.0, round(u.pixel_scale));
float frac_in_row = fract(uv.y * u.screen_height);
float row_pos = floor(frac_in_row * ps);
float is_dark = step(ps - 1.0, row_pos);
float scan = mix(1.0, 0.0, is_dark);
colour *= mix(1.0, scan, u.scanline_strength);
}
if (u.gamma_strength > 0.0) {
vec3 enc = pow(colour, vec3(1.0 / 2.2));
colour = mix(colour, enc, u.gamma_strength);
}
// Viñeta
vec2 d = uv - 0.5;
float vignette = 1.0 - dot(d, d) * u.vignette_strength;
colour *= clamp(vignette, 0.0, 1.0);
// Máscara de fósforo RGB — después de scanlines (orden original):
// filas brillantes saturadas → máscara invisible, filas oscuras → RGB visible.
if (u.mask_strength > 0.0) {
float whichMask = fract(gl_FragCoord.x * 0.3333333);
vec3 mask = vec3(0.80);
if (whichMask < 0.3333333)
mask.x = 1.0;
else if (whichMask < 0.6666666)
mask.y = 1.0;
else
mask.z = 1.0;
colour = mix(colour, colour * mask, u.mask_strength);
}
// Parpadeo de fósforo CRT (~50 Hz)
if (u.flicker > 0.0) {
float flicker_wave = sin(u.time * 100.0) * 0.5 + 0.5;
colour *= 1.0 - u.flicker * 0.04 * flicker_wave;
}
out_color = vec4(colour, 1.0);
}

24
data/shaders/postfx.vert Normal file
View File

@@ -0,0 +1,24 @@
#version 450
// Vulkan GLSL vertex shader — postfx full-screen triangle
// Used for SDL3 GPU API (SPIR-V path, Win/Linux).
// Compile: glslc postfx.vert -o postfx.vert.spv
// xxd -i postfx.vert.spv > ../../source/core/rendering/sdl3gpu/postfx_vert_spv.h
layout(location = 0) out vec2 v_uv;
void main() {
// Full-screen triangle (no vertex buffer needed)
const vec2 positions[3] = vec2[3](
vec2(-1.0, -1.0),
vec2( 3.0, -1.0),
vec2(-1.0, 3.0)
);
const vec2 uvs[3] = vec2[3](
vec2(0.0, 1.0),
vec2(2.0, 1.0),
vec2(0.0,-1.0)
);
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
v_uv = uvs[gl_VertexIndex];
}

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

@@ -96,15 +96,15 @@ inline int JA_FeedMusicChunk(JA_Music_t* music) {
if (!music || !music->vorbis || !music->stream) return 0; if (!music || !music->vorbis || !music->stream) return 0;
short chunk[JA_MUSIC_CHUNK_SHORTS]; short chunk[JA_MUSIC_CHUNK_SHORTS];
const int channels = music->spec.channels; const int numChannels = music->spec.channels;
const int samples_per_channel = stb_vorbis_get_samples_short_interleaved( const int samples_per_channel = stb_vorbis_get_samples_short_interleaved(
music->vorbis, music->vorbis,
channels, numChannels,
chunk, chunk,
JA_MUSIC_CHUNK_SHORTS); JA_MUSIC_CHUNK_SHORTS);
if (samples_per_channel <= 0) return 0; if (samples_per_channel <= 0) return 0;
const int bytes = samples_per_channel * channels * JA_MUSIC_BYTES_PER_SAMPLE; const int bytes = samples_per_channel * numChannels * JA_MUSIC_BYTES_PER_SAMPLE;
SDL_PutAudioStreamData(music->stream, chunk, bytes); SDL_PutAudioStreamData(music->stream, chunk, bytes);
return samples_per_channel; return samples_per_channel;
} }

View File

@@ -0,0 +1,42 @@
#include "core/input/global_inputs.hpp"
#include "core/input/input.h"
#include "core/rendering/screen.h"
namespace GlobalInputs {
auto handle() -> bool {
if (Screen::get() == nullptr || Input::get() == nullptr) { return false; }
if (Input::get()->checkInput(input_window_fullscreen, REPEAT_FALSE)) {
Screen::get()->toggleVideoMode();
return true;
}
if (Input::get()->checkInput(input_window_dec_size, REPEAT_FALSE)) {
Screen::get()->decWindowZoom();
return true;
}
if (Input::get()->checkInput(input_window_inc_size, REPEAT_FALSE)) {
Screen::get()->incWindowZoom();
return true;
}
if (Input::get()->checkInput(input_prev_preset, REPEAT_FALSE)) {
Screen::get()->prevPreset();
return true;
}
if (Input::get()->checkInput(input_next_preset, REPEAT_FALSE)) {
Screen::get()->nextPreset();
return true;
}
if (Input::get()->checkInput(input_toggle_shader, REPEAT_FALSE)) {
Screen::get()->toggleShaderEnabled();
return true;
}
if (Input::get()->checkInput(input_toggle_shader_type, REPEAT_FALSE)) {
Screen::get()->toggleActiveShader();
return true;
}
return false;
}
} // namespace GlobalInputs

View File

@@ -0,0 +1,10 @@
#pragma once
namespace GlobalInputs {
// Gestiona els atalls globals disponibles en qualsevol escena: zoom de
// finestra (F1/F2), fullscreen (F3), presets de shader (F8/F9), toggle
// shader (F10) i tipus de shader POSTFX↔CRTPI (F11). Retorna true si ha
// consumit alguna tecla (per si la capa cridant vol suprimir-la del
// processament específic de l'escena).
auto handle() -> bool;
} // namespace GlobalInputs

View File

@@ -1,8 +1,9 @@
#include "input.h" #include "core/input/input.h"
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <iostream> // for basic_ostream, operator<<, cout, basi... #include <algorithm> // for any_of
#include <iostream> // for basic_ostream, operator<<, cout, basi...
// Emscripten-only: SDL 3.4+ ja no casa el GUID dels mandos de Chrome Android // Emscripten-only: SDL 3.4+ ja no casa el GUID dels mandos de Chrome Android
// amb gamecontrollerdb (el gamepad.id d'Android no porta Vendor/Product, el // amb gamecontrollerdb (el gamepad.id d'Android no porta Vendor/Product, el
@@ -39,11 +40,30 @@ static void installWebStandardMapping(SDL_JoystickID jid) {
#endif #endif
} }
// Constructor // Instancia única
Input::Input(std::string file) { Input *Input::instance = nullptr;
// Fichero gamecontrollerdb.txt
dbPath = file;
// Singleton API
void Input::init(const std::string &gameControllerDbPath) {
Input::instance = new Input(gameControllerDbPath);
}
void Input::destroy() {
delete Input::instance;
Input::instance = nullptr;
}
auto Input::get() -> Input * {
return Input::instance;
}
// Constructor
Input::Input(const std::string &file)
: numGamepads(0),
dbPath(file),
verbose(true),
disabledUntil(d_notDisabled),
enabled(true) {
// Inicializa las variables // Inicializa las variables
keyBindings_t kb; keyBindings_t kb;
kb.scancode = 0; kb.scancode = 0;
@@ -54,10 +74,6 @@ Input::Input(std::string file) {
gcb.button = SDL_GAMEPAD_BUTTON_INVALID; gcb.button = SDL_GAMEPAD_BUTTON_INVALID;
gcb.active = false; gcb.active = false;
gameControllerBindings.resize(input_number_of_inputs, gcb); gameControllerBindings.resize(input_number_of_inputs, gcb);
numGamepads = 0;
verbose = true;
enabled = true;
} }
// Destructor // Destructor
@@ -294,10 +310,8 @@ bool Input::handleGamepadAdded(SDL_JoystickID jid, std::string &outName) {
} }
// Si el mando ya está registrado no hace nada (ej. evento retroactivo tras el scan inicial) // Si el mando ya está registrado no hace nada (ej. evento retroactivo tras el scan inicial)
for (SDL_JoystickID existing : connectedControllerIds) { if (std::any_of(connectedControllerIds.begin(), connectedControllerIds.end(), [jid](SDL_JoystickID existing) { return existing == jid; })) {
if (existing == jid) { return false;
return false;
}
} }
installWebStandardMapping(jid); installWebStandardMapping(jid);

View File

@@ -34,6 +34,12 @@ enum inputs_e {
input_window_inc_size, input_window_inc_size,
input_window_dec_size, input_window_dec_size,
// GPU / shaders (hotkeys provisionales hasta que haya menú de opciones)
input_prev_preset,
input_next_preset,
input_toggle_shader,
input_toggle_shader_type,
// Input obligatorio // Input obligatorio
input_number_of_inputs input_number_of_inputs
}; };
@@ -73,9 +79,17 @@ class Input {
// Construye el nombre visible de un mando (name truncado + sufijo #N) // Construye el nombre visible de un mando (name truncado + sufijo #N)
std::string buildControllerName(SDL_Gamepad *pad, int padIndex); std::string buildControllerName(SDL_Gamepad *pad, int padIndex);
// Constructor privado (usar Input::init)
explicit Input(const std::string &file);
// Instancia única
static Input *instance;
public: public:
// Constructor // Singleton API
Input(std::string file); static void init(const std::string &gameControllerDbPath); // Crea la instancia
static void destroy(); // Libera la instancia
static auto get() -> Input *; // Obtiene el puntero a la instancia
// Destructor // Destructor
~Input(); ~Input();

View File

@@ -1,4 +1,4 @@
#include "mouse.hpp" #include "core/input/mouse.hpp"
namespace Mouse { namespace Mouse {
Uint32 cursorHideTime = 3000; // Tiempo en milisegundos para ocultar el cursor por inactividad Uint32 cursorHideTime = 3000; // Tiempo en milisegundos para ocultar el cursor por inactividad

View File

@@ -1,14 +1,30 @@
#include "lang.h" #include "core/locale/lang.h"
#include <fstream> // for basic_ifstream, basic_istream, ifstream #include <fstream> // for basic_ifstream, basic_istream, ifstream
#include <sstream> #include <sstream>
#include "asset.h" // for Asset #include "core/resources/asset.h" // for Asset
#include "resource_helper.h" #include "core/resources/resource_helper.h"
// Instancia única
Lang *Lang::instance = nullptr;
// Singleton API
void Lang::init() {
Lang::instance = new Lang();
}
void Lang::destroy() {
delete Lang::instance;
Lang::instance = nullptr;
}
auto Lang::get() -> Lang * {
return Lang::instance;
}
// Constructor // Constructor
Lang::Lang(Asset *mAsset) { Lang::Lang() {
this->mAsset = mAsset;
} }
// Destructor // Destructor
@@ -21,19 +37,19 @@ bool Lang::setLang(Uint8 lang) {
switch (lang) { switch (lang) {
case es_ES: case es_ES:
file = mAsset->get("es_ES.txt"); file = Asset::get()->get("es_ES.txt");
break; break;
case en_UK: case en_UK:
file = mAsset->get("en_UK.txt"); file = Asset::get()->get("en_UK.txt");
break; break;
case ba_BA: case ba_BA:
file = mAsset->get("ba_BA.txt"); file = Asset::get()->get("ba_BA.txt");
break; break;
default: default:
file = mAsset->get("en_UK.txt"); file = Asset::get()->get("en_UK.txt");
break; break;
} }

View File

@@ -3,7 +3,6 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <string> // for string, basic_string #include <string> // for string, basic_string
class Asset;
// Códigos de idioma // Códigos de idioma
constexpr int es_ES = 0; constexpr int es_ES = 0;
@@ -17,12 +16,19 @@ constexpr int MAX_TEXT_STRINGS = 100;
// Clase Lang // Clase Lang
class Lang { class Lang {
private: private:
Asset *mAsset; // Objeto que gestiona todos los ficheros de recursos
std::string mTextStrings[MAX_TEXT_STRINGS]; // Vector con los textos std::string mTextStrings[MAX_TEXT_STRINGS]; // Vector con los textos
// Constructor privado (usar Lang::init)
Lang();
// Instancia única
static Lang *instance;
public: public:
// Constructor // Singleton API
Lang(Asset *mAsset); static void init(); // Crea la instancia
static void destroy(); // Libera la instancia
static auto get() -> Lang *; // Obtiene el puntero a la instancia
// Destructor // Destructor
~Lang(); ~Lang();

View File

@@ -1,10 +1,10 @@
#include "animatedsprite.h" #include "core/rendering/animatedsprite.h"
#include <fstream> // for basic_ostream, operator<<, basic_istream, basic... #include <fstream> // for basic_ostream, operator<<, basic_istream, basic...
#include <iostream> // for cout #include <iostream> // for cout
#include <sstream> // for basic_stringstream #include <sstream> // for basic_stringstream
#include "texture.h" // for Texture #include "core/rendering/texture.h" // for Texture
// Parser compartido: lee un istream con el formato .ani // Parser compartido: lee un istream con el formato .ani
static animatedSprite_t parseAnimationStream(std::istream &file, Texture *texture, const std::string &filename, bool verbose) { static animatedSprite_t parseAnimationStream(std::istream &file, Texture *texture, const std::string &filename, bool verbose) {
@@ -22,6 +22,8 @@ static animatedSprite_t parseAnimationStream(std::istream &file, Texture *textur
while (std::getline(file, line)) { while (std::getline(file, line)) {
if (line == "[animation]") { if (line == "[animation]") {
animation_t buffer; animation_t buffer;
buffer.speed = 0;
buffer.loop = -1;
buffer.counter = 0; buffer.counter = 0;
buffer.currentFrame = 0; buffer.currentFrame = 0;
buffer.completed = false; buffer.completed = false;
@@ -82,7 +84,7 @@ static animatedSprite_t parseAnimationStream(std::istream &file, Texture *textur
} }
// Carga la animación desde un fichero // Carga la animación desde un fichero
animatedSprite_t loadAnimationFromFile(Texture *texture, std::string filePath, bool verbose) { animatedSprite_t loadAnimationFromFile(Texture *texture, const std::string &filePath, bool verbose) {
const std::string filename = filePath.substr(filePath.find_last_of("\\/") + 1); const std::string filename = filePath.substr(filePath.find_last_of("\\/") + 1);
std::ifstream file(filePath); std::ifstream file(filePath);
if (!file.good()) { if (!file.good()) {
@@ -109,42 +111,34 @@ animatedSprite_t loadAnimationFromMemory(Texture *texture, const std::vector<uin
} }
// Constructor // Constructor
AnimatedSprite::AnimatedSprite(Texture *texture, SDL_Renderer *renderer, std::string file, std::vector<std::string> *buffer) { AnimatedSprite::AnimatedSprite(Texture *texture, SDL_Renderer *renderer, const std::string &file, std::vector<std::string> *buffer)
: currentAnimation(0) {
// Copia los punteros // Copia los punteros
setTexture(texture); setTexture(texture);
setRenderer(renderer); setRenderer(renderer);
// Carga las animaciones // Carga las animaciones
if (file != "") { if (!file.empty()) {
animatedSprite_t as = loadAnimationFromFile(texture, file); animatedSprite_t as = loadAnimationFromFile(texture, file);
// Copia los datos de las animaciones // Copia los datos de las animaciones
for (auto animation : as.animations) { animation.insert(animation.end(), as.animations.begin(), as.animations.end());
this->animation.push_back(animation);
}
} }
else if (buffer) { else if (buffer) {
loadFromVector(buffer); loadFromVector(buffer);
} }
// Inicializa variables
currentAnimation = 0;
} }
// Constructor // Constructor
AnimatedSprite::AnimatedSprite(SDL_Renderer *renderer, animatedSprite_t *animation) { AnimatedSprite::AnimatedSprite(SDL_Renderer *renderer, animatedSprite_t *animation)
: currentAnimation(0) {
// Copia los punteros // Copia los punteros
setTexture(animation->texture); setTexture(animation->texture);
setRenderer(renderer); setRenderer(renderer);
// Inicializa variables
currentAnimation = 0;
// Copia los datos de las animaciones // Copia los datos de las animaciones
for (auto a : animation->animations) { this->animation.insert(this->animation.end(), animation->animations.begin(), animation->animations.end());
this->animation.push_back(a);
}
} }
// Destructor // Destructor
@@ -156,10 +150,10 @@ AnimatedSprite::~AnimatedSprite() {
} }
// Obtiene el indice de la animación a partir del nombre // Obtiene el indice de la animación a partir del nombre
int AnimatedSprite::getIndex(std::string name) { int AnimatedSprite::getIndex(const std::string &name) {
int index = -1; int index = -1;
for (auto a : animation) { for (const auto &a : animation) {
index++; index++;
if (a.name == name) { if (a.name == name) {
return index; return index;
@@ -222,12 +216,12 @@ void AnimatedSprite::setCurrentFrame(int num) {
} }
// Establece el valor del contador // Establece el valor del contador
void AnimatedSprite::setAnimationCounter(std::string name, int num) { void AnimatedSprite::setAnimationCounter(const std::string &name, int num) {
animation[getIndex(name)].counter = num; animation[getIndex(name)].counter = num;
} }
// Establece la velocidad de una animación // Establece la velocidad de una animación
void AnimatedSprite::setAnimationSpeed(std::string name, int speed) { void AnimatedSprite::setAnimationSpeed(const std::string &name, int speed) {
animation[getIndex(name)].counter = speed; animation[getIndex(name)].counter = speed;
} }
@@ -237,7 +231,7 @@ void AnimatedSprite::setAnimationSpeed(int index, int speed) {
} }
// Establece si la animación se reproduce en bucle // Establece si la animación se reproduce en bucle
void AnimatedSprite::setAnimationLoop(std::string name, int loop) { void AnimatedSprite::setAnimationLoop(const std::string &name, int loop) {
animation[getIndex(name)].loop = loop; animation[getIndex(name)].loop = loop;
} }
@@ -247,7 +241,7 @@ void AnimatedSprite::setAnimationLoop(int index, int loop) {
} }
// Establece el valor de la variable // Establece el valor de la variable
void AnimatedSprite::setAnimationCompleted(std::string name, bool value) { void AnimatedSprite::setAnimationCompleted(const std::string &name, bool value) {
animation[getIndex(name)].completed = value; animation[getIndex(name)].completed = value;
} }
@@ -262,7 +256,7 @@ bool AnimatedSprite::animationIsCompleted() {
} }
// Devuelve el rectangulo de una animación y frame concreto // Devuelve el rectangulo de una animación y frame concreto
SDL_Rect AnimatedSprite::getAnimationClip(std::string name, Uint8 index) { SDL_Rect AnimatedSprite::getAnimationClip(const std::string &name, Uint8 index) {
return animation[getIndex(name)].frames[index]; return animation[getIndex(name)].frames[index];
} }
@@ -292,6 +286,8 @@ bool AnimatedSprite::loadFromVector(std::vector<std::string> *source) {
// Si la linea contiene el texto [animation] se realiza el proceso de carga de una animación // Si la linea contiene el texto [animation] se realiza el proceso de carga de una animación
if (line == "[animation]") { if (line == "[animation]") {
animation_t buffer; animation_t buffer;
buffer.speed = 0;
buffer.loop = -1;
buffer.counter = 0; buffer.counter = 0;
buffer.currentFrame = 0; buffer.currentFrame = 0;
buffer.completed = false; buffer.completed = false;
@@ -391,7 +387,7 @@ bool AnimatedSprite::loadFromVector(std::vector<std::string> *source) {
} }
// Establece la animacion actual // Establece la animacion actual
void AnimatedSprite::setCurrentAnimation(std::string name) { void AnimatedSprite::setCurrentAnimation(const std::string &name) {
const int newAnimation = getIndex(name); const int newAnimation = getIndex(name);
if (currentAnimation != newAnimation) { if (currentAnimation != newAnimation) {
currentAnimation = newAnimation; currentAnimation = newAnimation;

View File

@@ -6,7 +6,7 @@
#include <string> // for string, basic_string #include <string> // for string, basic_string
#include <vector> // for vector #include <vector> // for vector
#include "movingsprite.h" // for MovingSprite #include "core/rendering/movingsprite.h" // for MovingSprite
class Texture; class Texture;
struct animation_t { struct animation_t {
@@ -25,7 +25,7 @@ struct animatedSprite_t {
}; };
// Carga la animación desde un fichero // Carga la animación desde un fichero
animatedSprite_t loadAnimationFromFile(Texture *texture, std::string filePath, bool verbose = false); animatedSprite_t loadAnimationFromFile(Texture *texture, const std::string &filePath, bool verbose = false);
// Carga la animación desde bytes en memoria // Carga la animación desde bytes en memoria
animatedSprite_t loadAnimationFromMemory(Texture *texture, const std::vector<uint8_t> &bytes, const std::string &nameForLogs = "", bool verbose = false); animatedSprite_t loadAnimationFromMemory(Texture *texture, const std::vector<uint8_t> &bytes, const std::string &nameForLogs = "", bool verbose = false);
@@ -38,7 +38,7 @@ class AnimatedSprite : public MovingSprite {
public: public:
// Constructor // Constructor
AnimatedSprite(Texture *texture = nullptr, SDL_Renderer *renderer = nullptr, std::string file = "", std::vector<std::string> *buffer = nullptr); explicit AnimatedSprite(Texture *texture = nullptr, SDL_Renderer *renderer = nullptr, const std::string &file = "", std::vector<std::string> *buffer = nullptr);
AnimatedSprite(SDL_Renderer *renderer, animatedSprite_t *animation); AnimatedSprite(SDL_Renderer *renderer, animatedSprite_t *animation);
// Destructor // Destructor
@@ -54,35 +54,35 @@ class AnimatedSprite : public MovingSprite {
void setCurrentFrame(int num); void setCurrentFrame(int num);
// Establece el valor del contador // Establece el valor del contador
void setAnimationCounter(std::string name, int num); void setAnimationCounter(const std::string &name, int num);
// Establece la velocidad de una animación // Establece la velocidad de una animación
void setAnimationSpeed(std::string name, int speed); void setAnimationSpeed(const std::string &name, int speed);
void setAnimationSpeed(int index, int speed); void setAnimationSpeed(int index, int speed);
// Establece el frame al que vuelve la animación al finalizar // Establece el frame al que vuelve la animación al finalizar
void setAnimationLoop(std::string name, int loop); void setAnimationLoop(const std::string &name, int loop);
void setAnimationLoop(int index, int loop); void setAnimationLoop(int index, int loop);
// Establece el valor de la variable // Establece el valor de la variable
void setAnimationCompleted(std::string name, bool value); void setAnimationCompleted(const std::string &name, bool value);
void setAnimationCompleted(int index, bool value); void setAnimationCompleted(int index, bool value);
// Comprueba si ha terminado la animación // Comprueba si ha terminado la animación
bool animationIsCompleted(); bool animationIsCompleted();
// Devuelve el rectangulo de una animación y frame concreto // Devuelve el rectangulo de una animación y frame concreto
SDL_Rect getAnimationClip(std::string name = "default", Uint8 index = 0); SDL_Rect getAnimationClip(const std::string &name = "default", Uint8 index = 0);
SDL_Rect getAnimationClip(int indexA = 0, Uint8 indexF = 0); SDL_Rect getAnimationClip(int indexA = 0, Uint8 indexF = 0);
// Obtiene el indice de la animación a partir del nombre // Obtiene el indice de la animación a partir del nombre
int getIndex(std::string name); int getIndex(const std::string &name);
// Carga la animación desde un vector // Carga la animación desde un vector
bool loadFromVector(std::vector<std::string> *source); bool loadFromVector(std::vector<std::string> *source);
// Establece la animacion actual // Establece la animacion actual
void setCurrentAnimation(std::string name = "default"); void setCurrentAnimation(const std::string &name = "default");
void setCurrentAnimation(int index = 0); void setCurrentAnimation(int index = 0);
// Actualiza las variables del objeto // Actualiza las variables del objeto

View File

@@ -1,16 +1,15 @@
#include "fade.h" #include "core/rendering/fade.h"
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <stdlib.h> // for rand #include <stdlib.h> // for rand
#include <iostream> // for char_traits, basic_ostream, operator<< #include <iostream> // for char_traits, basic_ostream, operator<<
#include "const.h" // for GAMECANVAS_HEIGHT, GAMECANVAS_WIDTH #include "game/defaults.hpp" // for GAMECANVAS_HEIGHT, GAMECANVAS_WIDTH
// Constructor // Constructor
Fade::Fade(SDL_Renderer *renderer) { Fade::Fade(SDL_Renderer *renderer)
mRenderer = renderer; : mRenderer(renderer) {
mBackbuffer = SDL_CreateTexture(mRenderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT); mBackbuffer = SDL_CreateTexture(mRenderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT);
if (mBackbuffer != nullptr) { if (mBackbuffer != nullptr) {
SDL_SetTextureScaleMode(mBackbuffer, SDL_SCALEMODE_NEAREST); SDL_SetTextureScaleMode(mBackbuffer, SDL_SCALEMODE_NEAREST);
@@ -53,7 +52,6 @@ void Fade::render() {
int alpha = mCounter * 4; int alpha = mCounter * 4;
if (alpha >= 255) { if (alpha >= 255) {
alpha = 255;
mFullscreenDone = true; mFullscreenDone = true;
// Deja todos los buffers del mismo color // Deja todos los buffers del mismo color

View File

@@ -0,0 +1,54 @@
#pragma once
#include <SDL3/SDL.h>
// Tipos de fundido
constexpr int FADE_FULLSCREEN = 0;
constexpr int FADE_CENTER = 1;
constexpr int FADE_RANDOM_SQUARE = 2;
// Clase Fade
class Fade {
private:
SDL_Renderer *mRenderer = nullptr; // El renderizador de la ventana
SDL_Texture *mBackbuffer = nullptr; // Textura para usar como backbuffer
Uint8 mFadeType = FADE_FULLSCREEN; // Tipo de fade a realizar
Uint16 mCounter = 0; // Contador interno
bool mEnabled = false; // Indica si el fade está activo
bool mFinished = false; // Indica si ha terminado la transición
Uint8 mR = 0, mG = 0, mB = 0; // Colores para el fade
Uint8 mROriginal = 0, mGOriginal = 0, mBOriginal = 0; // Colores originales para FADE_RANDOM_SQUARE
Uint32 mLastSquareTicks = 0; // Ticks del último cuadrado dibujado (FADE_RANDOM_SQUARE)
Uint16 mSquaresDrawn = 0; // Número de cuadrados dibujados (FADE_RANDOM_SQUARE)
bool mFullscreenDone = false; // Indica si el fade fullscreen ha terminado la fase de fundido
SDL_Rect mRect1{}; // Rectangulo usado para crear los efectos de transición
SDL_Rect mRect2{}; // Rectangulo usado para crear los efectos de transición
public:
// Constructor
explicit Fade(SDL_Renderer *renderer);
// Destructor
~Fade();
// Inicializa las variables
void init(Uint8 r, Uint8 g, Uint8 b);
// Pinta una transición en pantalla
void render();
// Actualiza las variables internas
void update();
// Activa el fade
void activateFade();
// Comprueba si ha terminado la transicion
bool hasEnded();
// Comprueba si está activo
bool isEnabled();
// Establece el tipo de fade
void setFadeType(Uint8 fadeType);
};

View File

@@ -1,55 +1,28 @@
#include "movingsprite.h" #include "core/rendering/movingsprite.h"
#include "texture.h" // for Texture #include "core/rendering/texture.h" // for Texture
// Constructor // Constructor
MovingSprite::MovingSprite(float x, float y, int w, int h, float velx, float vely, float accelx, float accely, Texture *texture, SDL_Renderer *renderer) { MovingSprite::MovingSprite(float x, float y, int w, int h, float velx, float vely, float accelx, float accely, Texture *texture, SDL_Renderer *renderer)
// Copia los punteros : Sprite(0, 0, w, h, texture, renderer),
this->texture = texture; x(x),
this->renderer = renderer; y(y),
xPrev(x),
// Establece el alto y el ancho del sprite yPrev(y),
this->w = w; vx(velx),
this->h = h; vy(vely),
ax(accelx),
// Establece la posición X,Y del sprite ay(accely),
this->x = x; zoomW(1),
this->y = y; zoomH(1),
xPrev = x; angle(0.0),
yPrev = y; rotateEnabled(false),
rotateSpeed(0),
// Establece la velocidad X,Y del sprite rotateAmount(0.0),
vx = velx; counter(0),
vy = vely; center(nullptr),
currentFlip(SDL_FLIP_NONE) {
// Establece la aceleración X,Y del sprite }
ax = accelx;
ay = accely;
// Establece el zoom W,H del sprite
zoomW = 1;
zoomH = 1;
// Establece el angulo con el que se dibujará
angle = (double)0;
// Establece los valores de rotacion
rotateEnabled = false;
rotateSpeed = 0;
rotateAmount = (double)0;
// Contador interno
counter = 0;
// Establece el rectangulo de donde coger la imagen
spriteClip = {0, 0, w, h};
// Establece el centro de rotación
center = nullptr;
// Establece el tipo de volteado
currentFlip = SDL_FLIP_NONE;
};
// Reinicia todas las variables // Reinicia todas las variables
void MovingSprite::clear() { void MovingSprite::clear() {

View File

@@ -2,7 +2,7 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include "sprite.h" // for Sprite #include "core/rendering/sprite.h" // for Sprite
class Texture; class Texture;
// Clase MovingSprite. Añade posicion y velocidad en punto flotante // Clase MovingSprite. Añade posicion y velocidad en punto flotante
@@ -33,7 +33,7 @@ class MovingSprite : public Sprite {
public: public:
// Constructor // Constructor
MovingSprite(float x = 0, float y = 0, int w = 0, int h = 0, float velx = 0, float vely = 0, float accelx = 0, float accely = 0, Texture *texture = nullptr, SDL_Renderer *renderer = nullptr); explicit MovingSprite(float x = 0, float y = 0, int w = 0, int h = 0, float velx = 0, float vely = 0, float accelx = 0, float accely = 0, Texture *texture = nullptr, SDL_Renderer *renderer = nullptr);
// Mueve el sprite // Mueve el sprite
void move(); void move();

View File

@@ -0,0 +1,722 @@
#include "core/rendering/screen.h"
#include <SDL3/SDL.h>
#include <algorithm> // for max, min
#include <cstring> // for memcpy
#include <iostream> // for basic_ostream, operator<<, cout, endl
#include <string> // for basic_string, char_traits, string
#include "core/input/mouse.hpp" // for Mouse::cursorVisible, Mouse::lastMouseMoveTime
#include "core/rendering/text.h" // for Text, TXT_CENTER, TXT_COLOR, TXT_STROKE
#include "core/resources/resource.h"
#include "game/defaults.hpp" // for GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT
#include "game/options.hpp" // for Options::video, Options::settings
#ifndef NO_SHADERS
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp" // for Rendering::SDL3GPUShader
#endif
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/html5.h>
// --- Fix per a fullscreen/resize en Emscripten ---
//
// SDL3 + Emscripten no emet de forma fiable SDL_EVENT_WINDOW_LEAVE_FULLSCREEN
// (libsdl-org/SDL#13300) ni SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED /
// SDL_EVENT_DISPLAY_ORIENTATION (libsdl-org/SDL#11389). Quan l'usuari ix de
// fullscreen amb Esc o rota el mòbil, el canvas HTML torna al tamany correcte
// però l'estat intern de SDL creu que segueix en fullscreen amb la resolució
// anterior i el viewport queda desencuadrat.
//
// Solució: registrar callbacks natius d'Emscripten, diferir la feina un tick
// del event loop (el canvas encara no està estable en el moment del callback)
// i cridar setVideoMode() amb el flag de fullscreen actualitzat. La crida
// interna a SDL_SetWindowFullscreen(false) és la peça que realment fa eixir
// SDL del seu estat intern de fullscreen — sense això res més funciona.
namespace {
Screen *g_screen_instance = nullptr;
void deferredCanvasResize(void * /*userData*/) {
if (g_screen_instance) {
g_screen_instance->handleCanvasResized();
}
}
EM_BOOL onEmFullscreenChange(int /*eventType*/, const EmscriptenFullscreenChangeEvent *event, void * /*userData*/) {
if (g_screen_instance && event) {
g_screen_instance->syncFullscreenFlagFromBrowser(event->isFullscreen != 0);
}
emscripten_async_call(deferredCanvasResize, nullptr, 0);
return EM_FALSE;
}
EM_BOOL onEmOrientationChange(int /*eventType*/, const EmscriptenOrientationChangeEvent * /*event*/, void * /*userData*/) {
emscripten_async_call(deferredCanvasResize, nullptr, 0);
return EM_FALSE;
}
} // namespace
#endif // __EMSCRIPTEN__
// Instancia única
Screen *Screen::instance = nullptr;
// Singleton API
void Screen::init(SDL_Window *window, SDL_Renderer *renderer) {
Screen::instance = new Screen(window, renderer);
}
void Screen::destroy() {
delete Screen::instance;
Screen::instance = nullptr;
}
auto Screen::get() -> Screen * {
return Screen::instance;
}
// Constructor
Screen::Screen(SDL_Window *window, SDL_Renderer *renderer)
: borderColor{0x00, 0x00, 0x00} {
// Inicializa variables
this->window = window;
this->renderer = renderer;
gameCanvasWidth = GAMECANVAS_WIDTH;
gameCanvasHeight = GAMECANVAS_HEIGHT;
// Establece el modo de video (fullscreen/ventana + logical presentation)
// ANTES de crear la textura — SDL3 GPU necesita la logical presentation
// del renderer ya aplicada al swapchain quan es reclama la ventana per a GPU.
// Mirror del pattern de jaildoctors_dilemma (que usa exactament 256×192 i
// funciona) on `initSDLVideo` configura la presentation abans de crear cap
// textura.
setVideoMode(Options::video.fullscreen);
// Força al window manager a completar el resize/posicionat abans de passar
// la ventana al dispositiu GPU. Sense açò en Linux/X11 hi ha un race
// condition que deixa el swapchain en estat inestable i fa crashear el
// driver Vulkan en `SDL_CreateGPUGraphicsPipeline`.
SDL_SyncWindow(window);
// Crea la textura donde se dibujan los graficos del juego.
// ARGB8888 per simplificar el readback cap al pipeline SDL3 GPU.
gameCanvas = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, gameCanvasWidth, gameCanvasHeight);
if (gameCanvas != nullptr) {
SDL_SetTextureScaleMode(gameCanvas, Options::video.scale_mode);
}
if (gameCanvas == nullptr) {
if (Options::settings.console) {
std::cout << "gameCanvas could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
}
}
#ifndef NO_SHADERS
// Buffer de readback del gameCanvas (lo dimensionamos una vez)
pixel_buffer_.resize(static_cast<size_t>(gameCanvasWidth) * static_cast<size_t>(gameCanvasHeight));
#endif
// Renderiza una vez la textura vacía al renderer abans d'inicialitzar els
// shaders: jaildoctors_dilemma ho fa així i evita que el driver Vulkan
// crashegi en la creació del pipeline gràfic. `initShaders()` es crida
// després des de `Director` amb el swapchain ja estable.
SDL_RenderTexture(renderer, gameCanvas, nullptr, nullptr);
// Estado inicial de las notificaciones. El Text real se enlaza después vía
// `initNotifications()` quan `Resource` ja estigui inicialitzat. Dividim
// això del constructor perquè `initShaders()` (GPU) ha de cridar-se ABANS
// de carregar recursos: si el SDL_Renderer ha fet abans moltes
// allocacions (carrega de textures), el driver Vulkan crasheja quan
// després es reclama la ventana per al dispositiu GPU.
notificationText = nullptr;
notificationMessage = "";
notificationTextColor = {0xFF, 0xFF, 0xFF};
notificationOutlineColor = {0x00, 0x00, 0x00};
notificationEndTime = 0;
notificationY = 2;
// Registra callbacks natius d'Emscripten per a fullscreen/orientation
registerEmscriptenEventCallbacks();
}
// Enllaça el Text de les notificacions amb el recurs compartit de `Resource`.
// S'ha de cridar després de `Resource::init(...)`.
void Screen::initNotifications() {
notificationText = Resource::get()->getText("8bithud");
}
// Destructor
Screen::~Screen() {
// notificationText es propiedad de Resource — no liberar.
#ifndef NO_SHADERS
shutdownShaders();
#endif
SDL_DestroyTexture(gameCanvas);
}
// Limpia la pantalla
void Screen::clean(color_t color) {
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, 0xFF);
SDL_RenderClear(renderer);
}
// Prepara para empezar a dibujar en la textura de juego
void Screen::start() {
SDL_SetRenderTarget(renderer, gameCanvas);
}
// Vuelca el contenido del renderizador en pantalla
void Screen::blit() {
// Dibuja la notificación activa sobre el gameCanvas antes de presentar
SDL_SetRenderTarget(renderer, gameCanvas);
renderNotification();
#ifndef NO_SHADERS
// Si el backend GPU està viu i accelerat, passem sempre per ell (tant amb
// shaders com sense). Seguim el mateix pattern que aee_plus: quan shader
// està desactivat, forcem POSTFX + params a zero només per a aquest frame
// i restaurem el shader actiu, així CRTPI no aplica les seues scanlines
// quan l'usuari ho ha desactivat.
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
SDL_Surface *surface = SDL_RenderReadPixels(renderer, nullptr);
if (surface != nullptr) {
if (surface->format == SDL_PIXELFORMAT_ARGB8888) {
std::memcpy(pixel_buffer_.data(), surface->pixels, pixel_buffer_.size() * sizeof(Uint32));
} else {
SDL_Surface *converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888);
if (converted != nullptr) {
std::memcpy(pixel_buffer_.data(), converted->pixels, pixel_buffer_.size() * sizeof(Uint32));
SDL_DestroySurface(converted);
}
}
SDL_DestroySurface(surface);
}
SDL_SetRenderTarget(renderer, nullptr);
if (Options::video.shader.enabled) {
// Ruta normal: shader amb els seus params.
shader_backend_->uploadPixels(pixel_buffer_.data(), gameCanvasWidth, gameCanvasHeight);
shader_backend_->render();
} else {
// Shader off: POSTFX amb params zero (passa-per-aquí). CRTPI no
// val perque sempre aplica els seus efectes interns; salvem i
// restaurem el shader actiu.
const auto PREV_SHADER = shader_backend_->getActiveShader();
if (PREV_SHADER != Rendering::ShaderType::POSTFX) {
shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
}
shader_backend_->setPostFXParams(Rendering::PostFXParams{});
shader_backend_->uploadPixels(pixel_buffer_.data(), gameCanvasWidth, gameCanvasHeight);
shader_backend_->render();
if (PREV_SHADER != Rendering::ShaderType::POSTFX) {
shader_backend_->setActiveShader(PREV_SHADER);
}
}
return;
}
#endif
// Vuelve a dejar el renderizador en modo normal
SDL_SetRenderTarget(renderer, nullptr);
// Borra el contenido previo
SDL_SetRenderDrawColor(renderer, borderColor.r, borderColor.g, borderColor.b, 0xFF);
SDL_RenderClear(renderer);
// Copia la textura de juego en el renderizador en la posición adecuada
SDL_FRect fdest = {(float)dest.x, (float)dest.y, (float)dest.w, (float)dest.h};
SDL_RenderTexture(renderer, gameCanvas, nullptr, &fdest);
// Muestra por pantalla el renderizador
SDL_RenderPresent(renderer);
}
// ============================================================================
// Video y ventana
// ============================================================================
// Establece el modo de video
void Screen::setVideoMode(bool fullscreen) {
applyFullscreen(fullscreen);
if (fullscreen) {
applyFullscreenLayout();
} else {
applyWindowedLayout();
}
applyLogicalPresentation(fullscreen);
}
// Cambia entre pantalla completa y ventana
void Screen::toggleVideoMode() {
setVideoMode(!Options::video.fullscreen);
}
// Reduce el zoom de la ventana
auto Screen::decWindowZoom() -> bool {
if (Options::video.fullscreen) { return false; }
const int PREV = Options::window.zoom;
Options::window.zoom = std::max(Options::window.zoom - 1, WINDOW_ZOOM_MIN);
if (Options::window.zoom == PREV) { return false; }
setVideoMode(false);
return true;
}
// Aumenta el zoom de la ventana
auto Screen::incWindowZoom() -> bool {
if (Options::video.fullscreen) { return false; }
const int PREV = Options::window.zoom;
Options::window.zoom = std::min(Options::window.zoom + 1, WINDOW_ZOOM_MAX);
if (Options::window.zoom == PREV) { return false; }
setVideoMode(false);
return true;
}
// Establece el zoom de la ventana directamente
auto Screen::setWindowZoom(int zoom) -> bool {
if (Options::video.fullscreen) { return false; }
if (zoom < WINDOW_ZOOM_MIN || zoom > WINDOW_ZOOM_MAX) { return false; }
if (zoom == Options::window.zoom) { return false; }
Options::window.zoom = zoom;
setVideoMode(false);
return true;
}
// Establece el escalado entero
void Screen::setIntegerScale(bool enabled) {
if (Options::video.integer_scale == enabled) { return; }
Options::video.integer_scale = enabled;
setVideoMode(Options::video.fullscreen);
}
// Alterna el escalado entero
void Screen::toggleIntegerScale() {
setIntegerScale(!Options::video.integer_scale);
}
// Establece el V-Sync
void Screen::setVSync(bool enabled) {
Options::video.vsync = enabled;
SDL_SetRenderVSync(renderer, enabled ? 1 : SDL_RENDERER_VSYNC_DISABLED);
#ifndef NO_SHADERS
if (shader_backend_) {
shader_backend_->setVSync(enabled);
}
#endif
}
// Alterna el V-Sync
void Screen::toggleVSync() {
setVSync(!Options::video.vsync);
}
// Cambia el color del borde
void Screen::setBorderColor(color_t color) {
borderColor = color;
}
// ============================================================================
// Helpers privados de setVideoMode
// ============================================================================
// SDL_SetWindowFullscreen + visibilidad del cursor
void Screen::applyFullscreen(bool fullscreen) {
SDL_SetWindowFullscreen(window, fullscreen);
if (fullscreen) {
SDL_HideCursor();
Mouse::cursorVisible = false;
} else {
SDL_ShowCursor();
Mouse::cursorVisible = true;
Mouse::lastMouseMoveTime = SDL_GetTicks();
}
}
// Calcula windowWidth/Height/dest para el modo ventana y aplica SDL_SetWindowSize
void Screen::applyWindowedLayout() {
windowWidth = gameCanvasWidth;
windowHeight = gameCanvasHeight;
dest = {0, 0, gameCanvasWidth, gameCanvasHeight};
#ifdef __EMSCRIPTEN__
windowWidth *= WASM_RENDER_SCALE;
windowHeight *= WASM_RENDER_SCALE;
dest.w *= WASM_RENDER_SCALE;
dest.h *= WASM_RENDER_SCALE;
#endif
// Modifica el tamaño de la ventana
SDL_SetWindowSize(window, windowWidth * Options::window.zoom, windowHeight * Options::window.zoom);
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
}
// Obtiene el tamaño de la ventana en fullscreen y calcula el rect del juego
void Screen::applyFullscreenLayout() {
SDL_GetWindowSize(window, &windowWidth, &windowHeight);
computeFullscreenGameRect();
}
// Calcula el rectángulo dest para fullscreen: integer_scale / aspect ratio
void Screen::computeFullscreenGameRect() {
if (Options::video.integer_scale) {
// Calcula el tamaño de la escala máxima
int scale = 0;
while (((gameCanvasWidth * (scale + 1)) <= windowWidth) && ((gameCanvasHeight * (scale + 1)) <= windowHeight)) {
scale++;
}
dest.w = gameCanvasWidth * scale;
dest.h = gameCanvasHeight * scale;
dest.x = (windowWidth - dest.w) / 2;
dest.y = (windowHeight - dest.h) / 2;
} else {
// Manté la relació d'aspecte sense escalat enter (letterbox/pillarbox).
float ratio = (float)gameCanvasWidth / (float)gameCanvasHeight;
if ((windowWidth - gameCanvasWidth) >= (windowHeight - gameCanvasHeight)) {
dest.h = windowHeight;
dest.w = (int)((windowHeight * ratio) + 0.5f);
dest.x = (windowWidth - dest.w) / 2;
dest.y = (windowHeight - dest.h) / 2;
} else {
dest.w = windowWidth;
dest.h = (int)((windowWidth / ratio) + 0.5f);
dest.x = (windowWidth - dest.w) / 2;
dest.y = (windowHeight - dest.h) / 2;
}
}
}
// Aplica la logical presentation y persiste el estado en options
void Screen::applyLogicalPresentation(bool fullscreen) {
SDL_SetRenderLogicalPresentation(renderer, windowWidth, windowHeight, SDL_LOGICAL_PRESENTATION_LETTERBOX);
Options::video.fullscreen = fullscreen;
}
// ============================================================================
// Notificaciones
// ============================================================================
// Muestra una notificación en la línea superior durante durationMs
void Screen::notify(const std::string &text, color_t textColor, color_t outlineColor, Uint32 durationMs) {
notificationMessage = text;
notificationTextColor = textColor;
notificationOutlineColor = outlineColor;
notificationEndTime = SDL_GetTicks() + durationMs;
}
// Limpia la notificación actual
void Screen::clearNotification() {
notificationEndTime = 0;
notificationMessage.clear();
}
// Dibuja la notificación activa (si la hay) sobre el gameCanvas
void Screen::renderNotification() {
if (notificationText == nullptr || SDL_GetTicks() >= notificationEndTime) {
return;
}
notificationText->writeDX(TXT_CENTER | TXT_COLOR | TXT_STROKE,
gameCanvasWidth / 2,
notificationY,
notificationMessage,
1,
notificationTextColor,
1,
notificationOutlineColor);
}
// ============================================================================
// Emscripten — fix per a fullscreen/resize (veure el bloc de comentaris al
// principi del fitxer i l'anonymous namespace amb els callbacks natius).
// ============================================================================
void Screen::handleCanvasResized() {
#ifdef __EMSCRIPTEN__
// La crida a SDL_SetWindowFullscreen + SDL_SetRenderLogicalPresentation
// que fa setVideoMode és l'única manera de resincronitzar l'estat intern
// de SDL amb el canvas HTML real.
setVideoMode(Options::video.fullscreen);
#endif
}
void Screen::syncFullscreenFlagFromBrowser(bool isFullscreen) {
#ifdef __EMSCRIPTEN__
Options::video.fullscreen = isFullscreen;
#else
(void)isFullscreen;
#endif
}
void Screen::registerEmscriptenEventCallbacks() {
#ifdef __EMSCRIPTEN__
// IMPORTANT: NO registrem resize callback. En mòbil, fer scroll fa que el
// navegador oculti/mostri la barra d'URL, disparant un resize del DOM per
// cada scroll. Això portava a cridar setVideoMode per cada scroll, que
// re-aplicava la logical presentation i corrompia el viewport intern de SDL.
g_screen_instance = this;
emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_TRUE, onEmFullscreenChange);
emscripten_set_orientationchange_callback(nullptr, EM_TRUE, onEmOrientationChange);
#endif
}
// ============================================================================
// GPU / shaders (SDL3 GPU post-procesado). En builds con NO_SHADERS (Emscripten)
// las operaciones son no-op; la ruta clásica sigue siendo la única disponible.
// ============================================================================
#ifndef NO_SHADERS
// Aplica al backend el shader actiu + els seus presets PostFX i CrtPi.
// Només s'ha de cridar quan `videoShaderEnabled=true` (en cas contrari el
// blit() ja força POSTFX+zero params per a desactivar els efectes sense
// tocar els paràmetres emmagatzemats).
void Screen::applyShaderParams() {
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) {
return;
}
shader_backend_->setActiveShader(Options::video.shader.current_shader);
applyCurrentPostFXPreset();
applyCurrentCrtPiPreset();
}
#endif
void Screen::initShaders() {
#ifndef NO_SHADERS
if (!shader_backend_) {
shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
const std::string FALLBACK_DRIVER = "none";
shader_backend_->setPreferredDriver(
Options::video.gpu.acceleration ? Options::video.gpu.preferred_driver : FALLBACK_DRIVER);
}
if (!shader_backend_->isHardwareAccelerated()) {
const bool ok = shader_backend_->init(window, gameCanvas, "", "");
if (Options::settings.console) {
std::cout << "Screen::initShaders: SDL3GPUShader::init() = " << (ok ? "OK" : "FAILED") << '\n';
}
}
if (shader_backend_->isHardwareAccelerated()) {
shader_backend_->setScaleMode(Options::video.integer_scale);
shader_backend_->setVSync(Options::video.vsync);
// Resol els índexs de preset a partir del nom emmagatzemat al config.
// Si el nom no existeix (preset esborrat del YAML), es queda en 0.
for (int i = 0; i < static_cast<int>(Options::postfx_presets.size()); ++i) {
if (Options::postfx_presets[i].name == Options::video.shader.current_postfx_preset_name) {
Options::current_postfx_preset = i;
break;
}
}
for (int i = 0; i < static_cast<int>(Options::crtpi_presets.size()); ++i) {
if (Options::crtpi_presets[i].name == Options::video.shader.current_crtpi_preset_name) {
Options::current_crtpi_preset = i;
break;
}
}
applyShaderParams(); // aplica preset del shader actiu
}
#endif
}
void Screen::shutdownShaders() {
#ifndef NO_SHADERS
// Només es crida des del destructor de Screen. Els toggles runtime NO la
// poden cridar: destruir + recrear el dispositiu SDL3 GPU amb la ventana
// ja reclamada és inestable (Vulkan/Radeon crasheja en el següent claim).
if (shader_backend_) {
shader_backend_->cleanup();
shader_backend_.reset();
}
#endif
}
auto Screen::isGpuAccelerated() const -> bool {
#ifndef NO_SHADERS
return shader_backend_ && shader_backend_->isHardwareAccelerated();
#else
return false;
#endif
}
void Screen::setShaderEnabled(bool enabled) {
if (Options::video.shader.enabled == enabled) { return; }
Options::video.shader.enabled = enabled;
#ifndef NO_SHADERS
if (enabled) {
applyShaderParams(); // restaura preset del shader actiu
}
// Si enabled=false, blit() forçarà POSTFX+zero per frame — no cal tocar
// res ara.
#endif
const color_t CYAN = {0x00, 0xFF, 0xFF};
const color_t BLACK = {0x00, 0x00, 0x00};
const Uint32 DUR_MS = 1500;
notify(enabled ? "Shader: ON" : "Shader: OFF", CYAN, BLACK, DUR_MS);
}
void Screen::toggleShaderEnabled() {
setShaderEnabled(!Options::video.shader.enabled);
}
auto Screen::isShaderEnabled() const -> bool {
return Options::video.shader.enabled;
}
#ifndef NO_SHADERS
void Screen::setActiveShader(Rendering::ShaderType type) {
Options::video.shader.current_shader = type;
if (Options::video.shader.enabled) {
applyShaderParams();
}
const color_t MAGENTA = {0xFF, 0x00, 0xFF};
const color_t BLACK = {0x00, 0x00, 0x00};
const Uint32 DUR_MS = 1500;
notify(type == Rendering::ShaderType::CRTPI ? "Shader: CRTPI" : "Shader: POSTFX", MAGENTA, BLACK, DUR_MS);
}
auto Screen::getActiveShader() const -> Rendering::ShaderType {
return Options::video.shader.current_shader;
}
#endif
void Screen::toggleActiveShader() {
#ifndef NO_SHADERS
const Rendering::ShaderType NEXT = getActiveShader() == Rendering::ShaderType::POSTFX
? Rendering::ShaderType::CRTPI
: Rendering::ShaderType::POSTFX;
setActiveShader(NEXT);
#else
Options::video.shader.current_shader = Options::video.shader.current_shader == Rendering::ShaderType::POSTFX
? Rendering::ShaderType::CRTPI
: Rendering::ShaderType::POSTFX;
#endif
}
// ============================================================================
// Presets de shaders
// ============================================================================
void Screen::applyCurrentPostFXPreset() {
#ifndef NO_SHADERS
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) { return; }
if (Options::postfx_presets.empty()) { return; }
if (Options::current_postfx_preset < 0 || Options::current_postfx_preset >= static_cast<int>(Options::postfx_presets.size())) {
Options::current_postfx_preset = 0;
}
const auto &PRESET = Options::postfx_presets[Options::current_postfx_preset];
Rendering::PostFXParams p;
p.vignette = PRESET.vignette;
p.scanlines = PRESET.scanlines;
p.chroma = PRESET.chroma;
p.mask = PRESET.mask;
p.gamma = PRESET.gamma;
p.curvature = PRESET.curvature;
p.bleeding = PRESET.bleeding;
p.flicker = PRESET.flicker;
shader_backend_->setPostFXParams(p);
#endif
}
void Screen::applyCurrentCrtPiPreset() {
#ifndef NO_SHADERS
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) { return; }
if (Options::crtpi_presets.empty()) { return; }
if (Options::current_crtpi_preset < 0 || Options::current_crtpi_preset >= static_cast<int>(Options::crtpi_presets.size())) {
Options::current_crtpi_preset = 0;
}
const auto &PRESET = Options::crtpi_presets[Options::current_crtpi_preset];
Rendering::CrtPiParams p;
p.scanline_weight = PRESET.scanline_weight;
p.scanline_gap_brightness = PRESET.scanline_gap_brightness;
p.bloom_factor = PRESET.bloom_factor;
p.input_gamma = PRESET.input_gamma;
p.output_gamma = PRESET.output_gamma;
p.mask_brightness = PRESET.mask_brightness;
p.curvature_x = PRESET.curvature_x;
p.curvature_y = PRESET.curvature_y;
p.mask_type = PRESET.mask_type;
p.enable_scanlines = PRESET.enable_scanlines;
p.enable_multisample = PRESET.enable_multisample;
p.enable_gamma = PRESET.enable_gamma;
p.enable_curvature = PRESET.enable_curvature;
p.enable_sharper = PRESET.enable_sharper;
shader_backend_->setCrtPiParams(p);
#endif
}
auto Screen::getCurrentPresetName() const -> const char * {
#ifndef NO_SHADERS
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) { return "---"; }
if (Options::video.shader.current_shader == Rendering::ShaderType::POSTFX) {
if (Options::current_postfx_preset >= 0 && Options::current_postfx_preset < static_cast<int>(Options::postfx_presets.size())) {
return Options::postfx_presets[Options::current_postfx_preset].name.c_str();
}
} else {
if (Options::current_crtpi_preset >= 0 && Options::current_crtpi_preset < static_cast<int>(Options::crtpi_presets.size())) {
return Options::crtpi_presets[Options::current_crtpi_preset].name.c_str();
}
}
#endif
return "---";
}
auto Screen::nextPreset() -> bool {
#ifndef NO_SHADERS
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) { return false; }
if (!Options::video.shader.enabled) { return false; }
if (Options::video.shader.current_shader == Rendering::ShaderType::POSTFX) {
if (Options::postfx_presets.empty()) { return false; }
const int N = static_cast<int>(Options::postfx_presets.size());
Options::current_postfx_preset = (Options::current_postfx_preset + 1) % N;
Options::video.shader.current_postfx_preset_name =
Options::postfx_presets[Options::current_postfx_preset].name;
applyCurrentPostFXPreset();
} else {
if (Options::crtpi_presets.empty()) { return false; }
const int N = static_cast<int>(Options::crtpi_presets.size());
Options::current_crtpi_preset = (Options::current_crtpi_preset + 1) % N;
Options::video.shader.current_crtpi_preset_name =
Options::crtpi_presets[Options::current_crtpi_preset].name;
applyCurrentCrtPiPreset();
}
const color_t GREEN = {0x00, 0xFF, 0x80};
const color_t BLACK = {0x00, 0x00, 0x00};
const Uint32 DUR_MS = 1500;
notify(std::string("Preset: ") + getCurrentPresetName(), GREEN, BLACK, DUR_MS);
return true;
#else
return false;
#endif
}
auto Screen::prevPreset() -> bool {
#ifndef NO_SHADERS
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) { return false; }
if (!Options::video.shader.enabled) { return false; }
if (Options::video.shader.current_shader == Rendering::ShaderType::POSTFX) {
if (Options::postfx_presets.empty()) { return false; }
const int N = static_cast<int>(Options::postfx_presets.size());
Options::current_postfx_preset = (Options::current_postfx_preset - 1 + N) % N;
Options::video.shader.current_postfx_preset_name =
Options::postfx_presets[Options::current_postfx_preset].name;
applyCurrentPostFXPreset();
} else {
if (Options::crtpi_presets.empty()) { return false; }
const int N = static_cast<int>(Options::crtpi_presets.size());
Options::current_crtpi_preset = (Options::current_crtpi_preset - 1 + N) % N;
Options::video.shader.current_crtpi_preset_name =
Options::crtpi_presets[Options::current_crtpi_preset].name;
applyCurrentCrtPiPreset();
}
const color_t GREEN = {0x00, 0xFF, 0x80};
const color_t BLACK = {0x00, 0x00, 0x00};
const Uint32 DUR_MS = 1500;
notify(std::string("Preset: ") + getCurrentPresetName(), GREEN, BLACK, DUR_MS);
return true;
#else
return false;
#endif
}

View File

@@ -2,16 +2,21 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <memory> // for unique_ptr
#include <string> // for string #include <string> // for string
#include <vector> // for vector
#include "utils/utils.h" // for color_t
#ifndef NO_SHADERS
#include "core/rendering/shader_backend.hpp" // for Rendering::ShaderType
namespace Rendering {
class ShaderBackend;
}
#endif
#include "utils.h" // for color_t
class Asset;
class Text; class Text;
// Tipos de filtro
constexpr int FILTER_NEAREST = 0;
constexpr int FILTER_LINEAL = 1;
class Screen { class Screen {
public: public:
// Constantes // Constantes
@@ -24,8 +29,12 @@ class Screen {
static constexpr int WASM_RENDER_SCALE = 3; static constexpr int WASM_RENDER_SCALE = 3;
#endif #endif
// Constructor y destructor // Singleton API
Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options_t *options); static void init(SDL_Window *window, SDL_Renderer *renderer); // Crea la instancia
static void destroy(); // Libera la instancia
static auto get() -> Screen *; // Obtiene el puntero a la instancia
// Destructor (público por requisitos de `delete` desde destroy())
~Screen(); ~Screen();
// Render loop // Render loop
@@ -50,10 +59,38 @@ class Screen {
void setBorderColor(color_t color); // Cambia el color del borde void setBorderColor(color_t color); // Cambia el color del borde
// Notificaciones // Notificaciones
void initNotifications(); // Enllaça el Text de notificacions amb `Resource`. A cridar després de `Resource::init(...)`.
void notify(const std::string &text, color_t textColor, color_t outlineColor, Uint32 durationMs); // Muestra una notificación en la línea superior del canvas durante durationMs. Sobrescribe cualquier notificación activa (sin apilación). void notify(const std::string &text, color_t textColor, color_t outlineColor, Uint32 durationMs); // Muestra una notificación en la línea superior del canvas durante durationMs. Sobrescribe cualquier notificación activa (sin apilación).
void clearNotification(); // Limpia la notificación actual void clearNotification(); // Limpia la notificación actual
// GPU / shaders (post-procesado). En builds con NO_SHADERS (Emscripten) son no-op.
void initShaders(); // Crea el backend GPU si no existe y lo inicializa
void shutdownShaders(); // Libera el backend GPU
auto isGpuAccelerated() const -> bool; // true si el backend existe y reporta hardware OK
void setShaderEnabled(bool enabled); // Activa o desactiva el post-procesado (persiste)
void toggleShaderEnabled(); // Alterna post-procesado
auto isShaderEnabled() const -> bool; // Estado actual (lee options)
#ifndef NO_SHADERS
void setActiveShader(Rendering::ShaderType type); // POSTFX o CRTPI
auto getActiveShader() const -> Rendering::ShaderType;
#endif
void toggleActiveShader(); // Alterna POSTFX ↔ CRTPI
// Presets de shaders (PostFX/CrtPi). Operen sobre el shader actiu.
// Retornen false si GPU off / shaders off / llista buida (igual que a aee_plus).
auto nextPreset() -> bool;
auto prevPreset() -> bool;
auto getCurrentPresetName() const -> const char *;
void applyCurrentPostFXPreset(); // Escriu el preset PostFX actiu al backend
void applyCurrentCrtPiPreset(); // Escriu el preset CrtPi actiu al backend
private: private:
// Constructor privado (usar Screen::init)
Screen(SDL_Window *window, SDL_Renderer *renderer);
// Instancia única
static Screen *instance;
// Helpers internos de setVideoMode // Helpers internos de setVideoMode
void applyFullscreen(bool fullscreen); // SDL_SetWindowFullscreen + visibilidad del cursor void applyFullscreen(bool fullscreen); // SDL_SetWindowFullscreen + visibilidad del cursor
void applyWindowedLayout(); // Calcula windowWidth/Height/dest + SDL_SetWindowSize + SDL_SetWindowPosition void applyWindowedLayout(); // Calcula windowWidth/Height/dest + SDL_SetWindowSize + SDL_SetWindowPosition
@@ -67,20 +104,22 @@ class Screen {
// Notificaciones // Notificaciones
void renderNotification(); // Dibuja la notificación activa (si la hay) sobre el gameCanvas void renderNotification(); // Dibuja la notificación activa (si la hay) sobre el gameCanvas
#ifndef NO_SHADERS
// Aplica els paràmetres actuals del shader al backend segons options
// (pass-through si `videoShaderEnabled==false`, preset per defecte si true).
void applyShaderParams();
#endif
// Objetos y punteros // Objetos y punteros
SDL_Window *window; // Ventana de la aplicación SDL_Window *window; // Ventana de la aplicación
SDL_Renderer *renderer; // El renderizador de la ventana SDL_Renderer *renderer; // El renderizador de la ventana
Asset *asset; // Objeto con el listado de recursos
SDL_Texture *gameCanvas; // Textura para completar la ventana de juego hasta la pantalla completa SDL_Texture *gameCanvas; // Textura para completar la ventana de juego hasta la pantalla completa
options_t *options; // Variable con todas las opciones del programa
// Variables // Variables
int windowWidth; // Ancho de la pantalla o ventana int windowWidth; // Ancho de la pantalla o ventana
int windowHeight; // Alto de la pantalla o ventana int windowHeight; // Alto de la pantalla o ventana
int gameCanvasWidth; // Resolución interna del juego. Es el ancho de la textura donde se dibuja el juego int gameCanvasWidth; // Resolución interna del juego. Es el ancho de la textura donde se dibuja el juego
int gameCanvasHeight; // Resolución interna del juego. Es el alto de la textura donde se dibuja el juego int gameCanvasHeight; // Resolución interna del juego. Es el alto de la textura donde se dibuja el juego
int borderWidth; // Anchura del borde
int borderHeight; // Altura del borde
SDL_Rect dest; // Coordenadas donde se va a dibujar la textura del juego sobre la pantalla o ventana SDL_Rect dest; // Coordenadas donde se va a dibujar la textura del juego sobre la pantalla o ventana
color_t borderColor; // Color del borde añadido a la textura de juego para rellenar la pantalla color_t borderColor; // Color del borde añadido a la textura de juego para rellenar la pantalla
@@ -91,4 +130,10 @@ class Screen {
color_t notificationOutlineColor; // Color del outline color_t notificationOutlineColor; // Color del outline
Uint32 notificationEndTime; // SDL_GetTicks() hasta el cual se muestra Uint32 notificationEndTime; // SDL_GetTicks() hasta el cual se muestra
int notificationY; // Fila vertical en el canvas virtual int notificationY; // Fila vertical en el canvas virtual
#ifndef NO_SHADERS
// GPU / shaders
std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend GPU (nullptr si inactivo)
std::vector<Uint32> pixel_buffer_; // Buffer de readback del gameCanvas (ARGB8888)
#endif
}; };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,178 @@
#pragma once
#include <SDL3/SDL.h>
#include <SDL3/SDL_gpu.h>
#include "core/rendering/shader_backend.hpp"
// PostFX uniforms pushed to fragment stage each frame.
// Must match the MSL struct and GLSL uniform block layout.
// 12 floats = 48 bytes — meets Metal/Vulkan 16-byte alignment requirement.
struct PostFXUniforms {
float vignette_strength; // 0 = none, ~0.8 = subtle
float chroma_strength; // 0 = off, ~0.2 = subtle chromatic aberration
float scanline_strength; // 0 = off, 1 = full
float screen_height; // logical height in pixels (used by bleeding effect)
float mask_strength; // 0 = off, 1 = full phosphor dot mask
float gamma_strength; // 0 = off, 1 = full gamma 2.4/2.2 correction
float curvature; // 0 = flat, 1 = max barrel distortion
float bleeding; // 0 = off, 1 = max NTSC chrominance bleeding
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
float time; // seconds since SDL init (SDL_GetTicks() / 1000.0f)
float oversample; // supersampling factor (1.0 = off, 3.0 = 3×SS)
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz — keep struct at 48 bytes (3 × 16)
};
// CrtPi uniforms pushed to fragment stage each frame.
// Must match the MSL struct and GLSL uniform block layout.
// 14 fields (8 floats + 6 ints) + 2 floats (texture size) = 16 fields = 64 bytes — 4 × 16-byte alignment.
struct CrtPiUniforms {
// vec4 #0
float scanline_weight; // Ajuste gaussiano (default 6.0)
float scanline_gap_brightness; // Brillo mínimo entre scanlines (default 0.12)
float bloom_factor; // Factor brillo zonas iluminadas (default 3.5)
float input_gamma; // Gamma de entrada (default 2.4)
// vec4 #1
float output_gamma; // Gamma de salida (default 2.2)
float mask_brightness; // Brillo sub-píxeles máscara (default 0.80)
float curvature_x; // Distorsión barrel X (default 0.05)
float curvature_y; // Distorsión barrel Y (default 0.10)
// vec4 #2
int mask_type; // 0=ninguna, 1=verde/magenta, 2=RGB fósforo
int enable_scanlines; // 0 = off, 1 = on
int enable_multisample; // 0 = off, 1 = on (antialiasing analítico)
int enable_gamma; // 0 = off, 1 = on
// vec4 #3
int enable_curvature; // 0 = off, 1 = on
int enable_sharper; // 0 = off, 1 = on
float texture_width; // Ancho del canvas en píxeles (inyectado en render)
float texture_height; // Alto del canvas en píxeles (inyectado en render)
};
// Downscale uniforms pushed to the Lanczos downscale fragment stage.
// 1 int + 3 floats = 16 bytes — meets Metal/Vulkan alignment.
struct DownscaleUniforms {
int algorithm; // 0 = Lanczos2 (ventana 2), 1 = Lanczos3 (ventana 3)
float pad0;
float pad1;
float pad2;
};
namespace Rendering {
/**
* @brief Backend de shaders usando SDL3 GPU API (Metal en macOS, Vulkan/SPIR-V en Win/Linux)
*
* Reemplaza el backend OpenGL para que los shaders PostFX funcionen en macOS.
* Pipeline: Surface pixels (CPU) → SDL_GPUTransferBuffer → SDL_GPUTexture (scene)
* → PostFX render pass → swapchain → present
*/
class SDL3GPUShader : public ShaderBackend {
public:
SDL3GPUShader() = default;
~SDL3GPUShader() override;
auto init(SDL_Window* window,
SDL_Texture* texture,
const std::string& vertex_source,
const std::string& fragment_source) -> bool override;
void render() override;
void setTextureSize(float width, float height) override {}
void cleanup() final; // Libera pipeline/texturas pero mantiene el device vivo
void destroy(); // Limpieza completa (device + swapchain); llamar solo al cerrar
[[nodiscard]] auto isHardwareAccelerated() const -> bool override { return is_initialized_; }
[[nodiscard]] auto getDriverName() const -> std::string override { return driver_name_; }
// Establece el driver GPU preferido (vacío = auto). Debe llamarse antes de init().
void setPreferredDriver(const std::string& driver) override { preferred_driver_ = driver; }
// Sube píxeles ARGB8888 desde CPU; llamado antes de render()
void uploadPixels(const Uint32* pixels, int width, int height) override;
// Actualiza los parámetros de intensidad de los efectos PostFX
void setPostFXParams(const PostFXParams& p) override;
// Activa/desactiva VSync en el swapchain
void setVSync(bool vsync) override;
// Activa/desactiva escalado entero (integer scale)
void setScaleMode(bool integer_scale) override;
// Establece factor de supersampling (1 = off, 3 = 3×SS)
void setOversample(int factor) override;
// Activa/desactiva interpolación LINEAR en el upscale (false = NEAREST)
void setLinearUpscale(bool linear) override;
// Selecciona algoritmo de downscale: 0=bilinear legacy, 1=Lanczos2, 2=Lanczos3
void setDownscaleAlgo(int algo) override;
// Devuelve las dimensiones de la textura de supersampling (0,0 si SS desactivado)
[[nodiscard]] auto getSsTextureSize() const -> std::pair<int, int> override;
// Selecciona el shader de post-procesado activo (POSTFX o CRTPI)
void setActiveShader(ShaderType type) override;
// Actualiza los parámetros del shader CRT-Pi
void setCrtPiParams(const CrtPiParams& p) override;
// Devuelve el shader activo
[[nodiscard]] auto getActiveShader() const -> ShaderType override { return active_shader_; }
private:
static auto createShaderMSL(SDL_GPUDevice* device,
const char* msl_source,
const char* entrypoint,
SDL_GPUShaderStage stage,
Uint32 num_samplers,
Uint32 num_uniform_buffers) -> SDL_GPUShader*;
static auto createShaderSPIRV(SDL_GPUDevice* device,
const uint8_t* spv_code,
size_t spv_size,
const char* entrypoint,
SDL_GPUShaderStage stage,
Uint32 num_samplers,
Uint32 num_uniform_buffers) -> SDL_GPUShader*;
auto createPipeline() -> bool;
auto createCrtPiPipeline() -> bool; // Pipeline dedicado para el shader CrtPi
auto reinitTexturesAndBuffer() -> bool; // Recrea scene_texture_ y upload_buffer_
auto recreateScaledTexture(int factor) -> bool; // Recrea scaled_texture_ para factor dado
static auto calcSsFactor(float zoom) -> int; // Primer múltiplo de 3 >= zoom (mín 3)
// Devuelve el mejor present mode disponible: IMMEDIATE > MAILBOX > VSYNC
[[nodiscard]] auto bestPresentMode(bool vsync) const -> SDL_GPUPresentMode;
SDL_Window* window_ = nullptr;
SDL_GPUDevice* device_ = nullptr;
SDL_GPUGraphicsPipeline* pipeline_ = nullptr; // PostFX pass (→ swapchain o → postfx_texture_)
SDL_GPUGraphicsPipeline* crtpi_pipeline_ = nullptr; // CrtPi pass (→ swapchain directo, sin SS)
SDL_GPUGraphicsPipeline* postfx_offscreen_pipeline_ = nullptr; // PostFX → postfx_texture_ (B8G8R8A8, solo con Lanczos)
SDL_GPUGraphicsPipeline* upscale_pipeline_ = nullptr; // Upscale pass (solo con SS)
SDL_GPUGraphicsPipeline* downscale_pipeline_ = nullptr; // Lanczos downscale (solo con SS + algo > 0)
SDL_GPUTexture* scene_texture_ = nullptr; // Canvas del juego (game_width_ × game_height_)
SDL_GPUTexture* scaled_texture_ = nullptr; // Upscale target (game×factor), solo con SS
SDL_GPUTexture* postfx_texture_ = nullptr; // PostFX output a resolución escalada, solo con Lanczos
SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
SDL_GPUSampler* sampler_ = nullptr; // NEAREST
SDL_GPUSampler* linear_sampler_ = nullptr; // LINEAR
PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_strength = 0.15F, .scanline_strength = 0.7F, .screen_height = 192.0F, .pixel_scale = 1.0F, .oversample = 1.0F};
CrtPiUniforms crtpi_uniforms_{.scanline_weight = 6.0F, .scanline_gap_brightness = 0.12F, .bloom_factor = 3.5F, .input_gamma = 2.4F, .output_gamma = 2.2F, .mask_brightness = 0.80F, .curvature_x = 0.05F, .curvature_y = 0.10F, .mask_type = 2, .enable_scanlines = 1, .enable_multisample = 1, .enable_gamma = 1};
ShaderType active_shader_ = ShaderType::POSTFX; // Shader de post-procesado activo
int game_width_ = 0; // Dimensiones originales del canvas
int game_height_ = 0;
int ss_factor_ = 0; // Factor SS activo (3, 6, 9...) o 0 si SS desactivado
int oversample_ = 1; // SS on/off (1 = off, >1 = on)
int downscale_algo_ = 1; // 0 = bilinear legacy, 1 = Lanczos2, 2 = Lanczos3
std::string driver_name_;
std::string preferred_driver_; // Driver preferido; vacío = auto (SDL elige)
bool is_initialized_ = false;
bool vsync_ = true;
bool integer_scale_ = false;
bool linear_upscale_ = false; // Upscale NEAREST (false) o LINEAR (true)
};
} // namespace Rendering

View File

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

View File

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

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

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

@@ -0,0 +1,175 @@
#pragma once
#include <SDL3/SDL.h>
#include <string>
#include <utility>
namespace Rendering {
/** @brief Identificador del shader de post-procesado activo */
enum class ShaderType { POSTFX,
CRTPI };
/**
* @brief Parámetros de intensidad de los efectos PostFX
* Definido a nivel de namespace para facilitar el uso desde subclases y screen.cpp
*/
struct PostFXParams {
float vignette = 0.0F; // Intensidad de la viñeta
float scanlines = 0.0F; // Intensidad de las scanlines
float chroma = 0.0F; // Aberración cromática
float mask = 0.0F; // Máscara de fósforo RGB
float gamma = 0.0F; // Corrección gamma (blend 0=off, 1=full)
float curvature = 0.0F; // Curvatura barrel CRT
float bleeding = 0.0F; // Sangrado de color NTSC
float flicker = 0.0F; // Parpadeo de fósforo CRT ~50 Hz
};
/**
* @brief Parámetros del shader CRT-Pi (algoritmo de scanlines continuas)
* Diferente al PostFX: usa pesos gaussianos por distancia subpixel y bloom.
*/
struct CrtPiParams {
float scanline_weight{6.0F}; // Ajuste gaussiano (mayor = scanlines más estrechas)
float scanline_gap_brightness{0.12F}; // Brillo mínimo en las ranuras entre scanlines
float bloom_factor{3.5F}; // Factor de brillo para zonas iluminadas
float input_gamma{2.4F}; // Gamma de entrada (linealización)
float output_gamma{2.2F}; // Gamma de salida (codificación)
float mask_brightness{0.80F}; // Sub-píxeles tenues en la máscara de fósforo
float curvature_x{0.05F}; // Distorsión barrel eje X
float curvature_y{0.10F}; // Distorsión barrel eje Y
int mask_type{2}; // 0=ninguna, 1=verde/magenta, 2=RGB fósforo
bool enable_scanlines{true}; // Activar efecto de scanlines
bool enable_multisample{true}; // Antialiasing analítico de scanlines
bool enable_gamma{true}; // Corrección gamma
bool enable_curvature{false}; // Distorsión barrel CRT
bool enable_sharper{false}; // Submuestreo más nítido (modo SHARPER)
};
/**
* @brief Interfaz abstracta para backends de renderizado con shaders
*
* Esta interfaz define el contrato que todos los backends de shaders
* deben cumplir (OpenGL, Metal, Vulkan, etc.)
*/
class ShaderBackend {
public:
virtual ~ShaderBackend() = default;
/**
* @brief Inicializa el backend de shaders
* @param window Ventana SDL
* @param texture Textura de backbuffer a la que aplicar shaders
* @param vertex_source Código fuente del vertex shader
* @param fragment_source Código fuente del fragment shader
* @return true si la inicialización fue exitosa
*/
virtual auto init(SDL_Window* window,
SDL_Texture* texture,
const std::string& vertex_source,
const std::string& fragment_source) -> bool = 0;
/**
* @brief Renderiza la textura con los shaders aplicados
*/
virtual void render() = 0;
/**
* @brief Establece el tamaño de la textura como parámetro del shader
* @param width Ancho de la textura
* @param height Alto de la textura
*/
virtual void setTextureSize(float width, float height) = 0;
/**
* @brief Limpia y libera recursos del backend
*/
virtual void cleanup() = 0;
/**
* @brief Sube píxeles ARGB8888 desde la CPU al backend de shaders
* Usado por SDL3GPUShader para evitar pasar por SDL_Texture
*/
virtual void uploadPixels(const Uint32* /*pixels*/, int /*width*/, int /*height*/) {}
/**
* @brief Establece los parámetros de intensidad de los efectos PostFX
* @param p Struct con todos los parámetros PostFX
*/
virtual void setPostFXParams(const PostFXParams& /*p*/) {}
/**
* @brief Activa o desactiva VSync en el swapchain del GPU device
*/
virtual void setVSync(bool /*vsync*/) {}
/**
* @brief Activa o desactiva el escalado entero (integer scale)
*/
virtual void setScaleMode(bool /*integer_scale*/) {}
/**
* @brief Establece el factor de supersampling (1 = off, 3 = 3× SS)
* Con factor > 1, la textura GPU se crea a game×factor resolución y
* las scanlines se hornean en CPU (uploadPixels). El sampler usa LINEAR.
*/
virtual void setOversample(int /*factor*/) {}
/**
* @brief Activa/desactiva interpolación LINEAR en el paso de upscale (SS).
* Por defecto NEAREST (false). Solo tiene efecto con supersampling activo.
*/
virtual void setLinearUpscale(bool /*linear*/) {}
[[nodiscard]] virtual auto isLinearUpscale() const -> bool { return false; }
/**
* @brief Selecciona el algoritmo de downscale tras el PostFX (SS activo).
* 0 = bilinear legacy (comportamiento actual, sin textura intermedia),
* 1 = Lanczos2 (ventana 2, ~25 muestras), 2 = Lanczos3 (ventana 3, ~49 muestras).
*/
virtual void setDownscaleAlgo(int /*algo*/) {}
[[nodiscard]] virtual auto getDownscaleAlgo() const -> int { return 0; }
/**
* @brief Devuelve las dimensiones de la textura de supersampling.
* @return Par (ancho, alto) en píxeles; (0, 0) si SS está desactivado.
*/
[[nodiscard]] virtual auto getSsTextureSize() const -> std::pair<int, int> { return {0, 0}; }
/**
* @brief Verifica si el backend está usando aceleración por hardware
* @return true si usa aceleración (OpenGL/Metal/Vulkan)
*/
[[nodiscard]] virtual auto isHardwareAccelerated() const -> bool = 0;
/**
* @brief Nombre del driver GPU activo (p.ej. "vulkan", "metal", "direct3d12")
* @return Cadena vacía si no disponible
*/
[[nodiscard]] virtual auto getDriverName() const -> std::string { return {}; }
/**
* @brief Establece el driver GPU preferido antes de init().
* Vacío = selección automática de SDL. Implementado en SDL3GPUShader.
*/
virtual void setPreferredDriver(const std::string& /*driver*/) {}
/**
* @brief Selecciona el shader de post-procesado activo (POSTFX o CRTPI).
* Debe llamarse antes de render(). No recrea pipelines.
*/
virtual void setActiveShader(ShaderType /*type*/) {}
/**
* @brief Establece los parámetros del shader CRT-Pi.
*/
virtual void setCrtPiParams(const CrtPiParams& /*p*/) {}
/**
* @brief Devuelve el shader de post-procesado activo.
*/
[[nodiscard]] virtual auto getActiveShader() const -> ShaderType { return ShaderType::POSTFX; }
};
} // namespace Rendering

View File

@@ -1,6 +1,6 @@
#include "smartsprite.h" #include "core/rendering/smartsprite.h"
#include "movingsprite.h" // for MovingSprite #include "core/rendering/movingsprite.h" // for MovingSprite
class Texture; class Texture;
// Constructor // Constructor

View File

@@ -2,7 +2,7 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include "animatedsprite.h" // for AnimatedSprite #include "core/rendering/animatedsprite.h" // for AnimatedSprite
class Texture; class Texture;
// Clase SmartSprite // Clase SmartSprite

View File

@@ -1,50 +1,28 @@
#include "sprite.h" #include "core/rendering/sprite.h"
#include "texture.h" // for Texture #include "core/rendering/texture.h" // for Texture
// Constructor // Constructor
Sprite::Sprite(int x, int y, int w, int h, Texture *texture, SDL_Renderer *renderer) { Sprite::Sprite(int x, int y, int w, int h, Texture *texture, SDL_Renderer *renderer)
// Establece la posición X,Y del sprite : x(x),
this->x = x; y(y),
this->y = y; w(w),
h(h),
// Establece el alto y el ancho del sprite renderer(renderer),
this->w = w; texture(texture),
this->h = h; spriteClip{0, 0, w, h},
enabled(true) {
// Establece el puntero al renderizador de la ventana
this->renderer = renderer;
// Establece la textura donde están los gráficos para el sprite
this->texture = texture;
// Establece el rectangulo de donde coger la imagen
spriteClip = {0, 0, w, h};
// Inicializa variables
enabled = true;
} }
Sprite::Sprite(SDL_Rect rect, Texture *texture, SDL_Renderer *renderer) { Sprite::Sprite(SDL_Rect rect, Texture *texture, SDL_Renderer *renderer)
// Establece la posición X,Y del sprite : x(rect.x),
x = rect.x; y(rect.y),
y = rect.y; w(rect.w),
h(rect.h),
// Establece el alto y el ancho del sprite renderer(renderer),
w = rect.w; texture(texture),
h = rect.h; spriteClip{0, 0, rect.w, rect.h},
enabled(true) {
// Establece el puntero al renderizador de la ventana
this->renderer = renderer;
// Establece la textura donde están los gráficos para el sprite
this->texture = texture;
// Establece el rectangulo de donde coger la imagen
spriteClip = {0, 0, w, h};
// Inicializa variables
enabled = true;
} }
// Destructor // Destructor

View File

@@ -19,7 +19,7 @@ class Sprite {
public: public:
// Constructor // Constructor
Sprite(int x = 0, int y = 0, int w = 0, int h = 0, Texture *texture = nullptr, SDL_Renderer *renderer = nullptr); explicit Sprite(int x = 0, int y = 0, int w = 0, int h = 0, Texture *texture = nullptr, SDL_Renderer *renderer = nullptr);
Sprite(SDL_Rect rect, Texture *texture, SDL_Renderer *renderer); Sprite(SDL_Rect rect, Texture *texture, SDL_Renderer *renderer);
// Destructor // Destructor

View File

@@ -1,13 +1,13 @@
#include "text.h" #include "core/rendering/text.h"
#include <fstream> // for char_traits, basic_ostream, basic_ifstream, ope... #include <fstream> // for char_traits, basic_ostream, basic_ifstream, ope...
#include <iostream> // for cout #include <iostream> // for cout
#include <sstream> #include <sstream>
#include "sprite.h" // for Sprite #include "core/rendering/sprite.h" // for Sprite
#include "texture.h" // for Texture #include "core/rendering/texture.h" // for Texture
#include "utils.h" // for color_t #include "utils/utils.h" // for color_t
// Parser compartido: rellena un textFile_t desde cualquier istream // Parser compartido: rellena un textFile_t desde cualquier istream
static void parseTextFileStream(std::istream &rfile, textFile_t &tf) { static void parseTextFileStream(std::istream &rfile, textFile_t &tf) {
@@ -39,8 +39,10 @@ static void computeTextFileOffsets(textFile_t &tf) {
} }
// Llena una estructuta textFile_t desde un fichero // Llena una estructuta textFile_t desde un fichero
textFile_t LoadTextFile(std::string file, bool verbose) { textFile_t LoadTextFile(const std::string &file, bool verbose) {
textFile_t tf; textFile_t tf;
tf.boxWidth = 0;
tf.boxHeight = 0;
for (int i = 0; i < 128; ++i) { for (int i = 0; i < 128; ++i) {
tf.offset[i].x = 0; tf.offset[i].x = 0;
tf.offset[i].y = 0; tf.offset[i].y = 0;
@@ -65,6 +67,8 @@ textFile_t LoadTextFile(std::string file, bool verbose) {
// Llena una estructura textFile_t desde bytes en memoria // Llena una estructura textFile_t desde bytes en memoria
textFile_t LoadTextFileFromMemory(const std::vector<uint8_t> &bytes, bool verbose) { textFile_t LoadTextFileFromMemory(const std::vector<uint8_t> &bytes, bool verbose) {
textFile_t tf; textFile_t tf;
tf.boxWidth = 0;
tf.boxHeight = 0;
for (int i = 0; i < 128; ++i) { for (int i = 0; i < 128; ++i) {
tf.offset[i].x = 0; tf.offset[i].x = 0;
tf.offset[i].y = 0; tf.offset[i].y = 0;
@@ -83,7 +87,7 @@ textFile_t LoadTextFileFromMemory(const std::vector<uint8_t> &bytes, bool verbos
} }
// Constructor // Constructor
Text::Text(std::string bitmapFile, std::string textFile, SDL_Renderer *renderer) { Text::Text(const std::string &bitmapFile, const std::string &textFile, SDL_Renderer *renderer) {
// Carga los offsets desde el fichero // Carga los offsets desde el fichero
textFile_t tf = LoadTextFile(textFile); textFile_t tf = LoadTextFile(textFile);
@@ -105,7 +109,7 @@ Text::Text(std::string bitmapFile, std::string textFile, SDL_Renderer *renderer)
} }
// Constructor // Constructor
Text::Text(std::string textFile, Texture *texture, SDL_Renderer *renderer) { Text::Text(const std::string &textFile, Texture *texture, SDL_Renderer *renderer) {
// Carga los offsets desde el fichero // Carga los offsets desde el fichero
textFile_t tf = LoadTextFile(textFile); textFile_t tf = LoadTextFile(textFile);
@@ -172,7 +176,7 @@ Text::~Text() {
} }
// Escribe texto en pantalla // Escribe texto en pantalla
void Text::write(int x, int y, std::string text, int kerning, int lenght) { void Text::write(int x, int y, const std::string &text, int kerning, int lenght) {
int shift = 0; int shift = 0;
if (lenght == -1) { if (lenght == -1) {
@@ -192,14 +196,14 @@ void Text::write(int x, int y, std::string text, int kerning, int lenght) {
} }
// Escribe el texto con colores // Escribe el texto con colores
void Text::writeColored(int x, int y, std::string text, color_t color, int kerning, int lenght) { void Text::writeColored(int x, int y, const std::string &text, color_t color, int kerning, int lenght) {
sprite->getTexture()->setColor(color.r, color.g, color.b); sprite->getTexture()->setColor(color.r, color.g, color.b);
write(x, y, text, kerning, lenght); write(x, y, text, kerning, lenght);
sprite->getTexture()->setColor(255, 255, 255); sprite->getTexture()->setColor(255, 255, 255);
} }
// Escribe el texto con sombra // Escribe el texto con sombra
void Text::writeShadowed(int x, int y, std::string text, color_t color, Uint8 shadowDistance, int kerning, int lenght) { void Text::writeShadowed(int x, int y, const std::string &text, color_t color, Uint8 shadowDistance, int kerning, int lenght) {
sprite->getTexture()->setColor(color.r, color.g, color.b); sprite->getTexture()->setColor(color.r, color.g, color.b);
write(x + shadowDistance, y + shadowDistance, text, kerning, lenght); write(x + shadowDistance, y + shadowDistance, text, kerning, lenght);
sprite->getTexture()->setColor(255, 255, 255); sprite->getTexture()->setColor(255, 255, 255);
@@ -207,13 +211,13 @@ void Text::writeShadowed(int x, int y, std::string text, color_t color, Uint8 sh
} }
// Escribe el texto centrado en un punto x // Escribe el texto centrado en un punto x
void Text::writeCentered(int x, int y, std::string text, int kerning, int lenght) { void Text::writeCentered(int x, int y, const std::string &text, int kerning, int lenght) {
x -= (Text::lenght(text, kerning) / 2); x -= (Text::lenght(text, kerning) / 2);
write(x, y, text, kerning, lenght); write(x, y, text, kerning, lenght);
} }
// Escribe texto con extras // Escribe texto con extras
void Text::writeDX(Uint8 flags, int x, int y, std::string text, int kerning, color_t textColor, Uint8 shadowDistance, color_t shadowColor, int lenght) { void Text::writeDX(Uint8 flags, int x, int y, const std::string &text, int kerning, color_t textColor, Uint8 shadowDistance, color_t shadowColor, int lenght) {
const bool centered = ((flags & TXT_CENTER) == TXT_CENTER); const bool centered = ((flags & TXT_CENTER) == TXT_CENTER);
const bool shadowed = ((flags & TXT_SHADOW) == TXT_SHADOW); const bool shadowed = ((flags & TXT_SHADOW) == TXT_SHADOW);
const bool colored = ((flags & TXT_COLOR) == TXT_COLOR); const bool colored = ((flags & TXT_COLOR) == TXT_COLOR);
@@ -245,7 +249,7 @@ void Text::writeDX(Uint8 flags, int x, int y, std::string text, int kerning, col
} }
// Obtiene la longitud en pixels de una cadena // Obtiene la longitud en pixels de una cadena
int Text::lenght(std::string text, int kerning) { int Text::lenght(const std::string &text, int kerning) {
int shift = 0; int shift = 0;
for (int i = 0; i < (int)text.length(); ++i) for (int i = 0; i < (int)text.length(); ++i)

View File

@@ -7,7 +7,7 @@
#include <vector> #include <vector>
class Sprite; class Sprite;
class Texture; class Texture;
#include "utils.h" #include "utils/utils.h"
// Opciones de texto // Opciones de texto
constexpr int TXT_COLOR = 1; constexpr int TXT_COLOR = 1;
@@ -28,7 +28,7 @@ struct textFile_t {
}; };
// Llena una estructuta textFile_t desde un fichero // Llena una estructuta textFile_t desde un fichero
textFile_t LoadTextFile(std::string file, bool verbose = false); textFile_t LoadTextFile(const std::string &file, bool verbose = false);
// Llena una estructura textFile_t desde bytes en memoria // Llena una estructura textFile_t desde bytes en memoria
textFile_t LoadTextFileFromMemory(const std::vector<uint8_t> &bytes, bool verbose = false); textFile_t LoadTextFileFromMemory(const std::vector<uint8_t> &bytes, bool verbose = false);
@@ -48,8 +48,8 @@ class Text {
public: public:
// Constructor // Constructor
Text(std::string bitmapFile, std::string textFile, SDL_Renderer *renderer); Text(const std::string &bitmapFile, const std::string &textFile, SDL_Renderer *renderer);
Text(std::string textFile, Texture *texture, SDL_Renderer *renderer); Text(const std::string &textFile, Texture *texture, SDL_Renderer *renderer);
Text(textFile_t *textFile, Texture *texture, SDL_Renderer *renderer); Text(textFile_t *textFile, Texture *texture, SDL_Renderer *renderer);
// Constructor desde bytes en memoria: comparte ownership del texture (no lo libera) // Constructor desde bytes en memoria: comparte ownership del texture (no lo libera)
@@ -58,23 +58,27 @@ class Text {
// Destructor // Destructor
~Text(); ~Text();
// No copiable (gestiona memoria dinámica)
Text(const Text &) = delete;
Text &operator=(const Text &) = delete;
// Escribe el texto en pantalla // Escribe el texto en pantalla
void write(int x, int y, std::string text, int kerning = 1, int lenght = -1); void write(int x, int y, const std::string &text, int kerning = 1, int lenght = -1);
// Escribe el texto con colores // Escribe el texto con colores
void writeColored(int x, int y, std::string text, color_t color, int kerning = 1, int lenght = -1); void writeColored(int x, int y, const std::string &text, color_t color, int kerning = 1, int lenght = -1);
// Escribe el texto con sombra // Escribe el texto con sombra
void writeShadowed(int x, int y, std::string text, color_t color, Uint8 shadowDistance = 1, int kerning = 1, int lenght = -1); void writeShadowed(int x, int y, const std::string &text, color_t color, Uint8 shadowDistance = 1, int kerning = 1, int lenght = -1);
// Escribe el texto centrado en un punto x // Escribe el texto centrado en un punto x
void writeCentered(int x, int y, std::string text, int kerning = 1, int lenght = -1); void writeCentered(int x, int y, const std::string &text, int kerning = 1, int lenght = -1);
// Escribe texto con extras // Escribe texto con extras
void writeDX(Uint8 flags, int x, int y, std::string text, int kerning = 1, color_t textColor = color_t(255, 255, 255), Uint8 shadowDistance = 1, color_t shadowColor = color_t(0, 0, 0), int lenght = -1); void writeDX(Uint8 flags, int x, int y, const std::string &text, int kerning = 1, color_t textColor = color_t(255, 255, 255), Uint8 shadowDistance = 1, color_t shadowColor = color_t(0, 0, 0), int lenght = -1);
// Obtiene la longitud en pixels de una cadena // Obtiene la longitud en pixels de una cadena
int lenght(std::string text, int kerning = 1); int lenght(const std::string &text, int kerning = 1);
// Devuelve el valor de la variable // Devuelve el valor de la variable
int getCharacterSize(); int getCharacterSize();

View File

@@ -1,5 +1,5 @@
#include "texture.h" #include "core/rendering/texture.h"
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <stdlib.h> // for exit #include <stdlib.h> // for exit
@@ -15,30 +15,25 @@ void Texture::setGlobalScaleMode(SDL_ScaleMode mode) {
} }
// Constructor // Constructor
Texture::Texture(SDL_Renderer *renderer, std::string path, bool verbose) { Texture::Texture(SDL_Renderer *renderer, const std::string &path, bool verbose)
// Copia punteros : texture(nullptr),
this->renderer = renderer; renderer(renderer),
this->path = path; width(0),
height(0),
// Inicializa path(path) {
texture = nullptr;
width = 0;
height = 0;
// Carga el fichero en la textura // Carga el fichero en la textura
if (path != "") { if (!path.empty()) {
loadFromFile(path, renderer, verbose); loadFromFile(path, renderer, verbose);
} }
} }
// Constructor desde bytes // Constructor desde bytes
Texture::Texture(SDL_Renderer *renderer, const std::vector<uint8_t> &bytes, bool verbose) { Texture::Texture(SDL_Renderer *renderer, const std::vector<uint8_t> &bytes, bool verbose)
this->renderer = renderer; : texture(nullptr),
this->path = ""; renderer(renderer),
texture = nullptr; width(0),
width = 0; height(0),
height = 0; path("") {
if (!bytes.empty()) { if (!bytes.empty()) {
loadFromMemory(bytes.data(), bytes.size(), renderer, verbose); loadFromMemory(bytes.data(), bytes.size(), renderer, verbose);
} }
@@ -53,7 +48,7 @@ Texture::~Texture() {
// Helper: convierte píxeles RGBA decodificados por stbi en SDL_Texture // Helper: convierte píxeles RGBA decodificados por stbi en SDL_Texture
static SDL_Texture *createTextureFromPixels(SDL_Renderer *renderer, unsigned char *data, int w, int h, int *out_w, int *out_h) { static SDL_Texture *createTextureFromPixels(SDL_Renderer *renderer, unsigned char *data, int w, int h, int *out_w, int *out_h) {
const int pitch = 4 * w; const int pitch = 4 * w;
SDL_Surface *loadedSurface = SDL_CreateSurfaceFrom(w, h, SDL_PIXELFORMAT_RGBA32, (void *)data, pitch); SDL_Surface *loadedSurface = SDL_CreateSurfaceFrom(w, h, SDL_PIXELFORMAT_RGBA32, static_cast<void *>(data), pitch);
if (loadedSurface == nullptr) { if (loadedSurface == nullptr) {
return nullptr; return nullptr;
} }
@@ -68,7 +63,7 @@ static SDL_Texture *createTextureFromPixels(SDL_Renderer *renderer, unsigned cha
} }
// Carga una imagen desde un fichero // Carga una imagen desde un fichero
bool Texture::loadFromFile(std::string path, SDL_Renderer *renderer, bool verbose) { bool Texture::loadFromFile(const std::string &path, SDL_Renderer *renderer, bool verbose) {
const std::string filename = path.substr(path.find_last_of("\\/") + 1); const std::string filename = path.substr(path.find_last_of("\\/") + 1);
int req_format = STBI_rgb_alpha; int req_format = STBI_rgb_alpha;
int w, h, orig_format; int w, h, orig_format;

View File

@@ -24,7 +24,7 @@ class Texture {
static void setGlobalScaleMode(SDL_ScaleMode mode); static void setGlobalScaleMode(SDL_ScaleMode mode);
// Constructor // Constructor
Texture(SDL_Renderer *renderer, std::string path = "", bool verbose = false); explicit Texture(SDL_Renderer *renderer, const std::string &path = "", bool verbose = false);
// Constructor desde bytes (PNG en memoria) // Constructor desde bytes (PNG en memoria)
Texture(SDL_Renderer *renderer, const std::vector<uint8_t> &bytes, bool verbose = false); Texture(SDL_Renderer *renderer, const std::vector<uint8_t> &bytes, bool verbose = false);
@@ -33,7 +33,7 @@ class Texture {
~Texture(); ~Texture();
// Carga una imagen desde un fichero // Carga una imagen desde un fichero
bool loadFromFile(std::string path, SDL_Renderer *renderer, bool verbose = false); bool loadFromFile(const std::string &path, SDL_Renderer *renderer, bool verbose = false);
// Carga una imagen desde bytes en memoria // Carga una imagen desde bytes en memoria
bool loadFromMemory(const uint8_t *data, size_t size, SDL_Renderer *renderer, bool verbose = false); bool loadFromMemory(const uint8_t *data, size_t size, SDL_Renderer *renderer, bool verbose = false);

View File

@@ -1,25 +1,22 @@
#include "writer.h" #include "core/rendering/writer.h"
#include "text.h" // for Text #include "core/rendering/text.h" // for Text
// Constructor // Constructor
Writer::Writer(Text *text) { Writer::Writer(Text *text)
// Copia los punteros : text(text),
this->text = text; posX(0),
posY(0),
// Inicializa variables kerning(0),
posX = 0; caption(""),
posY = 0; speed(0),
kerning = 0; writingCounter(0),
caption = ""; index(0),
speed = 0; lenght(0),
writingCounter = 0; completed(false),
index = 0; enabled(false),
lenght = 0; enabledCounter(0),
completed = false; finished(false) {
enabled = false;
enabledCounter = 0;
finished = false;
} }
// Actualiza el objeto // Actualiza el objeto
@@ -73,7 +70,7 @@ void Writer::setKerning(int value) {
} }
// Establece el valor de la variable // Establece el valor de la variable
void Writer::setCaption(std::string text) { void Writer::setCaption(const std::string &text) {
caption = text; caption = text;
lenght = text.length(); lenght = text.length();
} }

View File

@@ -25,7 +25,7 @@ class Writer {
public: public:
// Constructor // Constructor
Writer(Text *text); explicit Writer(Text *text);
// Actualiza el objeto // Actualiza el objeto
void update(); void update();
@@ -43,7 +43,7 @@ class Writer {
void setKerning(int value); void setKerning(int value);
// Establece el valor de la variable // Establece el valor de la variable
void setCaption(std::string text); void setCaption(const std::string &text);
// Establece el valor de la variable // Establece el valor de la variable
void setSpeed(int value); void setSpeed(int value);

View File

@@ -1,21 +1,38 @@
#include "asset.h" #include "core/resources/asset.h"
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <stddef.h> // for size_t #include <stddef.h> // for size_t
#include <iostream> // for basic_ostream, operator<<, cout, endl #include <iostream> // for basic_ostream, operator<<, cout, endl
#include "resource_helper.h" #include "core/resources/resource_helper.h"
// Instancia única
Asset *Asset::instance = nullptr;
// Singleton API
void Asset::init(const std::string &executablePath) {
Asset::instance = new Asset(executablePath);
}
void Asset::destroy() {
delete Asset::instance;
Asset::instance = nullptr;
}
auto Asset::get() -> Asset * {
return Asset::instance;
}
// Constructor // Constructor
Asset::Asset(std::string executablePath) { Asset::Asset(const std::string &executablePath)
this->executablePath = executablePath.substr(0, executablePath.find_last_of("\\/")); : longestName(0),
longestName = 0; executablePath(executablePath.substr(0, executablePath.find_last_of("\\/"))),
verbose = true; verbose(true) {
} }
// Añade un elemento a la lista // Añade un elemento a la lista
void Asset::add(std::string file, enum assetType type, bool required, bool absolute) { void Asset::add(const std::string &file, enum assetType type, bool required, bool absolute) {
item_t temp; item_t temp;
temp.file = absolute ? file : executablePath + file; temp.file = absolute ? file : executablePath + file;
temp.type = type; temp.type = type;
@@ -27,8 +44,8 @@ void Asset::add(std::string file, enum assetType type, bool required, bool absol
} }
// Devuelve el fichero de un elemento de la lista a partir de una cadena // Devuelve el fichero de un elemento de la lista a partir de una cadena
std::string Asset::get(std::string text) { std::string Asset::get(const std::string &text) {
for (auto f : fileList) { for (const auto &f : fileList) {
const size_t lastIndex = f.file.find_last_of("/") + 1; const size_t lastIndex = f.file.find_last_of("/") + 1;
const std::string file = f.file.substr(lastIndex, std::string::npos); const std::string file = f.file.substr(lastIndex, std::string::npos);
@@ -59,7 +76,7 @@ bool Asset::check() {
// Comprueba si hay ficheros de ese tipo // Comprueba si hay ficheros de ese tipo
bool any = false; bool any = false;
for (auto f : fileList) { for (const auto &f : fileList) {
if ((f.required) && (f.type == type)) { if ((f.required) && (f.type == type)) {
any = true; any = true;
} }
@@ -71,7 +88,7 @@ bool Asset::check() {
std::cout << "\n>> " << getTypeName(type).c_str() << " FILES" << std::endl; std::cout << "\n>> " << getTypeName(type).c_str() << " FILES" << std::endl;
} }
for (auto f : fileList) { for (const auto &f : fileList) {
if ((f.required) && (f.type == type)) { if ((f.required) && (f.type == type)) {
success &= checkFile(f.file); success &= checkFile(f.file);
} }
@@ -94,7 +111,7 @@ bool Asset::check() {
} }
// Comprueba que existe un fichero // Comprueba que existe un fichero
bool Asset::checkFile(std::string path) { bool Asset::checkFile(const std::string &path) {
bool success = false; bool success = false;
std::string result = "ERROR"; std::string result = "ERROR";

View File

@@ -34,20 +34,28 @@ class Asset {
bool verbose; // Indica si ha de mostrar información por pantalla bool verbose; // Indica si ha de mostrar información por pantalla
// Comprueba que existe un fichero // Comprueba que existe un fichero
bool checkFile(std::string executablePath); bool checkFile(const std::string &executablePath);
// Devuelve el nombre del tipo de recurso // Devuelve el nombre del tipo de recurso
std::string getTypeName(int type); std::string getTypeName(int type);
// Constructor privado (usar Asset::init)
explicit Asset(const std::string &path);
// Instancia única
static Asset *instance;
public: public:
// Constructor // Singleton API
Asset(std::string path); static void init(const std::string &executablePath); // Crea la instancia
static void destroy(); // Libera la instancia
static auto get() -> Asset *; // Obtiene el puntero a la instancia
// Añade un elemento a la lista // Añade un elemento a la lista
void add(std::string file, enum assetType type, bool required = true, bool absolute = false); void add(const std::string &file, enum assetType type, bool required = true, bool absolute = false);
// Devuelve un elemento de la lista a partir de una cadena // Devuelve un elemento de la lista a partir de una cadena
std::string get(std::string text); std::string get(const std::string &text);
// Devuelve toda la lista de items registrados // Devuelve toda la lista de items registrados
const std::vector<item_t> &getAll() const { return fileList; } const std::vector<item_t> &getAll() const { return fileList; }

View File

@@ -1,16 +1,19 @@
#include "resource.h" #include "core/resources/resource.h"
#include <algorithm> #include <algorithm>
#include <filesystem> #include <filesystem>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include "asset.h" #include "core/audio/jail_audio.hpp"
#include "jail_audio.hpp" #include "core/rendering/text.h"
#include "menu.h" #include "core/rendering/texture.h"
#include "resource_helper.h" #include "core/resources/asset.h"
#include "text.h" #include "core/resources/resource_helper.h"
#include "texture.h" #include "game/ui/menu.h"
// Nota: Asset::get() e Input::get() se consultan en preloadAll y al construir
// los menús; no se guardan punteros en el objeto Resource.
Resource *Resource::instance_ = nullptr; Resource *Resource::instance_ = nullptr;
@@ -25,9 +28,9 @@ static std::string stem(const std::string &path) {
return b.substr(0, dot); return b.substr(0, dot);
} }
void Resource::init(SDL_Renderer *renderer, Asset *asset, Input *input) { void Resource::init(SDL_Renderer *renderer) {
if (instance_ == nullptr) { if (instance_ == nullptr) {
instance_ = new Resource(renderer, asset, input); instance_ = new Resource(renderer);
instance_->preloadAll(); instance_->preloadAll();
} }
} }
@@ -41,10 +44,8 @@ Resource *Resource::get() {
return instance_; return instance_;
} }
Resource::Resource(SDL_Renderer *renderer, Asset *asset, Input *input) Resource::Resource(SDL_Renderer *renderer)
: renderer_(renderer), : renderer_(renderer) {}
asset_(asset),
input_(input) {}
Resource::~Resource() { Resource::~Resource() {
for (auto &[name, m] : menus_) delete m; for (auto &[name, m] : menus_) delete m;
@@ -64,7 +65,7 @@ Resource::~Resource() {
} }
void Resource::preloadAll() { void Resource::preloadAll() {
const auto &items = asset_->getAll(); const auto &items = Asset::get()->getAll();
// Pass 1: texturas, sonidos, músicas, animaciones (raw lines), demo, lenguajes // Pass 1: texturas, sonidos, músicas, animaciones (raw lines), demo, lenguajes
for (const auto &it : items) { for (const auto &it : items) {
@@ -155,7 +156,7 @@ void Resource::preloadAll() {
if (bname.size() < 4 || bname.substr(bname.size() - 4) != ".men") continue; if (bname.size() < 4 || bname.substr(bname.size() - 4) != ".men") continue;
auto bytes = ResourceHelper::loadFile(it.file); auto bytes = ResourceHelper::loadFile(it.file);
if (bytes.empty()) continue; if (bytes.empty()) continue;
Menu *m = new Menu(renderer_, asset_, input_, ""); Menu *m = new Menu(renderer_, "");
m->loadFromBytes(bytes, bname); m->loadFromBytes(bytes, bname);
const std::string s = stem(it.file); const std::string s = stem(it.file);
menus_[s] = m; menus_[s] = m;

View File

@@ -7,8 +7,6 @@
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
class Asset;
class Input;
class Menu; class Menu;
class Text; class Text;
class Texture; class Texture;
@@ -19,7 +17,7 @@ struct JA_Sound_t;
// Singleton inicializado desde Director; las escenas consultan handles via get*(). // Singleton inicializado desde Director; las escenas consultan handles via get*().
class Resource { class Resource {
public: public:
static void init(SDL_Renderer *renderer, Asset *asset, Input *input); static void init(SDL_Renderer *renderer);
static void destroy(); static void destroy();
static Resource *get(); static Resource *get();
@@ -32,14 +30,12 @@ class Resource {
const std::vector<uint8_t> &getDemoBytes() const { return demoBytes_; } const std::vector<uint8_t> &getDemoBytes() const { return demoBytes_; }
private: private:
Resource(SDL_Renderer *renderer, Asset *asset, Input *input); explicit Resource(SDL_Renderer *renderer);
~Resource(); ~Resource();
void preloadAll(); void preloadAll();
SDL_Renderer *renderer_; SDL_Renderer *renderer_;
Asset *asset_;
Input *input_;
std::unordered_map<std::string, Texture *> textures_; std::unordered_map<std::string, Texture *> textures_;
std::unordered_map<std::string, JA_Sound_t *> sounds_; std::unordered_map<std::string, JA_Sound_t *> sounds_;

View File

@@ -1,11 +1,11 @@
#include "resource_helper.h" #include "core/resources/resource_helper.h"
#include <algorithm> #include <algorithm>
#include <cstddef> #include <cstddef>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include "resource_loader.h" #include "core/resources/resource_loader.h"
namespace ResourceHelper { namespace ResourceHelper {
static bool resource_system_initialized = false; static bool resource_system_initialized = false;

View File

@@ -1,11 +1,11 @@
#include "resource_loader.h" #include "core/resources/resource_loader.h"
#include <algorithm> #include <algorithm>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include "resource_pack.h" #include "core/resources/resource_pack.h"
std::unique_ptr<ResourceLoader> ResourceLoader::instance = nullptr; std::unique_ptr<ResourceLoader> ResourceLoader::instance = nullptr;

View File

@@ -1,10 +1,11 @@
#include "resource_pack.h" #include "core/resources/resource_pack.h"
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <numeric>
#include <utility> #include <utility>
const std::string ResourcePack::DEFAULT_ENCRYPT_KEY = "CCRS_RESOURCES__2026"; const std::string ResourcePack::DEFAULT_ENCRYPT_KEY = "CCRS_RESOURCES__2026";
@@ -17,11 +18,8 @@ ResourcePack::~ResourcePack() {
} }
uint32_t ResourcePack::calculateChecksum(const std::vector<uint8_t>& data) { uint32_t ResourcePack::calculateChecksum(const std::vector<uint8_t>& data) {
uint32_t checksum = 0x12345678; return std::accumulate(data.begin(), data.end(), uint32_t(0x12345678),
for (unsigned char i : data) { [](uint32_t acc, uint8_t b) { return ((acc << 5) + acc) + b; });
checksum = ((checksum << 5) + checksum) + i;
}
return checksum;
} }
void ResourcePack::encryptData(std::vector<uint8_t>& data, const std::string& key) { void ResourcePack::encryptData(std::vector<uint8_t>& data, const std::string& key) {

View File

@@ -0,0 +1,652 @@
#include "core/system/director.h"
#include <SDL3/SDL.h>
#include <errno.h> // for errno, EEXIST, EACCES, ENAMETOO...
#include <stdio.h> // for printf, perror
#include <string.h> // for strcmp
#ifndef __EMSCRIPTEN__
#include <sys/stat.h> // for mkdir, stat, S_IRWXU
#include <unistd.h> // for getuid
#endif
#include <cstdlib> // for exit, EXIT_FAILURE, srand
#include <filesystem>
#include <fstream> // for basic_ostream, operator<<, basi...
#include <iostream> // for cout
#include <memory>
#include <string> // for basic_string, operator+, char_t...
#include <vector> // for vector
#include "core/audio/jail_audio.hpp" // for JA_Init
#include "core/input/input.h" // for Input, inputs_e, INPUT_USE_GAME...
#include "core/input/mouse.hpp" // for Mouse::handleEvent, Mouse::upda...
#include "core/locale/lang.h" // for Lang, MAX_LANGUAGES, ba_BA, en_UK
#include "core/rendering/screen.h" // for Screen
#include "core/rendering/texture.h" // for Texture
#include "core/resources/asset.h" // for Asset, assetType
#include "core/resources/resource.h"
#include "core/resources/resource_helper.h"
#include "game/defaults.hpp" // for SECTION_PROG_LOGO, GAMECANVAS_H...
#include "game/game.h" // for Game
#include "game/options.hpp" // for Options::init, loadFromFile...
#include "game/scenes/intro.h" // for Intro
#include "game/scenes/logo.h" // for Logo
#include "game/scenes/title.h" // for Title
#include "utils/utils.h" // for input_t, boolToString
#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)
#include <pwd.h>
#endif
// Constructor
Director::Director(int argc, const char *argv[]) {
std::cout << "Game start" << std::endl;
// Inicializa variables
section = new section_t();
section->name = SECTION_PROG_LOGO;
// Inicializa las opciones del programa (defaults + dispositivos d'entrada)
Options::init();
// Comprueba los parametros del programa (pot activar console)
checkProgramArguments(argc, argv);
// Crea la carpeta del sistema donde guardar datos
createSystemFolder("jailgames");
#ifndef DEBUG
createSystemFolder("jailgames/coffee_crisis");
#else
createSystemFolder("jailgames/coffee_crisis_debug");
#endif
// Estableix el fitxer de configuració i carrega les opcions (o crea el
// YAML amb defaults si no existeix).
Options::setConfigFile(systemFolder + "/config.yaml");
Options::loadFromFile();
// Presets de shaders (creats amb defaults si no existeixen).
Options::setPostFXFile(systemFolder + "/postfx.yaml");
Options::loadPostFXFromFile();
Options::setCrtPiFile(systemFolder + "/crtpi.yaml");
Options::loadCrtPiFromFile();
// Inicializa el sistema de recursos (pack + fallback).
// En wasm siempre se usa filesystem (MEMFS) porque el propio --preload-file
// de emscripten ya empaqueta data/ — no hay resources.pack.
{
#if defined(__EMSCRIPTEN__)
const bool enable_fallback = true;
#elif defined(RELEASE_BUILD)
const bool enable_fallback = false;
#else
const bool enable_fallback = true;
#endif
if (!ResourceHelper::initializeResourceSystem("resources.pack", enable_fallback)) {
std::cerr << "Fatal: resource system init failed (missing resources.pack?)" << std::endl;
exit(EXIT_FAILURE);
}
}
// Crea el objeto que controla los ficheros de recursos
Asset::init(executablePath);
Asset::get()->setVerbose(Options::settings.console);
// Si falta algún fichero no inicia el programa
if (!setFileList()) {
exit(EXIT_FAILURE);
}
// Inicializa SDL
initSDL();
// Inicializa JailAudio
initJailAudio();
// Establece el modo de escalado de texturas
Texture::setGlobalScaleMode(Options::video.scale_mode);
// Crea los objetos
Lang::init();
Lang::get()->setLang(Options::settings.language);
#ifdef __EMSCRIPTEN__
Input::init("/gamecontrollerdb.txt");
#else
{
const std::string binDir = std::filesystem::path(executablePath).parent_path().string();
#ifdef MACOS_BUNDLE
Input::init(binDir + "/../Resources/gamecontrollerdb.txt");
#else
Input::init(binDir + "/gamecontrollerdb.txt");
#endif
}
#endif
initInput();
// Orden importante: Screen + initShaders ANTES de Resource::init.
// Si `Resource::init` se ejecuta primero, carga ~100 texturas vía
// `SDL_CreateTexture` que dejan el SDL_Renderer con el swapchain en un
// estado que hace crashear al driver Vulkan cuando después `initShaders`
// intenta reclamar la ventana para el dispositivo SDL3 GPU.
//
// Por eso el constructor de Screen NO carga notificationText desde
// Resource; se enlaza después vía `Screen::get()->initNotifications()`.
Screen::init(window, renderer);
#ifndef NO_SHADERS
if (Options::video.gpu.acceleration) {
Screen::get()->initShaders();
}
#endif
// Ahora sí, precarga todos los recursos en memoria (texturas, sonidos,
// música, ...). Vivirán durante toda la vida de la app.
Resource::init(renderer);
// Completa el enlazado de Screen con recursos que necesitan Resource
// inicializado (actualmente sólo el Text de las notificaciones).
Screen::get()->initNotifications();
activeSection = ActiveSection::None;
}
Director::~Director() {
Options::saveToFile();
// Libera las secciones primero: sus destructores tocan audio/render SDL
// (p.ej. Intro::~Intro llama a JA_DeleteMusic) y deben ejecutarse antes
// de SDL_Quit().
logo.reset();
intro.reset();
title.reset();
game.reset();
// Screen puede tener referencias a Text propiedad de Resource: destruir
// Screen antes que Resource.
Screen::destroy();
// Libera todos los recursos precargados antes de cerrar SDL.
Resource::destroy();
Asset::destroy();
Input::destroy();
Lang::destroy();
delete section;
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
ResourceHelper::shutdownResourceSystem();
std::cout << "\nBye!" << std::endl;
}
// Inicializa el objeto input
void Director::initInput() {
// Establece si ha de mostrar mensajes
Input::get()->setVerbose(Options::settings.console);
// Busca si hay un mando conectado
Input::get()->discoverGameController();
// Teclado - Movimiento del jugador
Input::get()->bindKey(input_up, SDL_SCANCODE_UP);
Input::get()->bindKey(input_down, SDL_SCANCODE_DOWN);
Input::get()->bindKey(input_left, SDL_SCANCODE_LEFT);
Input::get()->bindKey(input_right, SDL_SCANCODE_RIGHT);
Input::get()->bindKey(input_fire_left, SDL_SCANCODE_Q);
Input::get()->bindKey(input_fire_center, SDL_SCANCODE_W);
Input::get()->bindKey(input_fire_right, SDL_SCANCODE_E);
// Teclado - Otros
Input::get()->bindKey(input_accept, SDL_SCANCODE_RETURN);
Input::get()->bindKey(input_cancel, SDL_SCANCODE_ESCAPE);
Input::get()->bindKey(input_pause, SDL_SCANCODE_ESCAPE);
Input::get()->bindKey(input_exit, SDL_SCANCODE_ESCAPE);
Input::get()->bindKey(input_window_dec_size, SDL_SCANCODE_F1);
Input::get()->bindKey(input_window_inc_size, SDL_SCANCODE_F2);
Input::get()->bindKey(input_window_fullscreen, SDL_SCANCODE_F3);
Input::get()->bindKey(input_prev_preset, SDL_SCANCODE_F8);
Input::get()->bindKey(input_next_preset, SDL_SCANCODE_F9);
Input::get()->bindKey(input_toggle_shader, SDL_SCANCODE_F10);
Input::get()->bindKey(input_toggle_shader_type, SDL_SCANCODE_F11);
// Mando - Movimiento del jugador
Input::get()->bindGameControllerButton(input_up, SDL_GAMEPAD_BUTTON_DPAD_UP);
Input::get()->bindGameControllerButton(input_down, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
Input::get()->bindGameControllerButton(input_left, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
Input::get()->bindGameControllerButton(input_right, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
Input::get()->bindGameControllerButton(input_fire_left, SDL_GAMEPAD_BUTTON_WEST);
Input::get()->bindGameControllerButton(input_fire_center, SDL_GAMEPAD_BUTTON_NORTH);
Input::get()->bindGameControllerButton(input_fire_right, SDL_GAMEPAD_BUTTON_EAST);
// Mando - Otros
// SOUTH queda sin asignar para evitar salidas accidentales: pausa/cancel se hace con START/BACK.
Input::get()->bindGameControllerButton(input_accept, SDL_GAMEPAD_BUTTON_EAST);
#ifdef GAME_CONSOLE
Input::get()->bindGameControllerButton(input_pause, SDL_GAMEPAD_BUTTON_BACK);
Input::get()->bindGameControllerButton(input_exit, SDL_GAMEPAD_BUTTON_START);
#else
Input::get()->bindGameControllerButton(input_pause, SDL_GAMEPAD_BUTTON_START);
Input::get()->bindGameControllerButton(input_exit, SDL_GAMEPAD_BUTTON_BACK);
#endif
}
// Inicializa JailAudio
void Director::initJailAudio() {
JA_Init(48000, SDL_AUDIO_S16, 2);
}
// Arranca SDL y crea la ventana
bool Director::initSDL() {
// Indicador de éxito
bool success = true;
// Inicializa SDL
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD)) {
if (Options::settings.console) {
std::cout << "SDL could not initialize!\nSDL Error: " << SDL_GetError() << std::endl;
}
success = false;
} else {
// Inicia el generador de numeros aleatorios
std::srand(static_cast<unsigned int>(SDL_GetTicks()));
// Crea la ventana
window = SDL_CreateWindow(
Options::window.caption.c_str(),
GAMECANVAS_WIDTH * Options::window.zoom,
GAMECANVAS_HEIGHT * Options::window.zoom,
0);
if (window == nullptr) {
if (Options::settings.console) {
std::cout << "Window could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
}
success = false;
} else {
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
// Crea un renderizador para la ventana
renderer = SDL_CreateRenderer(window, NULL);
if (renderer == nullptr) {
if (Options::settings.console) {
std::cout << "Renderer could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
}
success = false;
} else {
// Modo de blending por defecto (consistente con CCAE):
// permite alpha blending para fades y notificaciones.
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
// Activa vsync si es necesario
if (Options::video.vsync) {
SDL_SetRenderVSync(renderer, 1);
}
// Inicializa el color de renderizado
SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);
// Establece el tamaño del buffer de renderizado
SDL_SetRenderLogicalPresentation(renderer, GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX);
// Establece el modo de mezcla
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
}
}
}
if (Options::settings.console) {
std::cout << std::endl;
}
return success;
}
// Crea el indice de ficheros
bool Director::setFileList() {
#ifdef MACOS_BUNDLE
const std::string prefix = "/../Resources";
#else
const std::string prefix = "";
#endif
// Ficheros de configuración
Asset::get()->add(systemFolder + "/score.bin", t_data, false, true);
Asset::get()->add(prefix + "/data/demo/demo.bin", t_data);
// Musicas
Asset::get()->add(prefix + "/data/music/intro.ogg", t_music);
Asset::get()->add(prefix + "/data/music/playing.ogg", t_music);
Asset::get()->add(prefix + "/data/music/title.ogg", t_music);
// Sonidos
Asset::get()->add(prefix + "/data/sound/balloon.wav", t_sound);
Asset::get()->add(prefix + "/data/sound/bubble1.wav", t_sound);
Asset::get()->add(prefix + "/data/sound/bubble2.wav", t_sound);
Asset::get()->add(prefix + "/data/sound/bubble3.wav", t_sound);
Asset::get()->add(prefix + "/data/sound/bubble4.wav", t_sound);
Asset::get()->add(prefix + "/data/sound/bullet.wav", t_sound);
Asset::get()->add(prefix + "/data/sound/coffeeout.wav", t_sound);
Asset::get()->add(prefix + "/data/sound/hiscore.wav", t_sound);
Asset::get()->add(prefix + "/data/sound/itemdrop.wav", t_sound);
Asset::get()->add(prefix + "/data/sound/itempickup.wav", t_sound);
Asset::get()->add(prefix + "/data/sound/menu_move.wav", t_sound);
Asset::get()->add(prefix + "/data/sound/menu_select.wav", t_sound);
Asset::get()->add(prefix + "/data/sound/player_collision.wav", t_sound);
Asset::get()->add(prefix + "/data/sound/stage_change.wav", t_sound);
Asset::get()->add(prefix + "/data/sound/title.wav", t_sound);
Asset::get()->add(prefix + "/data/sound/clock.wav", t_sound);
Asset::get()->add(prefix + "/data/sound/powerball.wav", t_sound);
// Texturas
Asset::get()->add(prefix + "/data/gfx/balloon1.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/balloon1.ani", t_data);
Asset::get()->add(prefix + "/data/gfx/balloon2.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/balloon2.ani", t_data);
Asset::get()->add(prefix + "/data/gfx/balloon3.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/balloon3.ani", t_data);
Asset::get()->add(prefix + "/data/gfx/balloon4.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/balloon4.ani", t_data);
Asset::get()->add(prefix + "/data/gfx/bullet.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/game_buildings.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/game_clouds.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/game_grass.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/game_power_meter.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/game_sky_colors.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/game_text.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/intro.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/logo.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/menu_game_over.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/menu_game_over_end.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/item_points1_disk.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/item_points1_disk.ani", t_data);
Asset::get()->add(prefix + "/data/gfx/item_points2_gavina.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/item_points2_gavina.ani", t_data);
Asset::get()->add(prefix + "/data/gfx/item_points3_pacmar.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/item_points3_pacmar.ani", t_data);
Asset::get()->add(prefix + "/data/gfx/item_clock.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/item_clock.ani", t_data);
Asset::get()->add(prefix + "/data/gfx/item_coffee.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/item_coffee.ani", t_data);
Asset::get()->add(prefix + "/data/gfx/item_coffee_machine.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/item_coffee_machine.ani", t_data);
Asset::get()->add(prefix + "/data/gfx/title_bg_tile.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/title_coffee.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/title_crisis.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/title_dust.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/title_dust.ani", t_data);
Asset::get()->add(prefix + "/data/gfx/title_gradient.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/player_head.ani", t_data);
Asset::get()->add(prefix + "/data/gfx/player_body.ani", t_data);
Asset::get()->add(prefix + "/data/gfx/player_legs.ani", t_data);
Asset::get()->add(prefix + "/data/gfx/player_death.ani", t_data);
Asset::get()->add(prefix + "/data/gfx/player_fire.ani", t_data);
Asset::get()->add(prefix + "/data/gfx/player_bal1_head.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/player_bal1_body.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/player_bal1_legs.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/player_bal1_death.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/player_bal1_fire.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/player_arounder_head.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/player_arounder_body.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/player_arounder_legs.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/player_arounder_death.png", t_bitmap);
Asset::get()->add(prefix + "/data/gfx/player_arounder_fire.png", t_bitmap);
// Fuentes
Asset::get()->add(prefix + "/data/font/8bithud.png", t_font);
Asset::get()->add(prefix + "/data/font/8bithud.txt", t_font);
Asset::get()->add(prefix + "/data/font/nokia.png", t_font);
Asset::get()->add(prefix + "/data/font/nokia_big2.png", t_font);
Asset::get()->add(prefix + "/data/font/nokia.txt", t_font);
Asset::get()->add(prefix + "/data/font/nokia2.png", t_font);
Asset::get()->add(prefix + "/data/font/nokia2.txt", t_font);
Asset::get()->add(prefix + "/data/font/nokia_big2.txt", t_font);
Asset::get()->add(prefix + "/data/font/smb2_big.png", t_font);
Asset::get()->add(prefix + "/data/font/smb2_big.txt", t_font);
Asset::get()->add(prefix + "/data/font/smb2.png", t_font);
Asset::get()->add(prefix + "/data/font/smb2.txt", t_font);
// Textos
Asset::get()->add(prefix + "/data/lang/es_ES.txt", t_lang);
Asset::get()->add(prefix + "/data/lang/en_UK.txt", t_lang);
Asset::get()->add(prefix + "/data/lang/ba_BA.txt", t_lang);
// Menus
Asset::get()->add(prefix + "/data/menu/title.men", t_data);
Asset::get()->add(prefix + "/data/menu/title_gc.men", t_data);
Asset::get()->add(prefix + "/data/menu/options.men", t_data);
Asset::get()->add(prefix + "/data/menu/options_gc.men", t_data);
Asset::get()->add(prefix + "/data/menu/pause.men", t_data);
Asset::get()->add(prefix + "/data/menu/gameover.men", t_data);
Asset::get()->add(prefix + "/data/menu/player_select.men", t_data);
return Asset::get()->check();
}
// Comprueba los parametros del programa
void Director::checkProgramArguments(int argc, const char *argv[]) {
// Establece la ruta del programa
executablePath = argv[0];
// Comprueba el resto de parametros
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "--console") == 0) {
Options::settings.console = true;
}
}
}
// Crea la carpeta del sistema donde guardar datos
void Director::createSystemFolder(const std::string &folder) {
#ifdef __EMSCRIPTEN__
// En Emscripten usamos una carpeta en MEMFS (no persistente)
systemFolder = "/config/" + folder;
#elif _WIN32
systemFolder = std::string(getenv("APPDATA")) + "/" + folder;
#elif __APPLE__
struct passwd *pw = getpwuid(getuid());
const char *homedir = pw->pw_dir;
systemFolder = std::string(homedir) + "/Library/Application Support" + "/" + folder;
#elif __linux__
struct passwd *pw = getpwuid(getuid());
const char *homedir = pw->pw_dir;
systemFolder = std::string(homedir) + "/.config/" + folder;
{
// Intenta crear ".config", per si no existeix
std::string config_base_folder = std::string(homedir) + "/.config";
int ret = mkdir(config_base_folder.c_str(), S_IRWXU);
if (ret == -1 && errno != EEXIST) {
printf("ERROR CREATING CONFIG BASE FOLDER.");
exit(EXIT_FAILURE);
}
}
#endif
#ifdef __EMSCRIPTEN__
// En Emscripten no necesitamos crear carpetas (MEMFS las crea automáticamente)
(void)folder;
#else
struct stat st = {0};
if (stat(systemFolder.c_str(), &st) == -1) {
errno = 0;
#ifdef _WIN32
int ret = mkdir(systemFolder.c_str());
#else
int ret = mkdir(systemFolder.c_str(), S_IRWXU);
#endif
if (ret == -1) {
switch (errno) {
case EACCES:
printf("the parent directory does not allow write");
exit(EXIT_FAILURE);
case EEXIST:
printf("pathname already exists");
exit(EXIT_FAILURE);
case ENAMETOOLONG:
printf("pathname is too long");
exit(EXIT_FAILURE);
default:
perror("mkdir");
exit(EXIT_FAILURE);
}
}
}
#endif
}
// Gestiona las transiciones entre secciones
void Director::handleSectionTransition() {
// Determina qué sección debería estar activa
ActiveSection targetSection = ActiveSection::None;
switch (section->name) {
case SECTION_PROG_LOGO:
targetSection = ActiveSection::Logo;
break;
case SECTION_PROG_INTRO:
targetSection = ActiveSection::Intro;
break;
case SECTION_PROG_TITLE:
targetSection = ActiveSection::Title;
break;
case SECTION_PROG_GAME:
targetSection = ActiveSection::Game;
break;
}
// Si no ha cambiado, no hay nada que hacer
if (targetSection == activeSection) return;
// Destruye la sección anterior
logo.reset();
intro.reset();
title.reset();
game.reset();
// Crea la nueva sección
activeSection = targetSection;
switch (activeSection) {
case ActiveSection::Logo:
logo = std::make_unique<Logo>(renderer, section);
break;
case ActiveSection::Intro:
intro = std::make_unique<Intro>(renderer, section);
break;
case ActiveSection::Title:
title = std::make_unique<Title>(renderer, section);
break;
case ActiveSection::Game: {
const int numPlayers = section->subsection == SUBSECTION_GAME_PLAY_1P ? 1 : 2;
game = std::make_unique<Game>(numPlayers, 0, renderer, false, section);
break;
}
case ActiveSection::None:
break;
}
}
// Ejecuta un frame del juego
SDL_AppResult Director::iterate() {
#ifdef __EMSCRIPTEN__
// En WASM no se puede salir: reinicia al logo
if (section->name == SECTION_PROG_QUIT) {
section->name = SECTION_PROG_LOGO;
}
#else
if (section->name == SECTION_PROG_QUIT) {
return SDL_APP_SUCCESS;
}
#endif
// Actualiza la visibilidad del cursor del ratón
Mouse::updateCursorVisibility(Options::video.fullscreen);
// Gestiona las transiciones entre secciones
handleSectionTransition();
// Ejecuta un frame de la sección activa
switch (activeSection) {
case ActiveSection::Logo:
logo->iterate();
break;
case ActiveSection::Intro:
intro->iterate();
break;
case ActiveSection::Title:
title->iterate();
break;
case ActiveSection::Game:
game->iterate();
break;
case ActiveSection::None:
break;
}
return SDL_APP_CONTINUE;
}
// Procesa un evento
SDL_AppResult Director::handleEvent(SDL_Event *event) {
#ifndef __EMSCRIPTEN__
// Evento de salida de la aplicación
if (event->type == SDL_EVENT_QUIT) {
section->name = SECTION_PROG_QUIT;
return SDL_APP_SUCCESS;
}
#endif
// Hot-plug de mandos
if (event->type == SDL_EVENT_GAMEPAD_ADDED) {
std::string name;
if (Input::get()->handleGamepadAdded(event->gdevice.which, name)) {
Screen::get()->notify(name + " " + Lang::get()->getText(94),
color_t{0x40, 0xFF, 0x40},
color_t{0, 0, 0},
2500);
}
} else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) {
std::string name;
if (Input::get()->handleGamepadRemoved(event->gdevice.which, name)) {
Screen::get()->notify(name + " " + Lang::get()->getText(95),
color_t{0xFF, 0x50, 0x50},
color_t{0, 0, 0},
2500);
}
}
// Gestiona la visibilidad del cursor según el movimiento del ratón
Mouse::handleEvent(*event, Options::video.fullscreen);
// Reenvía el evento a la sección activa
switch (activeSection) {
case ActiveSection::Logo:
logo->handleEvent(event);
break;
case ActiveSection::Intro:
intro->handleEvent(event);
break;
case ActiveSection::Title:
title->handleEvent(event);
break;
case ActiveSection::Game:
game->handleEvent(event);
break;
case ActiveSection::None:
break;
}
return SDL_APP_CONTINUE;
}

View File

@@ -4,20 +4,12 @@
#include <memory> #include <memory>
#include <string> // for string, basic_string #include <string> // for string, basic_string
class Asset;
class Game; class Game;
class Input;
class Intro; class Intro;
class Lang;
class Logo; class Logo;
class Screen;
class Title; class Title;
struct options_t;
struct section_t; struct section_t;
// Textos
constexpr const char *WINDOW_CAPTION = "© 2020 Coffee Crisis — JailDesigner";
// Secciones activas del Director // Secciones activas del Director
enum class ActiveSection { None, enum class ActiveSection { None,
Logo, Logo,
@@ -30,10 +22,6 @@ class Director {
// Objetos y punteros // Objetos y punteros
SDL_Window *window; // La ventana donde dibujamos SDL_Window *window; // La ventana donde dibujamos
SDL_Renderer *renderer; // El renderizador de la ventana SDL_Renderer *renderer; // El renderizador de la ventana
Screen *screen; // Objeto encargado de dibujar en pantalla
Input *input; // Objeto Input para gestionar las entradas
Lang *lang; // Objeto para gestionar los textos en diferentes idiomas
Asset *asset; // Objeto que gestiona todos los ficheros de recursos
section_t *section; // Sección y subsección actual del programa; section_t *section; // Sección y subsección actual del programa;
// Secciones del juego // Secciones del juego
@@ -44,7 +32,6 @@ class Director {
std::unique_ptr<Game> game; std::unique_ptr<Game> game;
// Variables // Variables
struct options_t *options; // Variable con todas las opciones del programa
std::string executablePath; // Path del ejecutable std::string executablePath; // Path del ejecutable
std::string systemFolder; // Carpeta del sistema donde guardar datos std::string systemFolder; // Carpeta del sistema donde guardar datos
@@ -57,21 +44,9 @@ class Director {
// Inicializa el objeto input // Inicializa el objeto input
void initInput(); void initInput();
// Inicializa las opciones del programa
void initOptions();
// Asigna variables a partir de dos cadenas
bool setOptions(options_t *options, std::string var, std::string value);
// Crea el indice de ficheros // Crea el indice de ficheros
bool setFileList(); bool setFileList();
// Carga el fichero de configuración
bool loadConfigFile();
// Guarda el fichero de configuración
bool saveConfigFile();
// Comprueba los parametros del programa // Comprueba los parametros del programa
void checkProgramArguments(int argc, const char *argv[]); void checkProgramArguments(int argc, const char *argv[]);

View File

@@ -1,864 +0,0 @@
#include "director.h"
#include <SDL3/SDL.h>
#include <errno.h> // for errno, EEXIST, EACCES, ENAMETOO...
#include <stdio.h> // for printf, perror
#include <string.h> // for strcmp
#ifndef __EMSCRIPTEN__
#include <sys/stat.h> // for mkdir, stat, S_IRWXU
#include <unistd.h> // for getuid
#endif
#include <cstdlib> // for exit, EXIT_FAILURE, srand
#include <filesystem>
#include <fstream> // for basic_ostream, operator<<, basi...
#include <iostream> // for cout
#include <memory>
#include <string> // for basic_string, operator+, char_t...
#include <vector> // for vector
#include "asset.h" // for Asset, assetType
#include "const.h" // for SECTION_PROG_LOGO, GAMECANVAS_H...
#include "game.h" // for Game
#include "input.h" // for Input, inputs_e, INPUT_USE_GAME...
#include "intro.h" // for Intro
#include "jail_audio.hpp" // for JA_Init
#include "lang.h" // for Lang, MAX_LANGUAGES, ba_BA, en_UK
#include "logo.h" // for Logo
#include "mouse.hpp" // for Mouse::handleEvent, Mouse::upda...
#include "resource.h"
#include "resource_helper.h"
#include "screen.h" // for FILTER_NEAREST, Screen, FILTER_...
#include "texture.h" // for Texture
#include "title.h" // for Title
#include "utils.h" // for options_t, input_t, boolToString
#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)
#include <pwd.h>
#endif
// Constructor
Director::Director(int argc, const char *argv[]) {
std::cout << "Game start" << std::endl;
// Inicializa variables
section = new section_t();
section->name = SECTION_PROG_LOGO;
// Inicializa las opciones del programa
initOptions();
// Comprueba los parametros del programa
checkProgramArguments(argc, argv);
// Crea la carpeta del sistema donde guardar datos
createSystemFolder("jailgames");
#ifndef DEBUG
createSystemFolder("jailgames/coffee_crisis");
#else
createSystemFolder("jailgames/coffee_crisis_debug");
#endif
// Inicializa el sistema de recursos (pack + fallback).
// En wasm siempre se usa filesystem (MEMFS) porque el propio --preload-file
// de emscripten ya empaqueta data/ — no hay resources.pack.
{
#if defined(__EMSCRIPTEN__)
const bool enable_fallback = true;
#elif defined(RELEASE_BUILD)
const bool enable_fallback = false;
#else
const bool enable_fallback = true;
#endif
if (!ResourceHelper::initializeResourceSystem("resources.pack", enable_fallback)) {
std::cerr << "Fatal: resource system init failed (missing resources.pack?)" << std::endl;
exit(EXIT_FAILURE);
}
}
// Crea el objeto que controla los ficheros de recursos
asset = new Asset(executablePath);
asset->setVerbose(options->console);
// Si falta algún fichero no inicia el programa
if (!setFileList()) {
exit(EXIT_FAILURE);
}
// Carga el fichero de configuración
loadConfigFile();
// Inicializa SDL
initSDL();
// Inicializa JailAudio
initJailAudio();
// Establece el modo de escalado de texturas
Texture::setGlobalScaleMode(options->filter == FILTER_NEAREST ? SDL_SCALEMODE_NEAREST : SDL_SCALEMODE_LINEAR);
// Crea los objetos
lang = new Lang(asset);
lang->setLang(options->language);
#ifdef __EMSCRIPTEN__
input = new Input("/gamecontrollerdb.txt");
#else
{
const std::string binDir = std::filesystem::path(executablePath).parent_path().string();
#ifdef MACOS_BUNDLE
input = new Input(binDir + "/../Resources/gamecontrollerdb.txt");
#else
input = new Input(binDir + "/gamecontrollerdb.txt");
#endif
}
#endif
initInput();
// Precarga todos los recursos en memoria (texturas, sonidos, música, ...).
// Vivirán durante toda la vida de la app; las escenas leen handles compartidos.
// Debe ir antes de crear Screen porque Screen usa Text (propiedad de Resource).
Resource::init(renderer, asset, input);
screen = new Screen(window, renderer, asset, options);
activeSection = ActiveSection::None;
}
Director::~Director() {
saveConfigFile();
// Libera las secciones primero: sus destructores tocan audio/render SDL
// (p.ej. Intro::~Intro llama a JA_DeleteMusic) y deben ejecutarse antes
// de SDL_Quit().
logo.reset();
intro.reset();
title.reset();
game.reset();
// Screen puede tener referencias a Text propiedad de Resource: destruir
// Screen antes que Resource.
delete screen;
// Libera todos los recursos precargados antes de cerrar SDL.
Resource::destroy();
delete asset;
delete input;
delete lang;
delete options;
delete section;
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
ResourceHelper::shutdownResourceSystem();
std::cout << "\nBye!" << std::endl;
}
// Inicializa el objeto input
void Director::initInput() {
// Establece si ha de mostrar mensajes
input->setVerbose(options->console);
// Busca si hay un mando conectado
input->discoverGameController();
// Teclado - Movimiento del jugador
input->bindKey(input_up, SDL_SCANCODE_UP);
input->bindKey(input_down, SDL_SCANCODE_DOWN);
input->bindKey(input_left, SDL_SCANCODE_LEFT);
input->bindKey(input_right, SDL_SCANCODE_RIGHT);
input->bindKey(input_fire_left, SDL_SCANCODE_Q);
input->bindKey(input_fire_center, SDL_SCANCODE_W);
input->bindKey(input_fire_right, SDL_SCANCODE_E);
// Teclado - Otros
input->bindKey(input_accept, SDL_SCANCODE_RETURN);
input->bindKey(input_cancel, SDL_SCANCODE_ESCAPE);
input->bindKey(input_pause, SDL_SCANCODE_ESCAPE);
input->bindKey(input_exit, SDL_SCANCODE_ESCAPE);
input->bindKey(input_window_dec_size, SDL_SCANCODE_F1);
input->bindKey(input_window_inc_size, SDL_SCANCODE_F2);
input->bindKey(input_window_fullscreen, SDL_SCANCODE_F3);
// Mando - Movimiento del jugador
input->bindGameControllerButton(input_up, SDL_GAMEPAD_BUTTON_DPAD_UP);
input->bindGameControllerButton(input_down, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
input->bindGameControllerButton(input_left, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
input->bindGameControllerButton(input_right, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
input->bindGameControllerButton(input_fire_left, SDL_GAMEPAD_BUTTON_WEST);
input->bindGameControllerButton(input_fire_center, SDL_GAMEPAD_BUTTON_NORTH);
input->bindGameControllerButton(input_fire_right, SDL_GAMEPAD_BUTTON_EAST);
// Mando - Otros
// SOUTH queda sin asignar para evitar salidas accidentales: pausa/cancel se hace con START/BACK.
input->bindGameControllerButton(input_accept, SDL_GAMEPAD_BUTTON_EAST);
#ifdef GAME_CONSOLE
input->bindGameControllerButton(input_pause, SDL_GAMEPAD_BUTTON_BACK);
input->bindGameControllerButton(input_exit, SDL_GAMEPAD_BUTTON_START);
#else
input->bindGameControllerButton(input_pause, SDL_GAMEPAD_BUTTON_START);
input->bindGameControllerButton(input_exit, SDL_GAMEPAD_BUTTON_BACK);
#endif
}
// Inicializa JailAudio
void Director::initJailAudio() {
JA_Init(48000, SDL_AUDIO_S16, 2);
}
// Arranca SDL y crea la ventana
bool Director::initSDL() {
// Indicador de éxito
bool success = true;
// Inicializa SDL
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD)) {
if (options->console) {
std::cout << "SDL could not initialize!\nSDL Error: " << SDL_GetError() << std::endl;
}
success = false;
} else {
// Inicia el generador de numeros aleatorios
std::srand(static_cast<unsigned int>(SDL_GetTicks()));
// Crea la ventana
int incW = 0;
int incH = 0;
if (options->borderEnabled) {
incW = options->borderWidth * 2;
incH = options->borderHeight * 2;
}
window = SDL_CreateWindow(WINDOW_CAPTION, (options->gameWidth + incW) * options->windowSize, (options->gameHeight + incH) * options->windowSize, 0);
if (window == nullptr) {
if (options->console) {
std::cout << "Window could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
}
success = false;
} else {
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
// Crea un renderizador para la ventana
renderer = SDL_CreateRenderer(window, NULL);
if (renderer == nullptr) {
if (options->console) {
std::cout << "Renderer could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
}
success = false;
} else {
// Activa vsync si es necesario
if (options->vSync) {
SDL_SetRenderVSync(renderer, 1);
}
// Inicializa el color de renderizado
SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);
// Establece el tamaño del buffer de renderizado
SDL_SetRenderLogicalPresentation(renderer, options->gameWidth, options->gameHeight, SDL_LOGICAL_PRESENTATION_LETTERBOX);
// Establece el modo de mezcla
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
}
}
}
if (options->console) {
std::cout << std::endl;
}
return success;
}
// Crea el indice de ficheros
bool Director::setFileList() {
#ifdef MACOS_BUNDLE
const std::string prefix = "/../Resources";
#else
const std::string prefix = "";
#endif
// Ficheros de configuración
asset->add(systemFolder + "/config.txt", t_data, false, true);
asset->add(systemFolder + "/score.bin", t_data, false, true);
asset->add(prefix + "/data/demo/demo.bin", t_data);
// Musicas
asset->add(prefix + "/data/music/intro.ogg", t_music);
asset->add(prefix + "/data/music/playing.ogg", t_music);
asset->add(prefix + "/data/music/title.ogg", t_music);
// Sonidos
asset->add(prefix + "/data/sound/balloon.wav", t_sound);
asset->add(prefix + "/data/sound/bubble1.wav", t_sound);
asset->add(prefix + "/data/sound/bubble2.wav", t_sound);
asset->add(prefix + "/data/sound/bubble3.wav", t_sound);
asset->add(prefix + "/data/sound/bubble4.wav", t_sound);
asset->add(prefix + "/data/sound/bullet.wav", t_sound);
asset->add(prefix + "/data/sound/coffeeout.wav", t_sound);
asset->add(prefix + "/data/sound/hiscore.wav", t_sound);
asset->add(prefix + "/data/sound/itemdrop.wav", t_sound);
asset->add(prefix + "/data/sound/itempickup.wav", t_sound);
asset->add(prefix + "/data/sound/menu_move.wav", t_sound);
asset->add(prefix + "/data/sound/menu_select.wav", t_sound);
asset->add(prefix + "/data/sound/player_collision.wav", t_sound);
asset->add(prefix + "/data/sound/stage_change.wav", t_sound);
asset->add(prefix + "/data/sound/title.wav", t_sound);
asset->add(prefix + "/data/sound/clock.wav", t_sound);
asset->add(prefix + "/data/sound/powerball.wav", t_sound);
// Texturas
asset->add(prefix + "/data/gfx/balloon1.png", t_bitmap);
asset->add(prefix + "/data/gfx/balloon1.ani", t_data);
asset->add(prefix + "/data/gfx/balloon2.png", t_bitmap);
asset->add(prefix + "/data/gfx/balloon2.ani", t_data);
asset->add(prefix + "/data/gfx/balloon3.png", t_bitmap);
asset->add(prefix + "/data/gfx/balloon3.ani", t_data);
asset->add(prefix + "/data/gfx/balloon4.png", t_bitmap);
asset->add(prefix + "/data/gfx/balloon4.ani", t_data);
asset->add(prefix + "/data/gfx/bullet.png", t_bitmap);
asset->add(prefix + "/data/gfx/game_buildings.png", t_bitmap);
asset->add(prefix + "/data/gfx/game_clouds.png", t_bitmap);
asset->add(prefix + "/data/gfx/game_grass.png", t_bitmap);
asset->add(prefix + "/data/gfx/game_power_meter.png", t_bitmap);
asset->add(prefix + "/data/gfx/game_sky_colors.png", t_bitmap);
asset->add(prefix + "/data/gfx/game_text.png", t_bitmap);
asset->add(prefix + "/data/gfx/intro.png", t_bitmap);
asset->add(prefix + "/data/gfx/logo.png", t_bitmap);
asset->add(prefix + "/data/gfx/menu_game_over.png", t_bitmap);
asset->add(prefix + "/data/gfx/menu_game_over_end.png", t_bitmap);
asset->add(prefix + "/data/gfx/item_points1_disk.png", t_bitmap);
asset->add(prefix + "/data/gfx/item_points1_disk.ani", t_data);
asset->add(prefix + "/data/gfx/item_points2_gavina.png", t_bitmap);
asset->add(prefix + "/data/gfx/item_points2_gavina.ani", t_data);
asset->add(prefix + "/data/gfx/item_points3_pacmar.png", t_bitmap);
asset->add(prefix + "/data/gfx/item_points3_pacmar.ani", t_data);
asset->add(prefix + "/data/gfx/item_clock.png", t_bitmap);
asset->add(prefix + "/data/gfx/item_clock.ani", t_data);
asset->add(prefix + "/data/gfx/item_coffee.png", t_bitmap);
asset->add(prefix + "/data/gfx/item_coffee.ani", t_data);
asset->add(prefix + "/data/gfx/item_coffee_machine.png", t_bitmap);
asset->add(prefix + "/data/gfx/item_coffee_machine.ani", t_data);
asset->add(prefix + "/data/gfx/title_bg_tile.png", t_bitmap);
asset->add(prefix + "/data/gfx/title_coffee.png", t_bitmap);
asset->add(prefix + "/data/gfx/title_crisis.png", t_bitmap);
asset->add(prefix + "/data/gfx/title_dust.png", t_bitmap);
asset->add(prefix + "/data/gfx/title_dust.ani", t_data);
asset->add(prefix + "/data/gfx/title_gradient.png", t_bitmap);
asset->add(prefix + "/data/gfx/player_head.ani", t_data);
asset->add(prefix + "/data/gfx/player_body.ani", t_data);
asset->add(prefix + "/data/gfx/player_legs.ani", t_data);
asset->add(prefix + "/data/gfx/player_death.ani", t_data);
asset->add(prefix + "/data/gfx/player_fire.ani", t_data);
asset->add(prefix + "/data/gfx/player_bal1_head.png", t_bitmap);
asset->add(prefix + "/data/gfx/player_bal1_body.png", t_bitmap);
asset->add(prefix + "/data/gfx/player_bal1_legs.png", t_bitmap);
asset->add(prefix + "/data/gfx/player_bal1_death.png", t_bitmap);
asset->add(prefix + "/data/gfx/player_bal1_fire.png", t_bitmap);
asset->add(prefix + "/data/gfx/player_arounder_head.png", t_bitmap);
asset->add(prefix + "/data/gfx/player_arounder_body.png", t_bitmap);
asset->add(prefix + "/data/gfx/player_arounder_legs.png", t_bitmap);
asset->add(prefix + "/data/gfx/player_arounder_death.png", t_bitmap);
asset->add(prefix + "/data/gfx/player_arounder_fire.png", t_bitmap);
// Fuentes
asset->add(prefix + "/data/font/8bithud.png", t_font);
asset->add(prefix + "/data/font/8bithud.txt", t_font);
asset->add(prefix + "/data/font/nokia.png", t_font);
asset->add(prefix + "/data/font/nokia_big2.png", t_font);
asset->add(prefix + "/data/font/nokia.txt", t_font);
asset->add(prefix + "/data/font/nokia2.png", t_font);
asset->add(prefix + "/data/font/nokia2.txt", t_font);
asset->add(prefix + "/data/font/nokia_big2.txt", t_font);
asset->add(prefix + "/data/font/smb2_big.png", t_font);
asset->add(prefix + "/data/font/smb2_big.txt", t_font);
asset->add(prefix + "/data/font/smb2.png", t_font);
asset->add(prefix + "/data/font/smb2.txt", t_font);
// Textos
asset->add(prefix + "/data/lang/es_ES.txt", t_lang);
asset->add(prefix + "/data/lang/en_UK.txt", t_lang);
asset->add(prefix + "/data/lang/ba_BA.txt", t_lang);
// Menus
asset->add(prefix + "/data/menu/title.men", t_data);
asset->add(prefix + "/data/menu/title_gc.men", t_data);
asset->add(prefix + "/data/menu/options.men", t_data);
asset->add(prefix + "/data/menu/options_gc.men", t_data);
asset->add(prefix + "/data/menu/pause.men", t_data);
asset->add(prefix + "/data/menu/gameover.men", t_data);
asset->add(prefix + "/data/menu/player_select.men", t_data);
return asset->check();
}
// Inicializa las opciones del programa
void Director::initOptions() {
// Crea el puntero a la estructura de opciones
options = new options_t;
// Pone unos valores por defecto para las opciones de control
options->input.clear();
input_t inp;
inp.id = 0;
inp.name = "KEYBOARD";
inp.deviceType = INPUT_USE_KEYBOARD;
options->input.push_back(inp);
inp.id = 0;
inp.name = "GAME CONTROLLER";
inp.deviceType = INPUT_USE_GAMECONTROLLER;
options->input.push_back(inp);
// Opciones de video
options->gameWidth = GAMECANVAS_WIDTH;
options->gameHeight = GAMECANVAS_HEIGHT;
options->videoMode = 0;
options->windowSize = 3;
options->filter = FILTER_NEAREST;
options->vSync = true;
options->integerScale = true;
options->keepAspect = true;
options->borderWidth = 0;
options->borderHeight = 0;
options->borderEnabled = false;
// Opciones varios
options->playerSelected = 0;
options->difficulty = DIFFICULTY_NORMAL;
options->language = ba_BA;
options->console = false;
#ifdef __EMSCRIPTEN__
// En Emscripten la ventana la gestiona el navegador
options->windowSize = 4;
options->videoMode = 0;
options->integerScale = true;
#endif
}
// Comprueba los parametros del programa
void Director::checkProgramArguments(int argc, const char *argv[]) {
// Establece la ruta del programa
executablePath = argv[0];
// Comprueba el resto de parametros
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "--console") == 0) {
options->console = true;
}
}
}
// Crea la carpeta del sistema donde guardar datos
void Director::createSystemFolder(const std::string &folder) {
#ifdef __EMSCRIPTEN__
// En Emscripten usamos una carpeta en MEMFS (no persistente)
systemFolder = "/config/" + folder;
#elif _WIN32
systemFolder = std::string(getenv("APPDATA")) + "/" + folder;
#elif __APPLE__
struct passwd *pw = getpwuid(getuid());
const char *homedir = pw->pw_dir;
systemFolder = std::string(homedir) + "/Library/Application Support" + "/" + folder;
#elif __linux__
struct passwd *pw = getpwuid(getuid());
const char *homedir = pw->pw_dir;
systemFolder = std::string(homedir) + "/.config/" + folder;
{
// Intenta crear ".config", per si no existeix
std::string config_base_folder = std::string(homedir) + "/.config";
int ret = mkdir(config_base_folder.c_str(), S_IRWXU);
if (ret == -1 && errno != EEXIST) {
printf("ERROR CREATING CONFIG BASE FOLDER.");
exit(EXIT_FAILURE);
}
}
#endif
#ifdef __EMSCRIPTEN__
// En Emscripten no necesitamos crear carpetas (MEMFS las crea automáticamente)
(void)folder;
#else
struct stat st = {0};
if (stat(systemFolder.c_str(), &st) == -1) {
errno = 0;
#ifdef _WIN32
int ret = mkdir(systemFolder.c_str());
#else
int ret = mkdir(systemFolder.c_str(), S_IRWXU);
#endif
if (ret == -1) {
switch (errno) {
case EACCES:
printf("the parent directory does not allow write");
exit(EXIT_FAILURE);
case EEXIST:
printf("pathname already exists");
exit(EXIT_FAILURE);
case ENAMETOOLONG:
printf("pathname is too long");
exit(EXIT_FAILURE);
default:
perror("mkdir");
exit(EXIT_FAILURE);
}
}
}
#endif
}
// Carga el fichero de configuración
bool Director::loadConfigFile() {
// Indicador de éxito en la carga
bool success = true;
// Variables para manejar el fichero
const std::string filePath = "config.txt";
std::string line;
std::ifstream file(asset->get(filePath));
// Si el fichero se puede abrir
if (file.good()) {
// Procesa el fichero linea a linea
if (options->console) {
std::cout << "Reading file " << filePath << std::endl;
}
while (std::getline(file, line)) {
// Comprueba que la linea no sea un comentario
if (line.substr(0, 1) != "#") {
// Encuentra la posición del caracter '='
int pos = line.find("=");
// Procesa las dos subcadenas
if (!setOptions(options, line.substr(0, pos), line.substr(pos + 1, line.length()))) {
if (options->console) {
std::cout << "Warning: file " << filePath << std::endl;
std::cout << "Unknown parameter " << line.substr(0, pos).c_str() << std::endl;
}
success = false;
}
}
}
// Cierra el fichero
if (options->console) {
std::cout << "Closing file " << filePath << std::endl;
}
file.close();
}
// El fichero no existe
else { // Crea el fichero con los valores por defecto
saveConfigFile();
}
// Normaliza los valores
if (options->videoMode != 0 && options->videoMode != SDL_WINDOW_FULLSCREEN) {
options->videoMode = 0;
}
if (options->windowSize < 1 || options->windowSize > 4) {
options->windowSize = 3;
}
if (options->language < 0 || options->language > MAX_LANGUAGES) {
options->language = en_UK;
}
return success;
}
// Guarda el fichero de configuración
bool Director::saveConfigFile() {
bool success = true;
// Crea y abre el fichero de texto
std::ofstream file(asset->get("config.txt"));
if (file.good()) {
if (options->console) {
std::cout << asset->get("config.txt") << " open for writing" << std::endl;
}
} else {
if (options->console) {
std::cout << asset->get("config.txt") << " can't be opened" << std::endl;
}
}
// Opciones g´raficas
file << "## VISUAL OPTIONS\n";
if (options->videoMode == 0) {
file << "videoMode=0\n";
}
else if (options->videoMode == SDL_WINDOW_FULLSCREEN) {
file << "videoMode=SDL_WINDOW_FULLSCREEN\n";
}
file << "windowSize=" + std::to_string(options->windowSize) + "\n";
if (options->filter == FILTER_NEAREST) {
file << "filter=FILTER_NEAREST\n";
} else {
file << "filter=FILTER_LINEAL\n";
}
file << "vSync=" + boolToString(options->vSync) + "\n";
file << "integerScale=" + boolToString(options->integerScale) + "\n";
file << "keepAspect=" + boolToString(options->keepAspect) + "\n";
file << "borderEnabled=" + boolToString(options->borderEnabled) + "\n";
file << "borderWidth=" + std::to_string(options->borderWidth) + "\n";
file << "borderHeight=" + std::to_string(options->borderHeight) + "\n";
// Otras opciones del programa
file << "\n## OTHER OPTIONS\n";
file << "language=" + std::to_string(options->language) + "\n";
file << "difficulty=" + std::to_string(options->difficulty) + "\n";
file << "input0=" + std::to_string(options->input[0].deviceType) + "\n";
file << "input1=" + std::to_string(options->input[1].deviceType) + "\n";
// Cierra el fichero
file.close();
return success;
}
// Gestiona las transiciones entre secciones
void Director::handleSectionTransition() {
// Determina qué sección debería estar activa
ActiveSection targetSection = ActiveSection::None;
switch (section->name) {
case SECTION_PROG_LOGO:
targetSection = ActiveSection::Logo;
break;
case SECTION_PROG_INTRO:
targetSection = ActiveSection::Intro;
break;
case SECTION_PROG_TITLE:
targetSection = ActiveSection::Title;
break;
case SECTION_PROG_GAME:
targetSection = ActiveSection::Game;
break;
}
// Si no ha cambiado, no hay nada que hacer
if (targetSection == activeSection) return;
// Destruye la sección anterior
logo.reset();
intro.reset();
title.reset();
game.reset();
// Crea la nueva sección
activeSection = targetSection;
switch (activeSection) {
case ActiveSection::Logo:
logo = std::make_unique<Logo>(renderer, screen, asset, input, section);
break;
case ActiveSection::Intro:
intro = std::make_unique<Intro>(renderer, screen, asset, input, lang, section);
break;
case ActiveSection::Title:
title = std::make_unique<Title>(renderer, screen, input, asset, options, lang, section);
break;
case ActiveSection::Game: {
const int numPlayers = section->subsection == SUBSECTION_GAME_PLAY_1P ? 1 : 2;
game = std::make_unique<Game>(numPlayers, 0, renderer, screen, asset, lang, input, false, options, section);
break;
}
case ActiveSection::None:
break;
}
}
// Ejecuta un frame del juego
SDL_AppResult Director::iterate() {
#ifdef __EMSCRIPTEN__
// En WASM no se puede salir: reinicia al logo
if (section->name == SECTION_PROG_QUIT) {
section->name = SECTION_PROG_LOGO;
}
#else
if (section->name == SECTION_PROG_QUIT) {
return SDL_APP_SUCCESS;
}
#endif
// Actualiza la visibilidad del cursor del ratón
Mouse::updateCursorVisibility(options->videoMode != 0);
// Gestiona las transiciones entre secciones
handleSectionTransition();
// Ejecuta un frame de la sección activa
switch (activeSection) {
case ActiveSection::Logo:
logo->iterate();
break;
case ActiveSection::Intro:
intro->iterate();
break;
case ActiveSection::Title:
title->iterate();
break;
case ActiveSection::Game:
game->iterate();
break;
case ActiveSection::None:
break;
}
return SDL_APP_CONTINUE;
}
// Procesa un evento
SDL_AppResult Director::handleEvent(SDL_Event *event) {
#ifndef __EMSCRIPTEN__
// Evento de salida de la aplicación
if (event->type == SDL_EVENT_QUIT) {
section->name = SECTION_PROG_QUIT;
return SDL_APP_SUCCESS;
}
#endif
// Hot-plug de mandos
if (event->type == SDL_EVENT_GAMEPAD_ADDED) {
std::string name;
if (input->handleGamepadAdded(event->gdevice.which, name)) {
screen->notify(name + " " + lang->getText(94),
color_t{0x40, 0xFF, 0x40},
color_t{0, 0, 0},
2500);
}
} else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) {
std::string name;
if (input->handleGamepadRemoved(event->gdevice.which, name)) {
screen->notify(name + " " + lang->getText(95),
color_t{0xFF, 0x50, 0x50},
color_t{0, 0, 0},
2500);
}
}
// Gestiona la visibilidad del cursor según el movimiento del ratón
Mouse::handleEvent(*event, options->videoMode != 0);
// Reenvía el evento a la sección activa
switch (activeSection) {
case ActiveSection::Logo:
logo->handleEvent(event);
break;
case ActiveSection::Intro:
intro->handleEvent(event);
break;
case ActiveSection::Title:
title->handleEvent(event);
break;
case ActiveSection::Game:
game->handleEvent(event);
break;
case ActiveSection::None:
break;
}
return SDL_APP_CONTINUE;
}
// Asigna variables a partir de dos cadenas
bool Director::setOptions(options_t *options, std::string var, std::string value) {
// Indicador de éxito en la asignación
bool success = true;
// Opciones de video
if (var == "videoMode") {
if (value == "SDL_WINDOW_FULLSCREEN" || value == "SDL_WINDOW_FULLSCREEN_DESKTOP") {
options->videoMode = SDL_WINDOW_FULLSCREEN;
} else {
options->videoMode = 0;
}
}
else if (var == "windowSize") {
options->windowSize = std::stoi(value);
if ((options->windowSize < 1) || (options->windowSize > 4)) {
options->windowSize = 3;
}
}
else if (var == "filter") {
if (value == "FILTER_LINEAL") {
options->filter = FILTER_LINEAL;
} else {
options->filter = FILTER_NEAREST;
}
}
else if (var == "vSync") {
options->vSync = stringToBool(value);
}
else if (var == "integerScale") {
options->integerScale = stringToBool(value);
}
else if (var == "keepAspect") {
options->keepAspect = stringToBool(value);
}
else if (var == "borderEnabled") {
options->borderEnabled = stringToBool(value);
}
else if (var == "borderWidth") {
options->borderWidth = std::stoi(value);
}
else if (var == "borderHeight") {
options->borderHeight = std::stoi(value);
}
// Opciones varias
else if (var == "language") {
options->language = std::stoi(value);
}
else if (var == "difficulty") {
options->difficulty = std::stoi(value);
}
else if (var == "input0") {
options->input[0].deviceType = std::stoi(value);
}
else if (var == "input1") {
options->input[1].deviceType = std::stoi(value);
}
// Lineas vacias o que empiezan por comentario
else if (var == "" || var.substr(0, 1) == "#") {
}
else {
success = false;
}
return success;
}

14726
source/external/fkyaml_node.hpp vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,54 +0,0 @@
#pragma once
#include <SDL3/SDL.h>
// Tipos de fundido
constexpr int FADE_FULLSCREEN = 0;
constexpr int FADE_CENTER = 1;
constexpr int FADE_RANDOM_SQUARE = 2;
// Clase Fade
class Fade {
private:
SDL_Renderer *mRenderer; // El renderizador de la ventana
SDL_Texture *mBackbuffer; // Textura para usar como backbuffer
Uint8 mFadeType; // Tipo de fade a realizar
Uint16 mCounter; // Contador interno
bool mEnabled; // Indica si el fade está activo
bool mFinished; // Indica si ha terminado la transición
Uint8 mR, mG, mB; // Colores para el fade
Uint8 mROriginal, mGOriginal, mBOriginal; // Colores originales para FADE_RANDOM_SQUARE
Uint32 mLastSquareTicks; // Ticks del último cuadrado dibujado (FADE_RANDOM_SQUARE)
Uint16 mSquaresDrawn; // Número de cuadrados dibujados (FADE_RANDOM_SQUARE)
bool mFullscreenDone; // Indica si el fade fullscreen ha terminado la fase de fundido
SDL_Rect mRect1; // Rectangulo usado para crear los efectos de transición
SDL_Rect mRect2; // Rectangulo usado para crear los efectos de transición
public:
// Constructor
Fade(SDL_Renderer *renderer);
// Destructor
~Fade();
// Inicializa las variables
void init(Uint8 r, Uint8 g, Uint8 b);
// Pinta una transición en pantalla
void render();
// Actualiza las variables internas
void update();
// Activa el fade
void activateFade();
// Comprueba si ha terminado la transicion
bool hasEnded();
// Comprueba si está activo
bool isEnabled();
// Establece el tipo de fade
void setFadeType(Uint8 fadeType);
};

View File

@@ -2,8 +2,58 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include "lang.h" #include "core/locale/lang.h"
#include "utils.h" #include "utils/utils.h"
// =============================================================================
// Defaults per a Options (alineats amb coffee_crisis_arcade_edition).
// =============================================================================
namespace Defaults::Window {
constexpr const char *CAPTION = "© 2020 Coffee Crisis — JailDesigner";
constexpr int ZOOM = 3;
constexpr int MAX_ZOOM = 4;
} // namespace Defaults::Window
namespace Defaults::Video {
constexpr SDL_ScaleMode SCALE_MODE = SDL_ScaleMode::SDL_SCALEMODE_NEAREST;
constexpr bool FULLSCREEN = false;
constexpr bool VSYNC = true;
constexpr bool INTEGER_SCALE = true;
constexpr bool GPU_ACCELERATION = true;
constexpr const char *GPU_PREFERRED_DRIVER = "";
constexpr bool SHADER_ENABLED = false;
constexpr bool SUPERSAMPLING = false;
constexpr bool LINEAR_UPSCALE = false;
constexpr int DOWNSCALE_ALGO = 1;
} // namespace Defaults::Video
namespace Defaults::Audio {
constexpr bool ENABLED = true;
constexpr int VOLUME = 100;
} // namespace Defaults::Audio
namespace Defaults::Music {
constexpr bool ENABLED = true;
constexpr int VOLUME = 100;
} // namespace Defaults::Music
namespace Defaults::Sound {
constexpr bool ENABLED = true;
constexpr int VOLUME = 100;
} // namespace Defaults::Sound
namespace Defaults::Loading {
constexpr bool SHOW = false;
constexpr bool SHOW_RESOURCE_NAME = true;
constexpr bool WAIT_FOR_INPUT = false;
} // namespace Defaults::Loading
namespace Defaults::Settings {
constexpr int DIFFICULTY = DIFFICULTY_NORMAL;
constexpr int LANGUAGE = ba_BA;
constexpr palette_e PALETTE = p_zxspectrum;
} // namespace Defaults::Settings
// Tamaño de bloque // Tamaño de bloque
constexpr int BLOCK = 8; constexpr int BLOCK = 8;

View File

@@ -1,12 +1,12 @@
#include "balloon.h" #include "game/entities/balloon.h"
#include <math.h> // for abs #include <math.h> // for abs
#include "animatedsprite.h" // for AnimatedSprite #include "core/rendering/animatedsprite.h" // for AnimatedSprite
#include "const.h" // for PLAY_AREA_LEFT, PLAY_AREA_RIGHT, PLAY_AR... #include "core/rendering/movingsprite.h" // for MovingSprite
#include "movingsprite.h" // for MovingSprite #include "core/rendering/sprite.h" // for Sprite
#include "sprite.h" // for Sprite #include "core/rendering/texture.h" // for Texture
#include "texture.h" // for Texture #include "game/defaults.hpp" // for PLAY_AREA_LEFT, PLAY_AREA_RIGHT, PLAY_AR...
// Constructor // Constructor
Balloon::Balloon(float x, float y, Uint8 kind, float velx, float speed, Uint16 creationtimer, Texture *texture, std::vector<std::string> *animation, SDL_Renderer *renderer) { Balloon::Balloon(float x, float y, Uint8 kind, float velx, float speed, Uint16 creationtimer, Texture *texture, std::vector<std::string> *animation, SDL_Renderer *renderer) {
@@ -261,15 +261,13 @@ Balloon::Balloon(float x, float y, Uint8 kind, float velx, float speed, Uint16 c
stoppedCounter = 0; stoppedCounter = 0;
blinking = false; blinking = false;
visible = true; visible = true;
invulnerable = true;
beingCreated = true;
creationCounter = creationtimer; creationCounter = creationtimer;
creationCounterIni = creationtimer; creationCounterIni = creationtimer;
popping = false; popping = false;
// Actualiza valores // Valores iniciales dependentes del timer
beingCreated = creationCounter == 0 ? false : true; beingCreated = creationCounter != 0;
invulnerable = beingCreated == false ? false : true; invulnerable = beingCreated;
counter = 0; counter = 0;
travelY = 1.0f; travelY = 1.0f;

View File

@@ -5,7 +5,7 @@
#include <string> // for string #include <string> // for string
#include <vector> // for vector #include <vector> // for vector
#include "utils.h" // for circle_t #include "utils/utils.h" // for circle_t
class AnimatedSprite; class AnimatedSprite;
class Texture; class Texture;

View File

@@ -1,7 +1,7 @@
#include "bullet.h" #include "game/entities/bullet.h"
#include "const.h" // for NO_KIND, PLAY_AREA_LEFT, PLAY_AREA_RIGHT, PLAY_A... #include "core/rendering/sprite.h" // for Sprite
#include "sprite.h" // for Sprite #include "game/defaults.hpp" // for NO_KIND, PLAY_AREA_LEFT, PLAY_AREA_RIGHT, PLAY_A...
class Texture; class Texture;
// Constructor // Constructor

View File

@@ -2,7 +2,7 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include "utils.h" // for circle_t #include "utils/utils.h" // for circle_t
class Sprite; class Sprite;
class Texture; class Texture;

View File

@@ -1,9 +1,9 @@
#include "item.h" #include "game/entities/item.h"
#include <stdlib.h> // for rand #include <stdlib.h> // for rand
#include "animatedsprite.h" // for AnimatedSprite #include "core/rendering/animatedsprite.h" // for AnimatedSprite
#include "const.h" // for PLAY_AREA_LEFT, PLAY_AREA_RIGHT, PLAY_AR... #include "game/defaults.hpp" // for PLAY_AREA_LEFT, PLAY_AREA_RIGHT, PLAY_AR...
class Texture; class Texture;
// Constructor // Constructor
@@ -107,9 +107,6 @@ void Item::move() {
// Si el objeto se sale por la parte inferior // Si el objeto se sale por la parte inferior
if (posY + height > PLAY_AREA_BOTTOM) { if (posY + height > PLAY_AREA_BOTTOM) {
// Corrige
posY -= velY;
// Detiene el objeto // Detiene el objeto
velY = 0; velY = 0;
velX = 0; velX = 0;

View File

@@ -5,7 +5,7 @@
#include <string> // for string #include <string> // for string
#include <vector> // for vector #include <vector> // for vector
#include "utils.h" // for circle_t #include "utils/utils.h" // for circle_t
class AnimatedSprite; class AnimatedSprite;
class Texture; class Texture;

View File

@@ -1,14 +1,14 @@
#include "player.h" #include "game/entities/player.h"
#include <stdlib.h> // for rand #include <stdlib.h> // for rand
#include "animatedsprite.h" // for AnimatedSprite #include "core/input/input.h" // for inputs_e
#include "const.h" // for PLAY_AREA_LEFT, PLAY_AREA_RIGHT #include "core/rendering/animatedsprite.h" // for AnimatedSprite
#include "input.h" // for inputs_e #include "core/rendering/texture.h" // for Texture
#include "texture.h" // for Texture #include "game/defaults.hpp" // for PLAY_AREA_LEFT, PLAY_AREA_RIGHT
// Constructor // Constructor
Player::Player(float x, int y, SDL_Renderer *renderer, std::vector<Texture *> texture, std::vector<std::vector<std::string> *> animations) { Player::Player(float x, int y, SDL_Renderer *renderer, const std::vector<Texture *> &texture, const std::vector<std::vector<std::string> *> &animations) {
// Copia los punteros // Copia los punteros
this->renderer = renderer; this->renderer = renderer;
@@ -189,19 +189,12 @@ void Player::render() {
// Establece el estado del jugador cuando camina // Establece el estado del jugador cuando camina
void Player::setWalkingStatus(Uint8 status) { void Player::setWalkingStatus(Uint8 status) {
// Si cambiamos de estado, reiniciamos la animación statusWalking = status;
if (statusWalking != status) {
statusWalking = status;
// legsSprite->setCurrentFrame(0);
}
} }
// Establece el estado del jugador cuando dispara // Establece el estado del jugador cuando dispara
void Player::setFiringStatus(Uint8 status) { void Player::setFiringStatus(Uint8 status) {
// Si cambiamos de estado, reiniciamos la animación statusFiring = status;
if (statusFiring != status) {
statusFiring = status;
}
} }
// Establece la animación correspondiente al estado // Establece la animación correspondiente al estado
@@ -521,7 +514,7 @@ void Player::updatePowerUpHeadOffset() {
} }
// Pone las texturas del jugador // Pone las texturas del jugador
void Player::setPlayerTextures(std::vector<Texture *> texture) { void Player::setPlayerTextures(const std::vector<Texture *> &texture) {
headSprite->setTexture(texture[0]); headSprite->setTexture(texture[0]);
bodySprite->setTexture(texture[1]); bodySprite->setTexture(texture[1]);
legsSprite->setTexture(texture[2]); legsSprite->setTexture(texture[2]);

View File

@@ -5,7 +5,7 @@
#include <string> // for string #include <string> // for string
#include <vector> // for vector #include <vector> // for vector
#include "utils.h" // for circle_t #include "utils/utils.h" // for circle_t
class AnimatedSprite; class AnimatedSprite;
class Texture; class Texture;
@@ -81,7 +81,7 @@ class Player {
public: public:
// Constructor // Constructor
Player(float x, int y, SDL_Renderer *renderer, std::vector<Texture *> texture, std::vector<std::vector<std::string> *> animations); Player(float x, int y, SDL_Renderer *renderer, const std::vector<Texture *> &texture, const std::vector<std::vector<std::string> *> &animations);
// Destructor // Destructor
~Player(); ~Player();
@@ -96,7 +96,7 @@ class Player {
void render(); void render();
// Pone las texturas del jugador // Pone las texturas del jugador
void setPlayerTextures(std::vector<Texture *> texture); void setPlayerTextures(const std::vector<Texture *> &texture);
// Actua en consecuencia de la entrada recibida // Actua en consecuencia de la entrada recibida
void setInput(Uint8 input); void setInput(Uint8 input);

View File

@@ -1,41 +1,40 @@
#include "game.h" #include "game/game.h"
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <stdlib.h> // for rand #include <stdlib.h> // for rand
#include <algorithm> // for max, min #include <algorithm> // for max, min
#include <numeric> // for accumulate
#include <fstream> // for basic_ifstream #include <fstream> // for basic_ifstream
#include <iostream> // for basic_ostream, char_traits, operator<< #include <iostream> // for basic_ostream, char_traits, operator<<
#include "asset.h" // for Asset #include "core/audio/jail_audio.hpp" // for JA_PlaySound, JA_DeleteSound, JA_LoadSound
#include "balloon.h" // for Balloon, BALLOON_VELX_NEGATIVE, BALLOON_... #include "core/input/global_inputs.hpp" // for GlobalInputs::handle
#include "bullet.h" // for Bullet, BULLET_LEFT, BULLET_RIGHT, BULLE... #include "core/input/input.h" // for inputs_e, Input, REPEAT_TRUE, REPEAT_FALSE
#include "const.h" // for PLAY_AREA_CENTER_X, BLOCK, PLAY_AREA_CEN... #include "core/locale/lang.h" // for Lang
#include "fade.h" // for Fade, FADE_CENTER #include "core/rendering/fade.h" // for Fade, FADE_CENTER
#include "input.h" // for inputs_e, Input, REPEAT_TRUE, REPEAT_FALSE #include "core/rendering/movingsprite.h" // for MovingSprite
#include "item.h" // for Item, ITEM_COFFEE_MACHINE, ITEM_CLOCK #include "core/rendering/screen.h" // for Screen
#include "jail_audio.hpp" // for JA_PlaySound, JA_DeleteSound, JA_LoadSound #include "core/rendering/smartsprite.h" // for SmartSprite
#include "lang.h" // for Lang #include "core/rendering/sprite.h" // for Sprite
#include "menu.h" // for Menu #include "core/rendering/text.h" // for Text, TXT_CENTER
#include "movingsprite.h" // for MovingSprite #include "core/rendering/texture.h" // for Texture
#include "player.h" // for Player, DEATH_COUNTER #include "core/resources/asset.h" // for Asset
#include "resource.h" #include "core/resources/resource.h"
#include "screen.h" // for Screen #include "game/defaults.hpp" // for PLAY_AREA_CENTER_X, BLOCK, PLAY_AREA_CEN...
#include "smartsprite.h" // for SmartSprite #include "game/entities/balloon.h" // for Balloon, BALLOON_VELX_NEGATIVE, BALLOON_...
#include "sprite.h" // for Sprite #include "game/entities/bullet.h" // for Bullet, BULLET_LEFT, BULLET_RIGHT, BULLE...
#include "text.h" // for Text, TXT_CENTER #include "game/entities/item.h" // for Item, ITEM_COFFEE_MACHINE, ITEM_CLOCK
#include "texture.h" // for Texture #include "game/entities/player.h" // for Player, DEATH_COUNTER
#include "game/options.hpp" // for Options
#include "game/ui/menu.h" // for Menu
struct JA_Sound_t; struct JA_Sound_t;
// Constructor // Constructor
Game::Game(int numPlayers, int currentStage, SDL_Renderer *renderer, Screen *screen, Asset *asset, Lang *lang, Input *input, bool demo, options_t *options, section_t *section) { Game::Game(int numPlayers, int currentStage, SDL_Renderer *renderer, bool demo, section_t *section)
: lastStageReached(currentStage) {
// Copia los punteros // Copia los punteros
this->renderer = renderer; this->renderer = renderer;
this->screen = screen;
this->asset = asset;
this->lang = lang;
this->input = input;
this->options = options;
this->section = section; this->section = section;
// Pasa variables // Pasa variables
@@ -46,12 +45,11 @@ Game::Game(int numPlayers, int currentStage, SDL_Renderer *renderer, Screen *scr
#else #else
this->currentStage = currentStage; this->currentStage = currentStage;
#endif #endif
lastStageReached = currentStage;
if (numPlayers == 1) { // Si solo juega un jugador, permite jugar tanto con teclado como con mando if (numPlayers == 1) { // Si solo juega un jugador, permite jugar tanto con teclado como con mando
onePlayerControl = options->input[0].deviceType; onePlayerControl = Options::inputs[0].deviceType;
options->input[0].deviceType = INPUT_USE_ANY; Options::inputs[0].deviceType = INPUT_USE_ANY;
} }
difficulty = options->difficulty; difficulty = Options::settings.difficulty;
// Crea los objetos // Crea los objetos
fade = new Fade(renderer); fade = new Fade(renderer);
@@ -96,7 +94,7 @@ Game::~Game() {
// Restaura el metodo de control // Restaura el metodo de control
if (numPlayers == 1) { if (numPlayers == 1) {
options->input[0].deviceType = onePlayerControl; Options::inputs[0].deviceType = onePlayerControl;
} }
// Elimina todos los objetos contenidos en vectores (jugadores, balas, etc.) // Elimina todos los objetos contenidos en vectores (jugadores, balas, etc.)
@@ -143,7 +141,7 @@ void Game::init() {
// Crea los jugadores // Crea los jugadores
if (numPlayers == 1) { if (numPlayers == 1) {
Player *player = new Player(PLAY_AREA_CENTER_X - 11, PLAY_AREA_BOTTOM - 24, renderer, playerTextures[options->playerSelected], playerAnimations); Player *player = new Player(PLAY_AREA_CENTER_X - 11, PLAY_AREA_BOTTOM - 24, renderer, playerTextures[Options::settings.player_selected], playerAnimations);
players.push_back(player); players.push_back(player);
} }
@@ -327,7 +325,7 @@ void Game::init() {
// Carga los recursos necesarios para la sección 'Game' // Carga los recursos necesarios para la sección 'Game'
void Game::loadMedia() { void Game::loadMedia() {
if (options->console) { if (Options::settings.console) {
std::cout << std::endl std::cout << std::endl
<< "** LOADING RESOURCES FOR GAME SECTION" << std::endl; << "** LOADING RESOURCES FOR GAME SECTION" << std::endl;
} }
@@ -403,15 +401,15 @@ void Game::loadMedia() {
// Menus // Menus
gameOverMenu = R->getMenu("gameover"); gameOverMenu = R->getMenu("gameover");
gameOverMenu->setItemCaption(0, lang->getText(48)); gameOverMenu->setItemCaption(0, Lang::get()->getText(48));
gameOverMenu->setItemCaption(1, lang->getText(49)); gameOverMenu->setItemCaption(1, Lang::get()->getText(49));
const int w = text->getCharacterSize() * lang->getText(45).length(); const int w = text->getCharacterSize() * Lang::get()->getText(45).length();
gameOverMenu->setRectSize(w, 0); gameOverMenu->setRectSize(w, 0);
gameOverMenu->centerMenuOnX(199); gameOverMenu->centerMenuOnX(199);
pauseMenu = R->getMenu("pause"); pauseMenu = R->getMenu("pause");
pauseMenu->setItemCaption(0, lang->getText(41)); pauseMenu->setItemCaption(0, Lang::get()->getText(41));
pauseMenu->setItemCaption(1, lang->getText(46)); pauseMenu->setItemCaption(1, Lang::get()->getText(46));
pauseMenu->setItemCaption(2, lang->getText(47)); pauseMenu->setItemCaption(2, Lang::get()->getText(47));
// Sonidos // Sonidos
balloonSound = R->getSound("balloon.wav"); balloonSound = R->getSound("balloon.wav");
@@ -433,7 +431,7 @@ void Game::loadMedia() {
// Musicas // Musicas
gameMusic = R->getMusic("playing.ogg"); gameMusic = R->getMusic("playing.ogg");
if (options->console) { if (Options::settings.console) {
std::cout << "** RESOURCES FOR GAME SECTION LOADED" << std::endl std::cout << "** RESOURCES FOR GAME SECTION LOADED" << std::endl
<< std::endl; << std::endl;
} }
@@ -443,20 +441,20 @@ void Game::loadMedia() {
bool Game::loadScoreFile() { bool Game::loadScoreFile() {
// Indicador de éxito en la carga // Indicador de éxito en la carga
bool success = true; bool success = true;
const std::string p = asset->get("score.bin"); const std::string p = Asset::get()->get("score.bin");
const std::string filename = p.substr(p.find_last_of("\\/") + 1); const std::string filename = p.substr(p.find_last_of("\\/") + 1);
SDL_IOStream *file = SDL_IOFromFile(p.c_str(), "r+b"); SDL_IOStream *file = SDL_IOFromFile(p.c_str(), "r+b");
// El fichero no existe // El fichero no existe
if (file == nullptr) { if (file == nullptr) {
if (options->console) { if (Options::settings.console) {
std::cout << "Warning: Unable to open " << filename.c_str() << " file" << std::endl; std::cout << "Warning: Unable to open " << filename.c_str() << " file" << std::endl;
} }
// Creamos el fichero para escritura // Creamos el fichero para escritura
file = SDL_IOFromFile(p.c_str(), "w+b"); file = SDL_IOFromFile(p.c_str(), "w+b");
if (file != nullptr) { if (file != nullptr) {
if (options->console) { if (Options::settings.console) {
std::cout << "New file (" << filename.c_str() << ") created!" << std::endl; std::cout << "New file (" << filename.c_str() << ") created!" << std::endl;
} }
@@ -469,7 +467,7 @@ bool Game::loadScoreFile() {
// Cerramos el fichero // Cerramos el fichero
SDL_CloseIO(file); SDL_CloseIO(file);
} else { } else {
if (options->console) { if (Options::settings.console) {
std::cout << "Error: Unable to create file " << filename.c_str() << std::endl; std::cout << "Error: Unable to create file " << filename.c_str() << std::endl;
} }
success = false; success = false;
@@ -478,7 +476,7 @@ bool Game::loadScoreFile() {
// El fichero existe // El fichero existe
else { else {
// Cargamos los datos // Cargamos los datos
if (options->console) { if (Options::settings.console) {
std::cout << "Reading file " << filename.c_str() << std::endl; std::cout << "Reading file " << filename.c_str() << std::endl;
} }
for (int i = 0; i < TOTAL_SCORE_DATA; ++i) for (int i = 0; i < TOTAL_SCORE_DATA; ++i)
@@ -511,12 +509,12 @@ bool Game::loadDemoFile() {
for (int i = 0; i < TOTAL_DEMO_DATA; ++i) { for (int i = 0; i < TOTAL_DEMO_DATA; ++i) {
memcpy(&demo.dataFile[i], bytes.data() + i * sizeof(demoKeys_t), sizeof(demoKeys_t)); memcpy(&demo.dataFile[i], bytes.data() + i * sizeof(demoKeys_t), sizeof(demoKeys_t));
} }
if (options->console) { if (Options::settings.console) {
std::cout << "Demo data loaded (" << bytes.size() << " bytes)" << std::endl; std::cout << "Demo data loaded (" << bytes.size() << " bytes)" << std::endl;
} }
} else { } else {
// Si no hay datos (bytes vacíos o tamaño inválido), inicializamos a cero. // Si no hay datos (bytes vacíos o tamaño inválido), inicializamos a cero.
if (options->console) { if (Options::settings.console) {
std::cout << "Warning: demo data missing or too small, initializing to zero" << std::endl; std::cout << "Warning: demo data missing or too small, initializing to zero" << std::endl;
} }
for (int i = 0; i < TOTAL_DEMO_DATA; ++i) { for (int i = 0; i < TOTAL_DEMO_DATA; ++i) {
@@ -535,7 +533,7 @@ bool Game::loadDemoFile() {
// Guarda el fichero de puntos // Guarda el fichero de puntos
bool Game::saveScoreFile() { bool Game::saveScoreFile() {
bool success = true; bool success = true;
const std::string p = asset->get("score.bin"); const std::string p = Asset::get()->get("score.bin");
const std::string filename = p.substr(p.find_last_of("\\/") + 1); const std::string filename = p.substr(p.find_last_of("\\/") + 1);
SDL_IOStream *file = SDL_IOFromFile(p.c_str(), "w+b"); SDL_IOStream *file = SDL_IOFromFile(p.c_str(), "w+b");
if (file != nullptr) { if (file != nullptr) {
@@ -544,14 +542,14 @@ bool Game::saveScoreFile() {
SDL_WriteIO(file, &scoreDataFile[i], sizeof(Uint32)); SDL_WriteIO(file, &scoreDataFile[i], sizeof(Uint32));
} }
if (options->console) { if (Options::settings.console) {
std::cout << "Writing file " << filename.c_str() << std::endl; std::cout << "Writing file " << filename.c_str() << std::endl;
} }
// Cerramos el fichero // Cerramos el fichero
SDL_CloseIO(file); SDL_CloseIO(file);
} else { } else {
if (options->console) { if (Options::settings.console) {
std::cout << "Error: Unable to save " << filename.c_str() << " file! " << SDL_GetError() << std::endl; std::cout << "Error: Unable to save " << filename.c_str() << " file! " << SDL_GetError() << std::endl;
} }
} }
@@ -561,7 +559,7 @@ bool Game::saveScoreFile() {
// Guarda el fichero de datos para la demo // Guarda el fichero de datos para la demo
bool Game::saveDemoFile() { bool Game::saveDemoFile() {
bool success = true; bool success = true;
const std::string p = asset->get("demo.bin"); const std::string p = Asset::get()->get("demo.bin");
const std::string filename = p.substr(p.find_last_of("\\/") + 1); const std::string filename = p.substr(p.find_last_of("\\/") + 1);
if (demo.recording) { if (demo.recording) {
SDL_IOStream *file = SDL_IOFromFile(p.c_str(), "w+b"); SDL_IOStream *file = SDL_IOFromFile(p.c_str(), "w+b");
@@ -571,14 +569,14 @@ bool Game::saveDemoFile() {
SDL_WriteIO(file, &demo.dataFile[i], sizeof(demoKeys_t)); SDL_WriteIO(file, &demo.dataFile[i], sizeof(demoKeys_t));
} }
if (options->console) { if (Options::settings.console) {
std::cout << "Writing file " << filename.c_str() << std::endl; std::cout << "Writing file " << filename.c_str() << std::endl;
} }
// Cerramos el fichero // Cerramos el fichero
SDL_CloseIO(file); SDL_CloseIO(file);
} else { } else {
if (options->console) { if (Options::settings.console) {
std::cout << "Error: Unable to save " << filename.c_str() << " file! " << SDL_GetError() << std::endl; std::cout << "Error: Unable to save " << filename.c_str() << " file! " << SDL_GetError() << std::endl;
} }
} }
@@ -964,7 +962,6 @@ void Game::initEnemyFormations() {
// #24 - Treinta enemigos BALLOON1. Del centro hacia los extremos. Juntos. Simetricos // #24 - Treinta enemigos BALLOON1. Del centro hacia los extremos. Juntos. Simetricos
j = 24; j = 24;
enemyFormation[j].numberOfEnemies = 30; enemyFormation[j].numberOfEnemies = 30;
incX = 0;
incTime = 5; incTime = 5;
for (int i = 0; i < enemyFormation[j].numberOfEnemies; i++) { for (int i = 0; i < enemyFormation[j].numberOfEnemies; i++) {
Uint8 half = enemyFormation[j].numberOfEnemies / 2; Uint8 half = enemyFormation[j].numberOfEnemies / 2;
@@ -984,7 +981,6 @@ void Game::initEnemyFormations() {
// #25 - Treinta enemigos BALLOON1. Del centro hacia adentro. Juntos. Simetricos // #25 - Treinta enemigos BALLOON1. Del centro hacia adentro. Juntos. Simetricos
j = 25; j = 25;
enemyFormation[j].numberOfEnemies = 30; enemyFormation[j].numberOfEnemies = 30;
incX = BALLOON_WIDTH_1 + 1;
incTime = 5; incTime = 5;
for (int i = 0; i < enemyFormation[j].numberOfEnemies; i++) { for (int i = 0; i < enemyFormation[j].numberOfEnemies; i++) {
Uint8 half = enemyFormation[j].numberOfEnemies / 2; Uint8 half = enemyFormation[j].numberOfEnemies / 2;
@@ -1374,33 +1370,33 @@ void Game::renderScoreBoard() {
const int offsetRight = PLAY_AREA_RIGHT - 45; const int offsetRight = PLAY_AREA_RIGHT - 45;
// PLAYER1 - SCORE // PLAYER1 - SCORE
textScoreBoard->writeCentered(offsetLeft, offset1, lang->getText(53)); textScoreBoard->writeCentered(offsetLeft, offset1, Lang::get()->getText(53));
textScoreBoard->writeCentered(offsetLeft, offset2, updateScoreText(players[0]->getScore())); textScoreBoard->writeCentered(offsetLeft, offset2, updateScoreText(players[0]->getScore()));
// PLAYER1 - MULT // PLAYER1 - MULT
textScoreBoard->writeCentered(offsetLeft, offset3, lang->getText(55)); textScoreBoard->writeCentered(offsetLeft, offset3, Lang::get()->getText(55));
textScoreBoard->writeCentered(offsetLeft, offset4, std::to_string(players[0]->getScoreMultiplier()).substr(0, 3)); textScoreBoard->writeCentered(offsetLeft, offset4, std::to_string(players[0]->getScoreMultiplier()).substr(0, 3));
if (numPlayers == 2) { if (numPlayers == 2) {
// PLAYER2 - SCORE // PLAYER2 - SCORE
textScoreBoard->writeCentered(offsetRight, offset1, lang->getText(54)); textScoreBoard->writeCentered(offsetRight, offset1, Lang::get()->getText(54));
textScoreBoard->writeCentered(offsetRight, offset2, updateScoreText(players[1]->getScore())); textScoreBoard->writeCentered(offsetRight, offset2, updateScoreText(players[1]->getScore()));
// PLAYER2 - MULT // PLAYER2 - MULT
textScoreBoard->writeCentered(offsetRight, offset3, lang->getText(55)); textScoreBoard->writeCentered(offsetRight, offset3, Lang::get()->getText(55));
textScoreBoard->writeCentered(offsetRight, offset4, std::to_string(players[1]->getScoreMultiplier()).substr(0, 3)); textScoreBoard->writeCentered(offsetRight, offset4, std::to_string(players[1]->getScoreMultiplier()).substr(0, 3));
} else { } else {
// PLAYER2 - SCORE // PLAYER2 - SCORE
textScoreBoard->writeCentered(offsetRight, offset1, lang->getText(54)); textScoreBoard->writeCentered(offsetRight, offset1, Lang::get()->getText(54));
textScoreBoard->writeCentered(offsetRight, offset2, "0000000"); textScoreBoard->writeCentered(offsetRight, offset2, "0000000");
// PLAYER2 - MULT // PLAYER2 - MULT
textScoreBoard->writeCentered(offsetRight, offset3, lang->getText(55)); textScoreBoard->writeCentered(offsetRight, offset3, Lang::get()->getText(55));
textScoreBoard->writeCentered(offsetRight, offset4, "1.0"); textScoreBoard->writeCentered(offsetRight, offset4, "1.0");
} }
// STAGE // STAGE
textScoreBoard->writeCentered(PLAY_AREA_CENTER_X, offset1, lang->getText(57) + std::to_string(stage[currentStage].number)); textScoreBoard->writeCentered(PLAY_AREA_CENTER_X, offset1, Lang::get()->getText(57) + std::to_string(stage[currentStage].number));
// POWERMETER // POWERMETER
powerMeterSprite->setPosY(offset2); powerMeterSprite->setPosY(offset2);
@@ -1411,7 +1407,7 @@ void Game::renderScoreBoard() {
powerMeterSprite->render(); powerMeterSprite->render();
// HI-SCORE // HI-SCORE
textScoreBoard->writeCentered(PLAY_AREA_CENTER_X, offset3, lang->getText(56)); textScoreBoard->writeCentered(PLAY_AREA_CENTER_X, offset3, Lang::get()->getText(56));
textScoreBoard->writeCentered(PLAY_AREA_CENTER_X, offset4, hiScoreName + updateScoreText(hiScore)); textScoreBoard->writeCentered(PLAY_AREA_CENTER_X, offset4, hiScoreName + updateScoreText(hiScore));
} }
@@ -1489,12 +1485,12 @@ void Game::updateStage() {
// Actualiza el estado de muerte // Actualiza el estado de muerte
void Game::updateDeath() { void Game::updateDeath() {
// Comprueba si todos los jugadores estan muertos // Comprueba si todos los jugadores estan muertos
bool allPlayersAreDead = true; bool allDead = true;
for (auto player : players) { for (auto player : players) {
allPlayersAreDead &= (!player->isAlive()); allDead &= (!player->isAlive());
} }
if (allPlayersAreDead) { if (allDead) {
if (deathCounter > 0) { if (deathCounter > 0) {
deathCounter--; deathCounter--;
@@ -1611,30 +1607,6 @@ void Game::incBalloonSpeed() {
} }
} }
// Decrementa la velocidad de los globos
void Game::decBalloonSpeed() {
// La velocidad solo se decrementa en el modo normal
if (difficulty == DIFFICULTY_NORMAL) {
if (enemySpeed == BALLOON_SPEED_5) {
enemySpeed = BALLOON_SPEED_4;
}
else if (enemySpeed == BALLOON_SPEED_4) {
enemySpeed = BALLOON_SPEED_3;
}
else if (enemySpeed == BALLOON_SPEED_3) {
enemySpeed = BALLOON_SPEED_2;
}
else if (enemySpeed == BALLOON_SPEED_2) {
enemySpeed = BALLOON_SPEED_1;
}
setBalloonSpeed(enemySpeed);
}
}
// Actualiza la velocidad de los globos en funcion del poder acumulado de la fase // Actualiza la velocidad de los globos en funcion del poder acumulado de la fase
void Game::updateBalloonSpeed() { void Game::updateBalloonSpeed() {
const float percent = (float)stage[currentStage].currentPower / (float)stage[currentStage].powerToComplete; const float percent = (float)stage[currentStage].currentPower / (float)stage[currentStage].powerToComplete;
@@ -1764,17 +1736,6 @@ void Game::destroyBalloon(Balloon *balloon) {
evaluateAndSetMenace(); evaluateAndSetMenace();
} }
// Explosiona todos los globos
void Game::popAllBalloons() {
for (auto balloon : balloons) {
if ((balloon->isEnabled()) && (!balloon->isPopping()) && (!balloon->isBeingCreated())) {
popBalloon(balloon);
}
}
JA_PlaySound(balloonSound);
}
// Destruye todos los globos // Destruye todos los globos
void Game::destroyAllBalloons() { void Game::destroyAllBalloons() {
for (auto balloon : balloons) { for (auto balloon : balloons) {
@@ -1811,21 +1772,6 @@ void Game::startAllBalloons() {
} }
} }
// Obtiene el numero de globos activos
Uint8 Game::countBalloons() {
Uint8 num = 0;
for (auto balloon : balloons) {
if (balloon->isEnabled()) {
if (!balloon->isPopping()) {
num++;
}
}
}
return num;
}
// Vacia el vector de globos // Vacia el vector de globos
void Game::freeBalloons() { void Game::freeBalloons() {
if (balloons.empty() == false) { if (balloons.empty() == false) {
@@ -2093,7 +2039,7 @@ void Game::freeItems() {
} }
// Crea un objeto SmartSprite para mostrar la puntuación al coger un objeto // Crea un objeto SmartSprite para mostrar la puntuación al coger un objeto
void Game::createItemScoreSprite(int x, int y, SmartSprite *sprite) { void Game::createItemScoreSprite(int x, int y, const SmartSprite *sprite) {
SmartSprite *ss = new SmartSprite(nullptr, renderer); SmartSprite *ss = new SmartSprite(nullptr, renderer);
smartSprites.push_back(ss); smartSprites.push_back(ss);
@@ -2238,12 +2184,8 @@ void Game::updateDeathSequence() {
// Calcula y establece el valor de amenaza en funcion de los globos activos // Calcula y establece el valor de amenaza en funcion de los globos activos
void Game::evaluateAndSetMenace() { void Game::evaluateAndSetMenace() {
menaceCurrent = 0; menaceCurrent = std::accumulate(balloons.begin(), balloons.end(), Uint8(0),
for (auto balloon : balloons) { [](Uint8 acc, Balloon *b) { return b->isEnabled() ? acc + b->getMenace() : acc; });
if (balloon->isEnabled()) {
menaceCurrent += balloon->getMenace();
}
}
} }
// Obtiene el valor de la variable // Obtiene el valor de la variable
@@ -2457,10 +2399,10 @@ void Game::renderBackground() {
// Dibuja el juego // Dibuja el juego
void Game::render() { void Game::render() {
// Prepara para empezar a dibujar en la textura de juego // Prepara para empezar a dibujar en la textura de juego
screen->start(); Screen::get()->start();
// Limpia la pantalla // Limpia la pantalla
screen->clean(bgColor); Screen::get()->clean(bgColor);
// Dibuja los objetos // Dibuja los objetos
renderBackground(); renderBackground();
@@ -2483,7 +2425,7 @@ void Game::render() {
renderFlashEffect(); renderFlashEffect();
// Vuelca el contenido del renderizador en pantalla // Vuelca el contenido del renderizador en pantalla
screen->blit(); Screen::get()->blit();
} }
// Gestiona el nivel de amenaza // Gestiona el nivel de amenaza
@@ -2517,18 +2459,8 @@ void Game::checkGameInput() {
demo.keys.fireLeft = 0; demo.keys.fireLeft = 0;
demo.keys.fireRight = 0; demo.keys.fireRight = 0;
// Comprueba las teclas de cambiar el tamaño de la centana y el modo de video // Atalls globals (zoom finestra, fullscreen, shaders, presets, ...)
if (input->checkInput(input_window_fullscreen, REPEAT_FALSE)) { GlobalInputs::handle();
screen->toggleVideoMode();
}
else if (input->checkInput(input_window_dec_size, REPEAT_FALSE)) {
screen->decWindowZoom();
}
else if (input->checkInput(input_window_inc_size, REPEAT_FALSE)) {
screen->incWindowZoom();
}
// Modo Demo activo // Modo Demo activo
if (demo.enabled) { if (demo.enabled) {
@@ -2570,7 +2502,7 @@ void Game::checkGameInput() {
} }
// Si se pulsa cualquier tecla, se sale del modo demo // Si se pulsa cualquier tecla, se sale del modo demo
if (input->checkAnyInput()) { if (Input::get()->checkAnyInput()) {
section->name = SECTION_PROG_TITLE; section->name = SECTION_PROG_TITLE;
} }
@@ -2588,12 +2520,12 @@ void Game::checkGameInput() {
for (auto player : players) { for (auto player : players) {
if (player->isAlive()) { if (player->isAlive()) {
// Input a la izquierda // Input a la izquierda
if (input->checkInput(input_left, REPEAT_TRUE, options->input[i].deviceType, options->input[i].id)) { if (Input::get()->checkInput(input_left, REPEAT_TRUE, Options::inputs[i].deviceType, Options::inputs[i].id)) {
player->setInput(input_left); player->setInput(input_left);
demo.keys.left = 1; demo.keys.left = 1;
} else { } else {
// Input a la derecha // Input a la derecha
if (input->checkInput(input_right, REPEAT_TRUE, options->input[i].deviceType, options->input[i].id)) { if (Input::get()->checkInput(input_right, REPEAT_TRUE, Options::inputs[i].deviceType, Options::inputs[i].id)) {
player->setInput(input_right); player->setInput(input_right);
demo.keys.right = 1; demo.keys.right = 1;
} else { } else {
@@ -2603,7 +2535,7 @@ void Game::checkGameInput() {
} }
} }
// Comprueba el input de disparar al centro // Comprueba el input de disparar al centro
if (input->checkInput(input_fire_center, REPEAT_TRUE, options->input[i].deviceType, options->input[i].id)) { if (Input::get()->checkInput(input_fire_center, REPEAT_TRUE, Options::inputs[i].deviceType, Options::inputs[i].id)) {
if (player->canFire()) { if (player->canFire()) {
player->setInput(input_fire_center); player->setInput(input_fire_center);
createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BULLET_UP, player->isPowerUp(), i); createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BULLET_UP, player->isPowerUp(), i);
@@ -2617,7 +2549,7 @@ void Game::checkGameInput() {
} }
// Comprueba el input de disparar a la izquierda // Comprueba el input de disparar a la izquierda
if (input->checkInput(input_fire_left, REPEAT_TRUE, options->input[i].deviceType, options->input[i].id)) { if (Input::get()->checkInput(input_fire_left, REPEAT_TRUE, Options::inputs[i].deviceType, Options::inputs[i].id)) {
if (player->canFire()) { if (player->canFire()) {
player->setInput(input_fire_left); player->setInput(input_fire_left);
createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BULLET_LEFT, player->isPowerUp(), i); createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BULLET_LEFT, player->isPowerUp(), i);
@@ -2631,7 +2563,7 @@ void Game::checkGameInput() {
} }
// Comprueba el input de disparar a la derecha // Comprueba el input de disparar a la derecha
if (input->checkInput(input_fire_right, REPEAT_TRUE, options->input[i].deviceType, options->input[i].id)) { if (Input::get()->checkInput(input_fire_right, REPEAT_TRUE, Options::inputs[i].deviceType, Options::inputs[i].id)) {
if (player->canFire()) { if (player->canFire()) {
player->setInput(input_fire_right); player->setInput(input_fire_right);
createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BULLET_RIGHT, player->isPowerUp(), i); createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BULLET_RIGHT, player->isPowerUp(), i);
@@ -2645,7 +2577,7 @@ void Game::checkGameInput() {
} }
// Comprueba el input de pausa // Comprueba el input de pausa
if (input->checkInput(input_pause, REPEAT_FALSE, options->input[i].deviceType, options->input[i].id)) { if (Input::get()->checkInput(input_pause, REPEAT_FALSE, Options::inputs[i].deviceType, Options::inputs[i].id)) {
section->subsection = SUBSECTION_GAME_PAUSE; section->subsection = SUBSECTION_GAME_PAUSE;
} }
@@ -2668,13 +2600,13 @@ void Game::checkGameInput() {
void Game::renderMessages() { void Game::renderMessages() {
// GetReady // GetReady
if ((counter < STAGE_COUNTER) && (!demo.enabled)) { if ((counter < STAGE_COUNTER) && (!demo.enabled)) {
textNokiaBig2->write((int)getReadyBitmapPath[counter], PLAY_AREA_CENTER_Y - 8, lang->getText(75), -2); textNokiaBig2->write((int)getReadyBitmapPath[counter], PLAY_AREA_CENTER_Y - 8, Lang::get()->getText(75), -2);
} }
// Time Stopped // Time Stopped
if (timeStopped) { if (timeStopped) {
if ((timeStoppedCounter > 100) || (timeStoppedCounter % 10 > 4)) { if ((timeStoppedCounter > 100) || (timeStoppedCounter % 10 > 4)) {
textNokia2->writeDX(TXT_CENTER, PLAY_AREA_CENTER_X, PLAY_AREA_FIRST_QUARTER_Y, lang->getText(36) + std::to_string(timeStoppedCounter / 10), -1, noColor, 1, shdwTxtColor); textNokia2->writeDX(TXT_CENTER, PLAY_AREA_CENTER_X, PLAY_AREA_FIRST_QUARTER_Y, Lang::get()->getText(36) + std::to_string(timeStoppedCounter / 10), -1, noColor, 1, shdwTxtColor);
} }
if (timeStoppedCounter > 100) { if (timeStoppedCounter > 100) {
@@ -2691,27 +2623,27 @@ void Game::renderMessages() {
// D E M O // D E M O
if (demo.enabled) { if (demo.enabled) {
if (demo.counter % 30 > 14) { if (demo.counter % 30 > 14) {
textNokiaBig2->writeDX(TXT_CENTER, PLAY_AREA_CENTER_X, PLAY_AREA_FIRST_QUARTER_Y, lang->getText(37), 0, noColor, 2, shdwTxtColor); textNokiaBig2->writeDX(TXT_CENTER, PLAY_AREA_CENTER_X, PLAY_AREA_FIRST_QUARTER_Y, Lang::get()->getText(37), 0, noColor, 2, shdwTxtColor);
} }
} }
// STAGE NUMBER // STAGE NUMBER
if (stageBitmapCounter < STAGE_COUNTER) { if (stageBitmapCounter < STAGE_COUNTER) {
const int stageNum = stage[currentStage].number; const int stageNum = stage[currentStage].number;
std::string text; std::string stageText;
if (stageNum == 10) { // Ultima fase if (stageNum == 10) { // Ultima fase
text = lang->getText(79); stageText = Lang::get()->getText(79);
} else { // X fases restantes } else { // X fases restantes
text = std::to_string(11 - stage[currentStage].number) + lang->getText(38); stageText = std::to_string(11 - stage[currentStage].number) + Lang::get()->getText(38);
} }
if (!gameCompleted) { // Escribe el numero de fases restantes if (!gameCompleted) { // Escribe el numero de fases restantes
textNokiaBig2->writeDX(TXT_CENTER, PLAY_AREA_CENTER_X, stageBitmapPath[stageBitmapCounter], text, -2, noColor, 2, shdwTxtColor); textNokiaBig2->writeDX(TXT_CENTER, PLAY_AREA_CENTER_X, stageBitmapPath[stageBitmapCounter], stageText, -2, noColor, 2, shdwTxtColor);
} else { // Escribe el texto de juego completado } else { // Escribe el texto de juego completado
text = lang->getText(50); stageText = Lang::get()->getText(50);
textNokiaBig2->writeDX(TXT_CENTER, PLAY_AREA_CENTER_X, stageBitmapPath[stageBitmapCounter], text, -2, noColor, 1, shdwTxtColor); textNokiaBig2->writeDX(TXT_CENTER, PLAY_AREA_CENTER_X, stageBitmapPath[stageBitmapCounter], stageText, -2, noColor, 1, shdwTxtColor);
textNokia2->writeDX(TXT_CENTER, PLAY_AREA_CENTER_X, stageBitmapPath[stageBitmapCounter] + textNokiaBig2->getCharacterSize() + 2, lang->getText(76), -1, noColor, 1, shdwTxtColor); textNokia2->writeDX(TXT_CENTER, PLAY_AREA_CENTER_X, stageBitmapPath[stageBitmapCounter] + textNokiaBig2->getCharacterSize() + 2, Lang::get()->getText(76), -1, noColor, 1, shdwTxtColor);
} }
} }
} }
@@ -2826,7 +2758,7 @@ bool Game::hasFinished() const {
} }
// Procesa un evento individual // Procesa un evento individual
void Game::handleEvent(SDL_Event *event) { void Game::handleEvent(const SDL_Event *event) {
// SDL_EVENT_QUIT ya lo maneja Director // SDL_EVENT_QUIT ya lo maneja Director
if (event->type == SDL_EVENT_WINDOW_FOCUS_LOST) { if (event->type == SDL_EVENT_WINDOW_FOCUS_LOST) {
@@ -2870,6 +2802,9 @@ void Game::updatePausedGame() {
// Actualiza el contador de ticks // Actualiza el contador de ticks
ticks = SDL_GetTicks(); ticks = SDL_GetTicks();
// Atalls globals (zoom finestra, fullscreen, shaders, presets, ...)
GlobalInputs::handle();
if (leavingPauseMenu) { if (leavingPauseMenu) {
if (pauseCounter > 0) { // El contador está descendiendo if (pauseCounter > 0) { // El contador está descendiendo
const bool a = pauseCounter == 90; const bool a = pauseCounter == 90;
@@ -2922,10 +2857,10 @@ void Game::updatePausedGame() {
// Dibuja el menu de pausa del juego // Dibuja el menu de pausa del juego
void Game::renderPausedGame() { void Game::renderPausedGame() {
// Prepara para empezar a dibujar en la textura de juego // Prepara para empezar a dibujar en la textura de juego
screen->start(); Screen::get()->start();
// Limpia la pantalla // Limpia la pantalla
screen->clean(bgColor); Screen::get()->clean(bgColor);
// Pinta el escenario // Pinta el escenario
{ {
@@ -2957,7 +2892,7 @@ void Game::renderPausedGame() {
fade->render(); fade->render();
// Vuelca el contenido del renderizador en pantalla // Vuelca el contenido del renderizador en pantalla
screen->blit(); Screen::get()->blit();
} }
// Inicializa el estado de pausa del juego // Inicializa el estado de pausa del juego
@@ -2983,6 +2918,9 @@ void Game::updateGameOverScreen() {
// Actualiza el contador de ticks // Actualiza el contador de ticks
ticks = SDL_GetTicks(); ticks = SDL_GetTicks();
// Atalls globals (zoom finestra, fullscreen, shaders, presets, ...)
GlobalInputs::handle();
// Actualiza la lógica del menu // Actualiza la lógica del menu
gameOverMenu->update(); gameOverMenu->update();
@@ -3032,30 +2970,13 @@ void Game::updateGameOverScreen() {
} }
} }
// Comprueba los eventos de la pantalla de game over
void Game::checkGameOverEvents() {
while (SDL_PollEvent(eventHandler) != 0) {
// Evento de salida de la aplicación
if (eventHandler->type == SDL_EVENT_QUIT) {
section->name = SECTION_PROG_QUIT;
break;
} else if (eventHandler->type == SDL_EVENT_KEY_DOWN && eventHandler->key.repeat == 0) {
if (gameCompleted) {
gameOverPostFade = 1;
fade->activateFade();
JA_PlaySound(itemPickUpSound);
}
}
}
}
// Dibuja los elementos de la pantalla de game over // Dibuja los elementos de la pantalla de game over
void Game::renderGameOverScreen() { void Game::renderGameOverScreen() {
// Prepara para empezar a dibujar en la textura de juego // Prepara para empezar a dibujar en la textura de juego
screen->start(); Screen::get()->start();
// Limpia la pantalla // Limpia la pantalla
screen->clean(bgColor); Screen::get()->clean(bgColor);
// Dibujo // Dibujo
if (!gameCompleted) { // Dibujo de haber perdido la partida if (!gameCompleted) { // Dibujo de haber perdido la partida
@@ -3068,33 +2989,33 @@ void Game::renderGameOverScreen() {
if (numPlayers == 1) { if (numPlayers == 1) {
// Congratulations!! // Congratulations!!
if (gameCompleted) { if (gameCompleted) {
text->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 8), lang->getText(50)); text->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 8), Lang::get()->getText(50));
} }
// Game Over // Game Over
textBig->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 6), lang->getText(43)); textBig->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 6), Lang::get()->getText(43));
// Your Score // Your Score
text->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 3), lang->getText(44) + std::to_string(players[0]->getScore())); text->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 3), Lang::get()->getText(44) + std::to_string(players[0]->getScore()));
} else { } else {
// Congratulations!! // Congratulations!!
if (gameCompleted) { if (gameCompleted) {
text->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 9), lang->getText(50)); text->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 9), Lang::get()->getText(50));
} }
// Game Over // Game Over
textBig->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 7), lang->getText(43)); textBig->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 7), Lang::get()->getText(43));
// Player1 Score // Player1 Score
text->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 4), lang->getText(77) + std::to_string(players[0]->getScore())); text->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 4), Lang::get()->getText(77) + std::to_string(players[0]->getScore()));
// Player2 Score // Player2 Score
text->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 2), lang->getText(78) + std::to_string(players[1]->getScore())); text->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 2), Lang::get()->getText(78) + std::to_string(players[1]->getScore()));
} }
// Continue? // Continue?
if (!gameCompleted) { // Solo dibuja el menu de continuar en el caso de no haber completado la partida if (!gameCompleted) { // Solo dibuja el menu de continuar en el caso de no haber completado la partida
text->writeCentered(199, PLAY_AREA_CENTER_Y + BLOCK * 3, lang->getText(45)); text->writeCentered(199, PLAY_AREA_CENTER_Y + BLOCK * 3, Lang::get()->getText(45));
gameOverMenu->render(); gameOverMenu->render();
} }
@@ -3102,7 +3023,7 @@ void Game::renderGameOverScreen() {
fade->render(); fade->render();
// Vuelca el contenido del renderizador en pantalla // Vuelca el contenido del renderizador en pantalla
screen->blit(); Screen::get()->blit();
} }
// Inicializa el estado de game over // Inicializa el estado de game over
@@ -3127,15 +3048,8 @@ bool Game::canPowerBallBeCreated() {
// Calcula el poder actual de los globos en pantalla // Calcula el poder actual de los globos en pantalla
int Game::calculateScreenPower() { int Game::calculateScreenPower() {
int power = 0; return std::accumulate(balloons.begin(), balloons.end(), 0,
[](int acc, Balloon *b) { return b->isEnabled() ? acc + b->getPower() : acc; });
for (auto balloon : balloons) {
if (balloon->isEnabled()) {
power += balloon->getPower();
}
}
return power;
} }
// Inicializa las variables que contienen puntos de ruta para mover objetos // Inicializa las variables que contienen puntos de ruta para mover objetos
@@ -3167,7 +3081,7 @@ void Game::initPaths() {
} }
// Letrero de GetReady // Letrero de GetReady
const int size = textNokiaBig2->lenght(lang->getText(75), -2); const int size = textNokiaBig2->lenght(Lang::get()->getText(75), -2);
const float start1 = PLAY_AREA_LEFT - size; const float start1 = PLAY_AREA_LEFT - size;
const float finish1 = PLAY_AREA_CENTER_X - (size / 2); const float finish1 = PLAY_AREA_CENTER_X - (size / 2);
@@ -3241,28 +3155,6 @@ bool Game::allPlayersAreDead() {
return success; return success;
} }
// Comprueba los eventos que hay en cola
void Game::checkEvents() {
while (SDL_PollEvent(eventHandler) != 0) {
// Evento de salida de la aplicación
if (eventHandler->type == SDL_EVENT_QUIT) {
section->name = SECTION_PROG_QUIT;
break;
}
else if (eventHandler->type == SDL_EVENT_WINDOW_FOCUS_LOST) {
section->subsection = SUBSECTION_GAME_PAUSE;
}
#ifdef PAUSE
else if (eventHandler->type == SDL_EVENT_KEY_DOWN) {
if (eventHandler->key.scancode == SDL_SCANCODE_P) {
pause = !pause;
}
}
#endif
}
}
// Elimina todos los objetos contenidos en vectores // Elimina todos los objetos contenidos en vectores
void Game::deleteAllVectorObjects() { void Game::deleteAllVectorObjects() {
for (auto player : players) { for (auto player : players) {
@@ -3291,33 +3183,6 @@ void Game::deleteAllVectorObjects() {
smartSprites.clear(); smartSprites.clear();
} }
// Recarga las texturas
void Game::reloadTextures() {
for (auto texture : itemTextures) {
texture->reLoad();
}
for (auto texture : balloonTextures) {
texture->reLoad();
}
for (auto texture : player1Textures) {
texture->reLoad();
}
for (auto texture : player2Textures) {
texture->reLoad();
}
bulletTexture->reLoad();
gameBuildingsTexture->reLoad();
gameCloudsTexture->reLoad();
gameGrassTexture->reLoad();
gamePowerMeterTexture->reLoad();
gameSkyColorsTexture->reLoad();
gameTextTexture->reLoad();
}
// Establece la máxima puntuación desde fichero o desde las puntuaciones online // Establece la máxima puntuación desde fichero o desde las puntuaciones online
void Game::setHiScore() { void Game::setHiScore() {
// Carga el fichero de puntos // Carga el fichero de puntos

View File

@@ -5,18 +5,14 @@
#include <string> // for string, basic_string #include <string> // for string, basic_string
#include <vector> // for vector #include <vector> // for vector
#include "utils.h" // for demoKeys_t, color_t #include "utils/utils.h" // for demoKeys_t, color_t
class Asset;
class Balloon; class Balloon;
class Bullet; class Bullet;
class Fade; class Fade;
class Input;
class Item; class Item;
class Lang;
class Menu; class Menu;
class MovingSprite; class MovingSprite;
class Player; class Player;
class Screen;
class SmartSprite; class SmartSprite;
class Sprite; class Sprite;
class Text; class Text;
@@ -131,10 +127,6 @@ class Game {
// Objetos y punteros // Objetos y punteros
SDL_Renderer *renderer; // El renderizador de la ventana SDL_Renderer *renderer; // El renderizador de la ventana
Screen *screen; // Objeto encargado de dibujar en pantalla
Asset *asset; // Objeto que gestiona todos los ficheros de recursos
Lang *lang; // Objeto para gestionar los textos en diferentes idiomas
Input *input; // Manejador de entrada
section_t *section; // Seccion actual dentro del juego section_t *section; // Seccion actual dentro del juego
std::vector<Player *> players; // Vector con los jugadores std::vector<Player *> players; // Vector con los jugadores
@@ -245,7 +237,6 @@ class Game {
Uint8 difficulty; // Dificultad del juego Uint8 difficulty; // Dificultad del juego
float difficultyScoreMultiplier; // Multiplicador de puntos en función de la dificultad float difficultyScoreMultiplier; // Multiplicador de puntos en función de la dificultad
color_t difficultyColor; // Color asociado a la dificultad color_t difficultyColor; // Color asociado a la dificultad
struct options_t *options; // Variable con todas las variables de las opciones del programa
Uint8 onePlayerControl; // Variable para almacenar el valor de las opciones Uint8 onePlayerControl; // Variable para almacenar el valor de las opciones
enemyFormation_t enemyFormation[NUMBER_OF_ENEMY_FORMATIONS]; // Vector con todas las formaciones enemigas enemyFormation_t enemyFormation[NUMBER_OF_ENEMY_FORMATIONS]; // Vector con todas las formaciones enemigas
enemyPool_t enemyPool[10]; // Variable con los diferentes conjuntos de formaciones enemigas enemyPool_t enemyPool[10]; // Variable con los diferentes conjuntos de formaciones enemigas
@@ -268,9 +259,6 @@ class Game {
// Dibuja el juego // Dibuja el juego
void render(); void render();
// Comprueba los eventos que hay en cola
void checkEvents();
// Inicializa las variables necesarias para la sección 'Game' // Inicializa las variables necesarias para la sección 'Game'
void init(); void init();
@@ -349,9 +337,6 @@ class Game {
// Incrementa la velocidad de los globos // Incrementa la velocidad de los globos
void incBalloonSpeed(); void incBalloonSpeed();
// Decrementa la velocidad de los globos
void decBalloonSpeed();
// Actualiza la velocidad de los globos en funcion del poder acumulado de la fase // Actualiza la velocidad de los globos en funcion del poder acumulado de la fase
void updateBalloonSpeed(); void updateBalloonSpeed();
@@ -361,9 +346,6 @@ class Game {
// Explosiona un globo. Lo destruye // Explosiona un globo. Lo destruye
void destroyBalloon(Balloon *balloon); void destroyBalloon(Balloon *balloon);
// Explosiona todos los globos
void popAllBalloons();
// Destruye todos los globos // Destruye todos los globos
void destroyAllBalloons(); void destroyAllBalloons();
@@ -373,9 +355,6 @@ class Game {
// Pone en marcha todos los globos // Pone en marcha todos los globos
void startAllBalloons(); void startAllBalloons();
// Obtiene el numero de globos activos
Uint8 countBalloons();
// Vacia el vector de globos // Vacia el vector de globos
void freeBalloons(); void freeBalloons();
@@ -416,7 +395,7 @@ class Game {
void freeItems(); void freeItems();
// Crea un objeto SmartSprite // Crea un objeto SmartSprite
void createItemScoreSprite(int x, int y, SmartSprite *sprite); void createItemScoreSprite(int x, int y, const SmartSprite *sprite);
// Vacia el vector de smartsprites // Vacia el vector de smartsprites
void freeSmartSprites(); void freeSmartSprites();
@@ -514,9 +493,6 @@ class Game {
// Inicializa el estado de game over // Inicializa el estado de game over
void enterGameOverScreen(); void enterGameOverScreen();
// Comprueba los eventos de la pantalla de game over
void checkGameOverEvents();
// Indica si se puede crear una powerball // Indica si se puede crear una powerball
bool canPowerBallBeCreated(); bool canPowerBallBeCreated();
@@ -538,15 +514,12 @@ class Game {
// Elimina todos los objetos contenidos en vectores // Elimina todos los objetos contenidos en vectores
void deleteAllVectorObjects(); void deleteAllVectorObjects();
// Recarga las texturas
void reloadTextures();
// Establece la máxima puntuación desde fichero o desde las puntuaciones online // Establece la máxima puntuación desde fichero o desde las puntuaciones online
void setHiScore(); void setHiScore();
public: public:
// Constructor // Constructor
Game(int numPlayers, int currentStage, SDL_Renderer *renderer, Screen *screen, Asset *asset, Lang *lang, Input *input, bool demo, options_t *options, section_t *section); Game(int numPlayers, int currentStage, SDL_Renderer *renderer, bool demo, section_t *section);
// Destructor // Destructor
~Game(); ~Game();
@@ -561,5 +534,5 @@ class Game {
bool hasFinished() const; bool hasFinished() const;
// Procesa un evento // Procesa un evento
void handleEvent(SDL_Event *event); void handleEvent(const SDL_Event *event);
}; };

574
source/game/options.cpp Normal file
View File

@@ -0,0 +1,574 @@
#include "game/options.hpp"
#include <SDL3/SDL.h>
#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
#include "core/input/input.h" // for INPUT_USE_KEYBOARD, INPUT_USE_GAMECONTROLLER
#include "core/locale/lang.h" // for MAX_LANGUAGES, en_UK
#include "external/fkyaml_node.hpp" // for fkyaml::node
#include "utils/utils.h" // for boolToString
namespace Options {
// --- Variables globales ---
Window window;
Video video;
Audio audio;
Loading loading;
Settings settings;
std::vector<input_t> inputs;
std::vector<PostFXPreset> postfx_presets;
std::string postfx_file_path;
int current_postfx_preset = 0;
std::vector<CrtPiPreset> crtpi_presets;
std::string crtpi_file_path;
int current_crtpi_preset = 0;
// --- Helpers locals ---
namespace {
void parseBoolField(const fkyaml::node &node, const std::string &key, bool &target) {
if (node.contains(key)) {
try {
target = node[key].get_value<bool>();
} catch (...) {}
}
}
void parseIntField(const fkyaml::node &node, const std::string &key, int &target) {
if (node.contains(key)) {
try {
target = node[key].get_value<int>();
} catch (...) {}
}
}
void parseStringField(const fkyaml::node &node, const std::string &key, std::string &target) {
if (node.contains(key)) {
try {
target = node[key].get_value<std::string>();
} catch (...) {}
}
}
void loadWindowFromYaml(const fkyaml::node &yaml) {
if (!yaml.contains("window")) { return; }
const auto &win = yaml["window"];
parseIntField(win, "zoom", window.zoom);
if (window.zoom < 1 || window.zoom > window.max_zoom) {
window.zoom = Defaults::Window::ZOOM;
}
}
void loadVideoFromYaml(const fkyaml::node &yaml) {
if (!yaml.contains("video")) { return; }
const auto &vid = yaml["video"];
parseBoolField(vid, "fullscreen", video.fullscreen);
parseBoolField(vid, "vsync", video.vsync);
parseBoolField(vid, "integer_scale", video.integer_scale);
if (vid.contains("scale_mode")) {
try {
video.scale_mode = static_cast<SDL_ScaleMode>(vid["scale_mode"].get_value<int>());
} catch (...) {}
}
if (vid.contains("gpu")) {
const auto &gpu = vid["gpu"];
parseBoolField(gpu, "acceleration", video.gpu.acceleration);
parseStringField(gpu, "preferred_driver", video.gpu.preferred_driver);
}
if (vid.contains("supersampling")) {
const auto &ss = vid["supersampling"];
parseBoolField(ss, "enabled", video.supersampling.enabled);
parseBoolField(ss, "linear_upscale", video.supersampling.linear_upscale);
parseIntField(ss, "downscale_algo", video.supersampling.downscale_algo);
}
if (vid.contains("shader")) {
const auto &sh = vid["shader"];
parseBoolField(sh, "enabled", video.shader.enabled);
if (sh.contains("current_shader")) {
try {
auto s = sh["current_shader"].get_value<std::string>();
video.shader.current_shader = (s == "crtpi" || s == "CRTPI")
? Rendering::ShaderType::CRTPI
: Rendering::ShaderType::POSTFX;
} catch (...) {}
}
parseStringField(sh, "current_postfx_preset", video.shader.current_postfx_preset_name);
parseStringField(sh, "current_crtpi_preset", video.shader.current_crtpi_preset_name);
}
}
void loadAudioFromYaml(const fkyaml::node &yaml) {
if (!yaml.contains("audio")) { return; }
const auto &aud = yaml["audio"];
parseBoolField(aud, "enabled", audio.enabled);
if (aud.contains("volume")) {
try {
audio.volume = std::clamp(aud["volume"].get_value<int>(), 0, 100);
} catch (...) {}
}
if (aud.contains("music")) {
const auto &mus = aud["music"];
parseBoolField(mus, "enabled", audio.music.enabled);
if (mus.contains("volume")) {
try {
audio.music.volume = std::clamp(mus["volume"].get_value<int>(), 0, 100);
} catch (...) {}
}
}
if (aud.contains("sound")) {
const auto &snd = aud["sound"];
parseBoolField(snd, "enabled", audio.sound.enabled);
if (snd.contains("volume")) {
try {
audio.sound.volume = std::clamp(snd["volume"].get_value<int>(), 0, 100);
} catch (...) {}
}
}
}
void loadLoadingFromYaml(const fkyaml::node &yaml) {
if (!yaml.contains("loading")) { return; }
const auto &ld = yaml["loading"];
parseBoolField(ld, "show", loading.show);
parseBoolField(ld, "show_resource_name", loading.show_resource_name);
parseBoolField(ld, "wait_for_input", loading.wait_for_input);
}
void loadSettingsFromYaml(const fkyaml::node &yaml) {
if (!yaml.contains("settings")) { return; }
const auto &st = yaml["settings"];
parseIntField(st, "difficulty", settings.difficulty);
parseIntField(st, "language", settings.language);
if (settings.language < 0 || settings.language > MAX_LANGUAGES) {
settings.language = en_UK;
}
if (st.contains("palette")) {
try {
settings.palette = static_cast<palette_e>(st["palette"].get_value<int>());
} catch (...) {}
}
parseIntField(st, "player_selected", settings.player_selected);
}
void loadInputsFromYaml(const fkyaml::node &yaml) {
if (!yaml.contains("input") || inputs.size() < 2) { return; }
const auto &ins = yaml["input"];
size_t i = 0;
for (const auto &entry : ins) {
if (i >= inputs.size()) { break; }
if (entry.contains("device_type")) {
try {
inputs[i].deviceType = static_cast<Uint8>(entry["device_type"].get_value<int>());
} catch (...) {}
}
++i;
}
}
} // namespace
// --- Funciones públiques ---
void setConfigFile(const std::string &file_path) {
settings.config_file = file_path;
}
void init() {
// Reinicia structs a defaults (els member-initializers ho fan sols).
window = Window{};
video = Video{};
audio = Audio{};
loading = Loading{};
// Preserva config_file si ja s'ha establert abans.
const std::string PREV_CONFIG_FILE = settings.config_file;
settings = Settings{};
settings.config_file = PREV_CONFIG_FILE;
#ifdef __EMSCRIPTEN__
// En Emscripten la ventana la gestiona el navegador
window.zoom = 4;
video.fullscreen = false;
video.integer_scale = true;
#endif
// Dispositius d'entrada per defecte
inputs.clear();
input_t kb;
kb.id = 0;
kb.name = "KEYBOARD";
kb.deviceType = INPUT_USE_KEYBOARD;
inputs.push_back(kb);
input_t gc;
gc.id = 0;
gc.name = "GAME CONTROLLER";
gc.deviceType = INPUT_USE_GAMECONTROLLER;
inputs.push_back(gc);
}
auto loadFromFile() -> bool {
init();
std::ifstream file(settings.config_file);
if (!file.is_open()) {
// Primera execució: crea el YAML amb defaults.
return saveToFile();
}
const std::string CONTENT((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
file.close();
try {
auto yaml = fkyaml::node::deserialize(CONTENT);
int file_version = 0;
if (yaml.contains("config_version")) {
try {
file_version = yaml["config_version"].get_value<int>();
} catch (...) {}
}
if (file_version != Settings::CURRENT_CONFIG_VERSION) {
std::cout << "Config version " << file_version
<< " != expected " << Settings::CURRENT_CONFIG_VERSION
<< ". Recreating defaults.\n";
init();
return saveToFile();
}
loadWindowFromYaml(yaml);
loadVideoFromYaml(yaml);
loadAudioFromYaml(yaml);
loadLoadingFromYaml(yaml);
loadSettingsFromYaml(yaml);
loadInputsFromYaml(yaml);
} catch (const fkyaml::exception &e) {
std::cout << "Error parsing YAML config: " << e.what() << ". Using defaults.\n";
init();
return saveToFile();
}
return true;
}
auto saveToFile() -> bool {
if (settings.config_file.empty()) { return false; }
std::ofstream file(settings.config_file);
if (!file.is_open()) {
std::cout << "Error: " << settings.config_file << " can't be opened for writing\n";
return false;
}
file << "# Coffee Crisis - Configuration file\n";
file << "# Auto-generated, managed by the game.\n\n";
file << "config_version: " << settings.config_version << "\n\n";
// WINDOW
file << "# WINDOW\n";
file << "window:\n";
file << " zoom: " << window.zoom << "\n";
file << " max_zoom: " << window.max_zoom << "\n\n";
// VIDEO
file << "# VIDEO\n";
file << "video:\n";
file << " fullscreen: " << boolToString(video.fullscreen) << "\n";
file << " vsync: " << boolToString(video.vsync) << "\n";
file << " integer_scale: " << boolToString(video.integer_scale) << "\n";
file << " scale_mode: " << static_cast<int>(video.scale_mode)
<< " # " << static_cast<int>(SDL_SCALEMODE_NEAREST) << ": nearest, "
<< static_cast<int>(SDL_SCALEMODE_LINEAR) << ": linear\n";
file << " gpu:\n";
file << " acceleration: " << boolToString(video.gpu.acceleration) << "\n";
file << " preferred_driver: \"" << video.gpu.preferred_driver << "\"\n";
file << " supersampling:\n";
file << " enabled: " << boolToString(video.supersampling.enabled) << "\n";
file << " linear_upscale: " << boolToString(video.supersampling.linear_upscale) << "\n";
file << " downscale_algo: " << video.supersampling.downscale_algo << "\n";
file << " shader:\n";
file << " enabled: " << boolToString(video.shader.enabled) << "\n";
file << " current_shader: "
<< (video.shader.current_shader == Rendering::ShaderType::CRTPI ? "crtpi" : "postfx")
<< "\n";
file << " current_postfx_preset: \"" << video.shader.current_postfx_preset_name << "\"\n";
file << " current_crtpi_preset: \"" << video.shader.current_crtpi_preset_name << "\"\n\n";
// AUDIO
file << "# AUDIO (volume range: 0..100)\n";
file << "audio:\n";
file << " enabled: " << boolToString(audio.enabled) << "\n";
file << " volume: " << audio.volume << "\n";
file << " music:\n";
file << " enabled: " << boolToString(audio.music.enabled) << "\n";
file << " volume: " << audio.music.volume << "\n";
file << " sound:\n";
file << " enabled: " << boolToString(audio.sound.enabled) << "\n";
file << " volume: " << audio.sound.volume << "\n\n";
// LOADING
file << "# LOADING SCREEN\n";
file << "loading:\n";
file << " show: " << boolToString(loading.show) << "\n";
file << " show_resource_name: " << boolToString(loading.show_resource_name) << "\n";
file << " wait_for_input: " << boolToString(loading.wait_for_input) << "\n\n";
// SETTINGS
file << "# SETTINGS\n";
file << "settings:\n";
file << " difficulty: " << settings.difficulty << "\n";
file << " language: " << settings.language << "\n";
file << " palette: " << static_cast<int>(settings.palette) << "\n";
file << " player_selected: " << settings.player_selected << "\n\n";
// INPUT
file << "# INPUT DEVICES (device_type: "
<< static_cast<int>(INPUT_USE_KEYBOARD) << "=KEYBOARD, "
<< static_cast<int>(INPUT_USE_GAMECONTROLLER) << "=GAMECONTROLLER)\n";
file << "input:\n";
for (size_t i = 0; i < inputs.size(); ++i) {
file << " - slot: " << i << "\n";
file << " device_type: " << static_cast<int>(inputs[i].deviceType) << "\n";
}
file.close();
return true;
}
// ========================================================================
// Presets de shaders (postfx.yaml / crtpi.yaml)
//
// Els defaults viuen en una única font (defaultPostFXPresets / defaultCrtPiPresets).
// Generem el YAML a partir d'ells el primer cop i els usem també com a
// fallback si el YAML és absent o corrupte. Si algú toca els valors, ho fa
// en un sol lloc.
// ========================================================================
namespace {
void parseFloatField(const fkyaml::node &node, const std::string &key, float &target) {
if (node.contains(key)) {
try {
target = node[key].get_value<float>();
} catch (...) {}
}
}
auto defaultPostFXPresets() -> const std::vector<PostFXPreset> & {
static const std::vector<PostFXPreset> DEFAULTS = {
{"CRT", 0.6F, 0.7F, 0.15F, 0.6F, 0.8F},
{"NTSC", 0.4F, 0.5F, 0.2F, 0.4F, 0.5F, 0.0F, 0.6F},
{"CURVED", 0.5F, 0.6F, 0.1F, 0.5F, 0.7F, 0.8F},
{"SCANLINES", 0.0F, 0.8F},
{"SUBTLE", 0.3F, 0.4F, 0.05F, 0.0F, 0.3F},
{"CRT LIVE", 0.5F, 0.6F, 0.3F, 0.3F, 0.4F, 0.3F, 0.4F, 0.8F},
};
return DEFAULTS;
}
auto defaultCrtPiPresets() -> const std::vector<CrtPiPreset> & {
static const std::vector<CrtPiPreset> DEFAULTS = {
{"Default", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, false, false},
{"Curved", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, true, false},
{"Sharp", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, false, true, false, true},
{"Minimal", 8.0F, 0.05F, 2.0F, 2.4F, 2.2F, 1.00F, 0.0F, 0.0F, 0, true, false, false, false, false},
};
return DEFAULTS;
}
void writePostFXDefaults(std::ostream &out) {
out << "# Coffee Crisis - PostFX Shader Presets\n\n";
out << "presets:\n";
for (const auto &p : defaultPostFXPresets()) {
out << " - name: \"" << p.name << "\"\n";
out << " vignette: " << p.vignette << "\n";
out << " scanlines: " << p.scanlines << "\n";
out << " chroma: " << p.chroma << "\n";
out << " mask: " << p.mask << "\n";
out << " gamma: " << p.gamma << "\n";
out << " curvature: " << p.curvature << "\n";
out << " bleeding: " << p.bleeding << "\n";
out << " flicker: " << p.flicker << "\n";
}
}
void writeCrtPiDefaults(std::ostream &out) {
out << "# Coffee Crisis - CrtPi Shader Presets\n\n";
out << "presets:\n";
for (const auto &p : defaultCrtPiPresets()) {
out << " - name: \"" << p.name << "\"\n";
out << " scanline_weight: " << p.scanline_weight << "\n";
out << " scanline_gap_brightness: " << p.scanline_gap_brightness << "\n";
out << " bloom_factor: " << p.bloom_factor << "\n";
out << " input_gamma: " << p.input_gamma << "\n";
out << " output_gamma: " << p.output_gamma << "\n";
out << " mask_brightness: " << p.mask_brightness << "\n";
out << " curvature_x: " << p.curvature_x << "\n";
out << " curvature_y: " << p.curvature_y << "\n";
out << " mask_type: " << p.mask_type << "\n";
out << " enable_scanlines: " << boolToString(p.enable_scanlines) << "\n";
out << " enable_multisample: " << boolToString(p.enable_multisample) << "\n";
out << " enable_gamma: " << boolToString(p.enable_gamma) << "\n";
out << " enable_curvature: " << boolToString(p.enable_curvature) << "\n";
out << " enable_sharper: " << boolToString(p.enable_sharper) << "\n";
}
}
} // namespace
void setPostFXFile(const std::string &path) {
postfx_file_path = path;
}
auto loadPostFXFromFile() -> bool {
postfx_presets.clear();
current_postfx_preset = 0;
std::ifstream file(postfx_file_path);
if (!file.is_open()) {
// No existeix: escriu el YAML a partir dels defaults i copia'ls a memòria.
std::ofstream out(postfx_file_path);
if (out.is_open()) {
writePostFXDefaults(out);
out.close();
}
postfx_presets = defaultPostFXPresets();
return true;
}
const std::string CONTENT((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
file.close();
try {
auto yaml = fkyaml::node::deserialize(CONTENT);
if (yaml.contains("presets")) {
for (const auto &p : yaml["presets"]) {
PostFXPreset preset;
if (p.contains("name")) {
try {
preset.name = p["name"].get_value<std::string>();
} catch (...) {}
}
parseFloatField(p, "vignette", preset.vignette);
parseFloatField(p, "scanlines", preset.scanlines);
parseFloatField(p, "chroma", preset.chroma);
parseFloatField(p, "mask", preset.mask);
parseFloatField(p, "gamma", preset.gamma);
parseFloatField(p, "curvature", preset.curvature);
parseFloatField(p, "bleeding", preset.bleeding);
parseFloatField(p, "flicker", preset.flicker);
postfx_presets.push_back(preset);
}
}
std::cout << "PostFX loaded: " << postfx_presets.size() << " preset(s)\n";
} catch (const fkyaml::exception &e) {
std::cout << "Error parsing PostFX YAML: " << e.what() << ". Using defaults.\n";
postfx_presets = defaultPostFXPresets();
return false;
}
if (postfx_presets.empty()) {
postfx_presets = defaultPostFXPresets();
}
return true;
}
void setCrtPiFile(const std::string &path) {
crtpi_file_path = path;
}
auto loadCrtPiFromFile() -> bool {
crtpi_presets.clear();
current_crtpi_preset = 0;
std::ifstream file(crtpi_file_path);
if (!file.is_open()) {
std::ofstream out(crtpi_file_path);
if (out.is_open()) {
writeCrtPiDefaults(out);
out.close();
}
crtpi_presets = defaultCrtPiPresets();
return true;
}
const std::string CONTENT((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
file.close();
try {
auto yaml = fkyaml::node::deserialize(CONTENT);
if (yaml.contains("presets")) {
for (const auto &p : yaml["presets"]) {
CrtPiPreset preset;
if (p.contains("name")) {
try {
preset.name = p["name"].get_value<std::string>();
} catch (...) {}
}
parseFloatField(p, "scanline_weight", preset.scanline_weight);
parseFloatField(p, "scanline_gap_brightness", preset.scanline_gap_brightness);
parseFloatField(p, "bloom_factor", preset.bloom_factor);
parseFloatField(p, "input_gamma", preset.input_gamma);
parseFloatField(p, "output_gamma", preset.output_gamma);
parseFloatField(p, "mask_brightness", preset.mask_brightness);
parseFloatField(p, "curvature_x", preset.curvature_x);
parseFloatField(p, "curvature_y", preset.curvature_y);
if (p.contains("mask_type")) {
try {
preset.mask_type = p["mask_type"].get_value<int>();
} catch (...) {}
}
if (p.contains("enable_scanlines")) {
try {
preset.enable_scanlines = p["enable_scanlines"].get_value<bool>();
} catch (...) {}
}
if (p.contains("enable_multisample")) {
try {
preset.enable_multisample = p["enable_multisample"].get_value<bool>();
} catch (...) {}
}
if (p.contains("enable_gamma")) {
try {
preset.enable_gamma = p["enable_gamma"].get_value<bool>();
} catch (...) {}
}
if (p.contains("enable_curvature")) {
try {
preset.enable_curvature = p["enable_curvature"].get_value<bool>();
} catch (...) {}
}
if (p.contains("enable_sharper")) {
try {
preset.enable_sharper = p["enable_sharper"].get_value<bool>();
} catch (...) {}
}
crtpi_presets.push_back(preset);
}
}
std::cout << "CrtPi loaded: " << crtpi_presets.size() << " preset(s)\n";
} catch (const fkyaml::exception &e) {
std::cout << "Error parsing CrtPi YAML: " << e.what() << ". Using defaults.\n";
crtpi_presets = defaultCrtPiPresets();
return false;
}
if (crtpi_presets.empty()) {
crtpi_presets = defaultCrtPiPresets();
}
return true;
}
} // namespace Options

153
source/game/options.hpp Normal file
View File

@@ -0,0 +1,153 @@
#pragma once
#include <SDL3/SDL.h>
#include <string>
#include <vector>
#include "core/rendering/shader_backend.hpp" // for Rendering::ShaderType
#include "game/defaults.hpp"
#include "utils/utils.h" // for input_t, palette_e
// =============================================================================
// Opciones del programa, alineades amb coffee_crisis_arcade_edition.
// L'estat viu en globals dins el namespace Options:: (window, video, audio,
// loading, settings, inputs). La persistència usa fkyaml i es guarda a
// config.yaml dins la carpeta de configuració del sistema.
// =============================================================================
namespace Options {
struct Window {
std::string caption = Defaults::Window::CAPTION;
int zoom = Defaults::Window::ZOOM;
int max_zoom = Defaults::Window::MAX_ZOOM;
};
struct GPU {
bool acceleration = Defaults::Video::GPU_ACCELERATION;
std::string preferred_driver = Defaults::Video::GPU_PREFERRED_DRIVER;
};
struct Supersampling {
bool enabled = Defaults::Video::SUPERSAMPLING;
bool linear_upscale = Defaults::Video::LINEAR_UPSCALE;
int downscale_algo = Defaults::Video::DOWNSCALE_ALGO;
};
struct ShaderConfig {
bool enabled = Defaults::Video::SHADER_ENABLED;
Rendering::ShaderType current_shader = Rendering::ShaderType::POSTFX;
std::string current_postfx_preset_name = "CRT";
std::string current_crtpi_preset_name = "Default";
int current_postfx_preset = 0;
int current_crtpi_preset = 0;
};
struct Video {
SDL_ScaleMode scale_mode = Defaults::Video::SCALE_MODE;
bool fullscreen = Defaults::Video::FULLSCREEN;
bool vsync = Defaults::Video::VSYNC;
bool integer_scale = Defaults::Video::INTEGER_SCALE;
GPU gpu;
Supersampling supersampling;
ShaderConfig shader;
};
struct Music {
bool enabled = Defaults::Music::ENABLED;
int volume = Defaults::Music::VOLUME;
};
struct Sound {
bool enabled = Defaults::Sound::ENABLED;
int volume = Defaults::Sound::VOLUME;
};
struct Audio {
bool enabled = Defaults::Audio::ENABLED;
int volume = Defaults::Audio::VOLUME;
Music music;
Sound sound;
};
struct Loading {
bool show = Defaults::Loading::SHOW;
bool show_resource_name = Defaults::Loading::SHOW_RESOURCE_NAME;
bool wait_for_input = Defaults::Loading::WAIT_FOR_INPUT;
};
struct Settings {
static constexpr int CURRENT_CONFIG_VERSION = 1;
int config_version = CURRENT_CONFIG_VERSION;
int difficulty = Defaults::Settings::DIFFICULTY;
int language = Defaults::Settings::LANGUAGE;
palette_e palette = Defaults::Settings::PALETTE;
bool console = false;
int player_selected = 0;
std::string config_file;
};
// Preset PostFX
struct PostFXPreset {
std::string name;
float vignette{0.0F};
float scanlines{0.0F};
float chroma{0.0F};
float mask{0.0F};
float gamma{0.0F};
float curvature{0.0F};
float bleeding{0.0F};
float flicker{0.0F};
};
// Preset CrtPi
struct CrtPiPreset {
std::string name;
float scanline_weight{6.0F};
float scanline_gap_brightness{0.12F};
float bloom_factor{3.5F};
float input_gamma{2.4F};
float output_gamma{2.2F};
float mask_brightness{0.80F};
float curvature_x{0.05F};
float curvature_y{0.10F};
int mask_type{2};
bool enable_scanlines{true};
bool enable_multisample{true};
bool enable_gamma{true};
bool enable_curvature{false};
bool enable_sharper{false};
};
// --- Variables globales ---
extern Window window;
extern Video video;
extern Audio audio;
extern Loading loading;
extern Settings settings;
extern std::vector<input_t> inputs; // [0]=KEYBOARD, [1]=GAMECONTROLLER per defecte
// Presets de shaders (carregats de postfx.yaml / crtpi.yaml al config folder)
extern std::vector<PostFXPreset> postfx_presets;
extern std::string postfx_file_path;
extern int current_postfx_preset; // Índex dins `postfx_presets`
extern std::vector<CrtPiPreset> crtpi_presets;
extern std::string crtpi_file_path;
extern int current_crtpi_preset; // Índex dins `crtpi_presets`
// --- Funciones ---
void init(); // Reinicia a defaults i omple `inputs`
void setConfigFile(const std::string &file_path); // Ruta del config.yaml
auto loadFromFile() -> bool; // Carrega el YAML; si no existeix, crea'l amb defaults
auto saveToFile() -> bool; // Guarda el YAML
// Presets de shaders. Si el fitxer no existeix, l'escriu amb els defaults
// i deixa els presets carregats en memòria.
void setPostFXFile(const std::string &path);
auto loadPostFXFromFile() -> bool;
void setCrtPiFile(const std::string &path);
auto loadCrtPiFromFile() -> bool;
} // namespace Options

View File

@@ -1,4 +1,4 @@
#include "instructions.h" #include "game/scenes/instructions.h"
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
@@ -6,26 +6,22 @@
#include <iostream> // for char_traits, basic_ostream, operator<< #include <iostream> // for char_traits, basic_ostream, operator<<
#include <string> // for basic_string #include <string> // for basic_string
#include "asset.h" // for Asset #include "core/audio/jail_audio.hpp" // for JA_StopMusic
#include "const.h" // for shdwTxtColor, GAMECANVAS_CENTER_X, GAME... #include "core/input/global_inputs.hpp" // for GlobalInputs::handle
#include "input.h" // for Input, REPEAT_FALSE, inputs_e #include "core/input/input.h" // for Input, REPEAT_FALSE, inputs_e
#include "jail_audio.hpp" // for JA_StopMusic #include "core/locale/lang.h" // for Lang
#include "lang.h" // for Lang #include "core/rendering/screen.h" // for Screen
#include "resource.h" #include "core/rendering/sprite.h" // for Sprite
#include "screen.h" // for Screen #include "core/rendering/text.h" // for Text, TXT_CENTER, TXT_COLOR, TXT_SHADOW
#include "sprite.h" // for Sprite #include "core/rendering/texture.h" // for Texture
#include "text.h" // for Text, TXT_CENTER, TXT_COLOR, TXT_SHADOW #include "core/resources/resource.h"
#include "texture.h" // for Texture #include "game/defaults.hpp" // for shdwTxtColor, GAMECANVAS_CENTER_X, GAME...
#include "utils.h" // for color_t, section_t #include "utils/utils.h" // for color_t, section_t
// Constructor // Constructor
Instructions::Instructions(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input, Lang *lang, section_t *section) { Instructions::Instructions(SDL_Renderer *renderer, section_t *section) {
// Copia los punteros // Copia los punteros
this->renderer = renderer; this->renderer = renderer;
this->screen = screen;
this->asset = asset;
this->input = input;
this->lang = lang;
this->section = section; this->section = section;
// Texturas (handles compartidos de Resource) // Texturas (handles compartidos de Resource)
@@ -121,21 +117,21 @@ void Instructions::render() {
SDL_RenderClear(renderer); SDL_RenderClear(renderer);
// Escribe el texto // Escribe el texto
text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 8, lang->getText(11), 1, orangeColor, 1, shdwTxtColor); text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 8, Lang::get()->getText(11), 1, orangeColor, 1, shdwTxtColor);
text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 24, lang->getText(12), 1, noColor, 1, shdwTxtColor); text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 24, Lang::get()->getText(12), 1, noColor, 1, shdwTxtColor);
text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 34, lang->getText(13), 1, noColor, 1, shdwTxtColor); text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 34, Lang::get()->getText(13), 1, noColor, 1, shdwTxtColor);
text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 48, lang->getText(14), 1, noColor, 1, shdwTxtColor); text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 48, Lang::get()->getText(14), 1, noColor, 1, shdwTxtColor);
text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 58, lang->getText(15), 1, noColor, 1, shdwTxtColor); text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 58, Lang::get()->getText(15), 1, noColor, 1, shdwTxtColor);
text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 75, lang->getText(16), 1, orangeColor, 1, shdwTxtColor); text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, 75, Lang::get()->getText(16), 1, orangeColor, 1, shdwTxtColor);
text->writeShadowed(84, 92, lang->getText(17), shdwTxtColor); text->writeShadowed(84, 92, Lang::get()->getText(17), shdwTxtColor);
text->writeShadowed(84, 108, lang->getText(18), shdwTxtColor); text->writeShadowed(84, 108, Lang::get()->getText(18), shdwTxtColor);
text->writeShadowed(84, 124, lang->getText(19), shdwTxtColor); text->writeShadowed(84, 124, Lang::get()->getText(19), shdwTxtColor);
text->writeShadowed(84, 140, lang->getText(20), shdwTxtColor); text->writeShadowed(84, 140, Lang::get()->getText(20), shdwTxtColor);
text->writeShadowed(84, 156, lang->getText(21), shdwTxtColor); text->writeShadowed(84, 156, Lang::get()->getText(21), shdwTxtColor);
if ((mode == m_manual) && (counter % 50 > 14)) { if ((mode == m_manual) && (counter % 50 > 14)) {
text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, GAMECANVAS_HEIGHT - 12, lang->getText(22), 1, orangeColor, 1, shdwTxtColor); text->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, GAMECANVAS_CENTER_X, GAMECANVAS_HEIGHT - 12, Lang::get()->getText(22), 1, orangeColor, 1, shdwTxtColor);
} }
// Disquito // Disquito
@@ -177,10 +173,10 @@ void Instructions::render() {
SDL_SetRenderTarget(renderer, nullptr); SDL_SetRenderTarget(renderer, nullptr);
// Prepara para empezar a dibujar en la textura de juego // Prepara para empezar a dibujar en la textura de juego
screen->start(); Screen::get()->start();
// Limpia la pantalla // Limpia la pantalla
screen->clean(bgColor); Screen::get()->clean(bgColor);
// Establece la ventana del backbuffer // Establece la ventana del backbuffer
if (mode == m_auto) { if (mode == m_auto) {
@@ -194,7 +190,7 @@ void Instructions::render() {
SDL_RenderTexture(renderer, backbuffer, nullptr, &fWindow); SDL_RenderTexture(renderer, backbuffer, nullptr, &fWindow);
// Vuelca el contenido del renderizador en pantalla // Vuelca el contenido del renderizador en pantalla
screen->blit(); Screen::get()->blit();
} }
// Comprueba los eventos // Comprueba los eventos
@@ -215,24 +211,15 @@ void Instructions::checkEvents() {
// Comprueba las entradas // Comprueba las entradas
void Instructions::checkInput() { void Instructions::checkInput() {
#ifndef __EMSCRIPTEN__ #ifndef __EMSCRIPTEN__
if (input->checkInput(input_exit, REPEAT_FALSE)) { if (Input::get()->checkInput(input_exit, REPEAT_FALSE)) {
quitRequested = true; quitRequested = true;
finished = true; finished = true;
} else return;
}
#endif #endif
if (input->checkInput(input_window_fullscreen, REPEAT_FALSE)) { if (GlobalInputs::handle()) { return; }
screen->toggleVideoMode();
}
else if (input->checkInput(input_window_dec_size, REPEAT_FALSE)) { if (Input::get()->checkInput(input_pause, REPEAT_FALSE) || Input::get()->checkInput(input_accept, REPEAT_FALSE) || Input::get()->checkInput(input_fire_left, REPEAT_FALSE) || Input::get()->checkInput(input_fire_center, REPEAT_FALSE) || Input::get()->checkInput(input_fire_right, REPEAT_FALSE)) {
screen->decWindowZoom();
}
else if (input->checkInput(input_window_inc_size, REPEAT_FALSE)) {
screen->incWindowZoom();
}
else if (input->checkInput(input_pause, REPEAT_FALSE) || input->checkInput(input_accept, REPEAT_FALSE) || input->checkInput(input_fire_left, REPEAT_FALSE) || input->checkInput(input_fire_center, REPEAT_FALSE) || input->checkInput(input_fire_right, REPEAT_FALSE)) {
if (mode == m_auto) { if (mode == m_auto) {
finished = true; finished = true;
} else { } else {

View File

@@ -3,10 +3,6 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <vector> // for vector #include <vector> // for vector
class Asset;
class Input;
class Lang;
class Screen;
class Sprite; class Sprite;
class Text; class Text;
class Texture; class Texture;
@@ -22,14 +18,10 @@ class Instructions {
private: private:
// Objetos y punteros // Objetos y punteros
SDL_Renderer *renderer; // El renderizador de la ventana SDL_Renderer *renderer; // El renderizador de la ventana
Screen *screen; // Objeto encargado de dibujar en pantalla
std::vector<Texture *> itemTextures; // Vector con las texturas de los items std::vector<Texture *> itemTextures; // Vector con las texturas de los items
SDL_Event *eventHandler; // Manejador de eventos SDL_Event *eventHandler; // Manejador de eventos
SDL_Texture *backbuffer; // Textura para usar como backbuffer SDL_Texture *backbuffer; // Textura para usar como backbuffer
Sprite *sprite; // Sprite con la textura de las instrucciones Sprite *sprite; // Sprite con la textura de las instrucciones
Asset *asset; // Objeto que gestiona todos los ficheros de recursos
Input *input; // Objeto pata gestionar la entrada
Lang *lang; // Objeto para gestionar los textos en diferentes idiomas
Text *text; // Objeto para escribir texto Text *text; // Objeto para escribir texto
section_t *section; // Estado del bucle principal para saber si continua o se sale section_t *section; // Estado del bucle principal para saber si continua o se sale
@@ -48,7 +40,7 @@ class Instructions {
public: public:
// Constructor // Constructor
Instructions(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input, Lang *lang, section_t *section); Instructions(SDL_Renderer *renderer, section_t *section);
// Destructor // Destructor
~Instructions(); ~Instructions();

View File

@@ -1,30 +1,26 @@
#include "intro.h" #include "game/scenes/intro.h"
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <string> // for basic_string #include <string> // for basic_string
#include "asset.h" // for Asset #include "core/audio/jail_audio.hpp" // for JA_StopMusic, JA_DeleteMusic, JA_LoadMusic
#include "const.h" // for GAMECANVAS_CENTER_X, GAMECANVAS_FIRST_QU... #include "core/input/global_inputs.hpp" // for GlobalInputs::handle
#include "input.h" // for Input, REPEAT_FALSE, inputs_e #include "core/input/input.h" // for Input, REPEAT_FALSE, inputs_e
#include "jail_audio.hpp" // for JA_StopMusic, JA_DeleteMusic, JA_LoadMusic #include "core/locale/lang.h" // for Lang
#include "lang.h" // for Lang #include "core/rendering/screen.h" // for Screen
#include "resource.h" #include "core/rendering/smartsprite.h" // for SmartSprite
#include "screen.h" // for Screen #include "core/rendering/text.h" // for Text
#include "smartsprite.h" // for SmartSprite #include "core/rendering/texture.h" // for Texture
#include "text.h" // for Text #include "core/rendering/writer.h" // for Writer
#include "texture.h" // for Texture #include "core/resources/resource.h"
#include "utils.h" // for section_t, color_t #include "game/defaults.hpp" // for GAMECANVAS_CENTER_X, GAMECANVAS_FIRST_QU...
#include "writer.h" // for Writer #include "utils/utils.h" // for section_t, color_t
// Constructor // Constructor
Intro::Intro(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input, Lang *lang, section_t *section) { Intro::Intro(SDL_Renderer *renderer, section_t *section) {
// Copia los punteros // Copia los punteros
this->renderer = renderer; this->renderer = renderer;
this->screen = screen;
this->lang = lang;
this->asset = asset;
this->input = input;
this->section = section; this->section = section;
// Reserva memoria para los objetos // Reserva memoria para los objetos
@@ -114,43 +110,43 @@ Intro::Intro(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input,
} }
// Un dia qualsevol de l'any 2000 // Un dia qualsevol de l'any 2000
texts[0]->setCaption(lang->getText(27)); texts[0]->setCaption(Lang::get()->getText(27));
texts[0]->setSpeed(8); texts[0]->setSpeed(8);
// Tot esta tranquil a la UPV // Tot esta tranquil a la UPV
texts[1]->setCaption(lang->getText(28)); texts[1]->setCaption(Lang::get()->getText(28));
texts[1]->setSpeed(8); texts[1]->setSpeed(8);
// Fins que un desaprensiu... // Fins que un desaprensiu...
texts[2]->setCaption(lang->getText(29)); texts[2]->setCaption(Lang::get()->getText(29));
texts[2]->setSpeed(12); texts[2]->setSpeed(12);
// HEY! ME ANE A FERME UN CORTAET... // HEY! ME ANE A FERME UN CORTAET...
texts[3]->setCaption(lang->getText(30)); texts[3]->setCaption(Lang::get()->getText(30));
texts[3]->setSpeed(8); texts[3]->setSpeed(8);
// UAAAAAAAAAAAAA!!! // UAAAAAAAAAAAAA!!!
texts[4]->setCaption(lang->getText(31)); texts[4]->setCaption(Lang::get()->getText(31));
texts[4]->setSpeed(1); texts[4]->setSpeed(1);
// Espera un moment... // Espera un moment...
texts[5]->setCaption(lang->getText(32)); texts[5]->setCaption(Lang::get()->getText(32));
texts[5]->setSpeed(16); texts[5]->setSpeed(16);
// Si resulta que no tinc solt! // Si resulta que no tinc solt!
texts[6]->setCaption(lang->getText(33)); texts[6]->setCaption(Lang::get()->getText(33));
texts[6]->setSpeed(2); texts[6]->setSpeed(2);
// MERDA DE MAQUINA! // MERDA DE MAQUINA!
texts[7]->setCaption(lang->getText(34)); texts[7]->setCaption(Lang::get()->getText(34));
texts[7]->setSpeed(3); texts[7]->setSpeed(3);
// Blop... blop... blop... // Blop... blop... blop...
texts[8]->setCaption(lang->getText(35)); texts[8]->setCaption(Lang::get()->getText(35));
texts[8]->setSpeed(16); texts[8]->setSpeed(16);
for (auto text : texts) { for (auto *t : texts) {
text->center(GAMECANVAS_CENTER_X); t->center(GAMECANVAS_CENTER_X);
} }
JA_PlayMusic(music, 0); JA_PlayMusic(music, 0);
@@ -170,45 +166,17 @@ Intro::~Intro() {
} }
} }
// Carga los recursos (ya no carga nada, se mantiene por si hay callers)
bool Intro::loadMedia() {
return true;
}
// Comprueba los eventos
void Intro::checkEvents() {
#ifndef __EMSCRIPTEN__
// Comprueba los eventos que hay en la cola
while (SDL_PollEvent(eventHandler) != 0) {
// Evento de salida de la aplicación
if (eventHandler->type == SDL_EVENT_QUIT) {
section->name = SECTION_PROG_QUIT;
break;
}
}
#endif
}
// Comprueba las entradas // Comprueba las entradas
void Intro::checkInput() { void Intro::checkInput() {
#ifndef __EMSCRIPTEN__ #ifndef __EMSCRIPTEN__
if (input->checkInput(input_exit, REPEAT_FALSE)) { if (Input::get()->checkInput(input_exit, REPEAT_FALSE)) {
section->name = SECTION_PROG_QUIT; section->name = SECTION_PROG_QUIT;
} else return;
}
#endif #endif
if (input->checkInput(input_window_fullscreen, REPEAT_FALSE)) { if (GlobalInputs::handle()) { return; }
screen->toggleVideoMode();
}
else if (input->checkInput(input_window_dec_size, REPEAT_FALSE)) { if (Input::get()->checkInput(input_pause, REPEAT_FALSE) || Input::get()->checkInput(input_accept, REPEAT_FALSE) || Input::get()->checkInput(input_fire_left, REPEAT_FALSE) || Input::get()->checkInput(input_fire_center, REPEAT_FALSE) || Input::get()->checkInput(input_fire_right, REPEAT_FALSE)) {
screen->decWindowZoom();
}
else if (input->checkInput(input_window_inc_size, REPEAT_FALSE)) {
screen->incWindowZoom();
}
else if (input->checkInput(input_pause, REPEAT_FALSE) || input->checkInput(input_accept, REPEAT_FALSE) || input->checkInput(input_fire_left, REPEAT_FALSE) || input->checkInput(input_fire_center, REPEAT_FALSE) || input->checkInput(input_fire_right, REPEAT_FALSE)) {
JA_StopMusic(); JA_StopMusic();
section->name = SECTION_PROG_TITLE; section->name = SECTION_PROG_TITLE;
section->subsection = SUBSECTION_TITLE_1; section->subsection = SUBSECTION_TITLE_1;
@@ -365,8 +333,8 @@ void Intro::update() {
bitmap->update(); bitmap->update();
} }
for (auto text : texts) { for (auto *t : texts) {
text->update(); t->update();
} }
// Actualiza las escenas de la intro // Actualiza las escenas de la intro
@@ -377,22 +345,22 @@ void Intro::update() {
// Dibuja el objeto en pantalla // Dibuja el objeto en pantalla
void Intro::render() { void Intro::render() {
// Prepara para empezar a dibujar en la textura de juego // Prepara para empezar a dibujar en la textura de juego
screen->start(); Screen::get()->start();
// Limpia la pantalla // Limpia la pantalla
screen->clean(bgColor); Screen::get()->clean(bgColor);
// Dibuja los objetos // Dibuja los objetos
for (auto bitmap : bitmaps) { for (auto bitmap : bitmaps) {
bitmap->render(); bitmap->render();
} }
for (auto text : texts) { for (auto *t : texts) {
text->render(); t->render();
} }
// Vuelca el contenido del renderizador en pantalla // Vuelca el contenido del renderizador en pantalla
screen->blit(); Screen::get()->blit();
} }
// Bucle principal // Bucle principal
@@ -411,6 +379,6 @@ void Intro::iterate() {
} }
// Procesa un evento individual // Procesa un evento individual
void Intro::handleEvent(SDL_Event *event) { void Intro::handleEvent(const SDL_Event *event) {
// SDL_EVENT_QUIT ya lo maneja Director // SDL_EVENT_QUIT ya lo maneja Director
} }

View File

@@ -3,10 +3,6 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <vector> // for vector #include <vector> // for vector
class Asset;
class Input;
class Lang;
class Screen;
class SmartSprite; class SmartSprite;
class Text; class Text;
class Texture; class Texture;
@@ -19,12 +15,8 @@ class Intro {
private: private:
// Objetos y punteros // Objetos y punteros
SDL_Renderer *renderer; // El renderizador de la ventana SDL_Renderer *renderer; // El renderizador de la ventana
Screen *screen; // Objeto encargado de dibujar en pantalla
Texture *texture; // Textura con los graficos Texture *texture; // Textura con los graficos
SDL_Event *eventHandler; // Manejador de eventos SDL_Event *eventHandler; // Manejador de eventos
Asset *asset; // Objeto que gestiona todos los ficheros de recursos
Lang *lang; // Objeto para gestionar los textos en diferentes idiomas
Input *input; // Objeto pata gestionar la entrada
std::vector<SmartSprite *> bitmaps; // Vector con los sprites inteligentes para los dibujos de la intro std::vector<SmartSprite *> bitmaps; // Vector con los sprites inteligentes para los dibujos de la intro
std::vector<Writer *> texts; // Textos de la intro std::vector<Writer *> texts; // Textos de la intro
Text *text; // Textos de la intro Text *text; // Textos de la intro
@@ -42,12 +34,6 @@ class Intro {
// Dibuja el objeto en pantalla // Dibuja el objeto en pantalla
void render(); void render();
// Carga los recursos
bool loadMedia();
// Comprueba los eventos
void checkEvents();
// Comprueba las entradas // Comprueba las entradas
void checkInput(); void checkInput();
@@ -56,7 +42,7 @@ class Intro {
public: public:
// Constructor // Constructor
Intro(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input, Lang *lang, section_t *section); Intro(SDL_Renderer *renderer, section_t *section);
// Destructor // Destructor
~Intro(); ~Intro();
@@ -68,5 +54,5 @@ class Intro {
void iterate(); void iterate();
// Procesa un evento // Procesa un evento
void handleEvent(SDL_Event *event); void handleEvent(const SDL_Event *event);
}; };

View File

@@ -1,31 +1,29 @@
#include "logo.h" #include "game/scenes/logo.h"
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <algorithm> // for min #include <algorithm> // for min
#include <string> // for basic_string #include <string> // for basic_string
#include "asset.h" // for Asset #include "core/audio/jail_audio.hpp" // for JA_StopMusic
#include "const.h" // for bgColor, SECTION_PROG_LOGO, SECTION_PROG... #include "core/input/global_inputs.hpp" // for GlobalInputs::handle
#include "input.h" // for Input, REPEAT_FALSE, inputs_e #include "core/input/input.h" // for Input, REPEAT_FALSE, inputs_e
#include "jail_audio.hpp" // for JA_StopMusic #include "core/rendering/screen.h" // for Screen
#include "resource.h" #include "core/rendering/sprite.h" // for Sprite
#include "screen.h" // for Screen #include "core/rendering/texture.h" // for Texture
#include "sprite.h" // for Sprite #include "core/resources/asset.h" // for Asset
#include "texture.h" // for Texture #include "core/resources/resource.h"
#include "utils.h" // for section_t, color_t #include "game/defaults.hpp" // for bgColor, SECTION_PROG_LOGO, SECTION_PROG...
#include "utils/utils.h" // for section_t, color_t
// Valores de inicialización y fin // Valores de inicialización y fin
constexpr int INIT_FADE = 100; constexpr int INIT_FADE = 100;
constexpr int END_LOGO = 200; constexpr int END_LOGO = 200;
// Constructor // Constructor
Logo::Logo(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input, section_t *section) { Logo::Logo(SDL_Renderer *renderer, section_t *section) {
// Copia la dirección de los objetos // Copia la dirección de los objetos
this->renderer = renderer; this->renderer = renderer;
this->screen = screen;
this->asset = asset;
this->input = input;
this->section = section; this->section = section;
// Reserva memoria para los punteros // Reserva memoria para los punteros
@@ -58,40 +56,17 @@ void Logo::checkLogoEnd() {
} }
} }
// Comprueba los eventos
void Logo::checkEvents() {
#ifndef __EMSCRIPTEN__
// Comprueba los eventos que hay en la cola
while (SDL_PollEvent(eventHandler) != 0) {
// Evento de salida de la aplicación
if (eventHandler->type == SDL_EVENT_QUIT) {
section->name = SECTION_PROG_QUIT;
break;
}
}
#endif
}
// Comprueba las entradas // Comprueba las entradas
void Logo::checkInput() { void Logo::checkInput() {
#ifndef __EMSCRIPTEN__ #ifndef __EMSCRIPTEN__
if (input->checkInput(input_exit, REPEAT_FALSE)) { if (Input::get()->checkInput(input_exit, REPEAT_FALSE)) {
section->name = SECTION_PROG_QUIT; section->name = SECTION_PROG_QUIT;
} else return;
}
#endif #endif
if (input->checkInput(input_window_fullscreen, REPEAT_FALSE)) { if (GlobalInputs::handle()) { return; }
screen->toggleVideoMode();
}
else if (input->checkInput(input_window_dec_size, REPEAT_FALSE)) { if (Input::get()->checkInput(input_pause, REPEAT_FALSE) || Input::get()->checkInput(input_accept, REPEAT_FALSE) || Input::get()->checkInput(input_fire_left, REPEAT_FALSE) || Input::get()->checkInput(input_fire_center, REPEAT_FALSE) || Input::get()->checkInput(input_fire_right, REPEAT_FALSE)) {
screen->decWindowZoom();
}
else if (input->checkInput(input_window_inc_size, REPEAT_FALSE)) {
screen->incWindowZoom();
}
else if (input->checkInput(input_pause, REPEAT_FALSE) || input->checkInput(input_accept, REPEAT_FALSE) || input->checkInput(input_fire_left, REPEAT_FALSE) || input->checkInput(input_fire_center, REPEAT_FALSE) || input->checkInput(input_fire_right, REPEAT_FALSE)) {
section->name = SECTION_PROG_TITLE; section->name = SECTION_PROG_TITLE;
section->subsection = SUBSECTION_TITLE_1; section->subsection = SUBSECTION_TITLE_1;
} }
@@ -128,10 +103,10 @@ void Logo::update() {
// Dibuja el objeto en pantalla // Dibuja el objeto en pantalla
void Logo::render() { void Logo::render() {
// Prepara para empezar a dibujar en la textura de juego // Prepara para empezar a dibujar en la textura de juego
screen->start(); Screen::get()->start();
// Limpia la pantalla // Limpia la pantalla
screen->clean({238, 238, 238}); Screen::get()->clean({238, 238, 238});
// Dibuja los objetos // Dibuja los objetos
sprite->render(); sprite->render();
@@ -140,7 +115,7 @@ void Logo::render() {
renderFade(); renderFade();
// Vuelca el contenido del renderizador en pantalla // Vuelca el contenido del renderizador en pantalla
screen->blit(); Screen::get()->blit();
} }
// Bucle para el logo del juego // Bucle para el logo del juego
@@ -159,6 +134,6 @@ void Logo::iterate() {
} }
// Procesa un evento individual // Procesa un evento individual
void Logo::handleEvent(SDL_Event *event) { void Logo::handleEvent(const SDL_Event *event) {
// SDL_EVENT_QUIT ya lo maneja Director // SDL_EVENT_QUIT ya lo maneja Director
} }

View File

@@ -1,9 +1,6 @@
#pragma once #pragma once
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
class Asset;
class Input;
class Screen;
class Sprite; class Sprite;
class Texture; class Texture;
struct section_t; struct section_t;
@@ -13,9 +10,6 @@ class Logo {
private: private:
// Objetos y punteros // Objetos y punteros
SDL_Renderer *renderer; // El renderizador de la ventana SDL_Renderer *renderer; // El renderizador de la ventana
Screen *screen; // Objeto encargado de dibujar en pantalla
Asset *asset; // Objeto que gestiona todos los ficheros de recursos
Input *input; // Objeto pata gestionar la entrada
Texture *texture; // Textura con los graficos Texture *texture; // Textura con los graficos
SDL_Event *eventHandler; // Manejador de eventos SDL_Event *eventHandler; // Manejador de eventos
Sprite *sprite; // Sprite con la textura del logo Sprite *sprite; // Sprite con la textura del logo
@@ -35,9 +29,6 @@ class Logo {
// Comprueba si ha terminado el logo // Comprueba si ha terminado el logo
void checkLogoEnd(); void checkLogoEnd();
// Comprueba los eventos
void checkEvents();
// Comprueba las entradas // Comprueba las entradas
void checkInput(); void checkInput();
@@ -46,7 +37,7 @@ class Logo {
public: public:
// Constructor // Constructor
Logo(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input, section_t *section); Logo(SDL_Renderer *renderer, section_t *section);
// Destructor // Destructor
~Logo(); ~Logo();
@@ -58,5 +49,5 @@ class Logo {
void iterate(); void iterate();
// Procesa un evento // Procesa un evento
void handleEvent(SDL_Event *event); void handleEvent(const SDL_Event *event);
}; };

View File

@@ -1,4 +1,4 @@
#include "title.h" #include "game/scenes/title.h"
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <stdlib.h> // for rand #include <stdlib.h> // for rand
@@ -6,31 +6,28 @@
#include <iostream> // for basic_ostream, operator<<, basic_ostrea... #include <iostream> // for basic_ostream, operator<<, basic_ostrea...
#include <string> // for basic_string, operator+, char_traits #include <string> // for basic_string, operator+, char_traits
#include "animatedsprite.h" // for AnimatedSprite #include "core/audio/jail_audio.hpp" // for JA_StopMusic, JA_GetMusicState, JA_Play...
#include "asset.h" // for Asset #include "core/input/global_inputs.hpp" // for GlobalInputs::handle
#include "const.h" // for GAMECANVAS_CENTER_X, SECTION_PROG_QUIT #include "core/input/input.h" // for Input, INPUT_USE_GAMECONTROLLER, INPUT_...
#include "fade.h" // for Fade #include "core/locale/lang.h" // for Lang, ba_BA, en_UK, es_ES
#include "game.h" // for Game #include "core/rendering/animatedsprite.h" // for AnimatedSprite
#include "input.h" // for Input, INPUT_USE_GAMECONTROLLER, INPUT_... #include "core/rendering/fade.h" // for Fade
#include "jail_audio.hpp" // for JA_StopMusic, JA_GetMusicState, JA_Play... #include "core/rendering/screen.h" // for Screen
#include "lang.h" // for Lang, ba_BA, en_UK, es_ES #include "core/rendering/smartsprite.h" // for SmartSprite
#include "menu.h" // for Menu #include "core/rendering/sprite.h" // for Sprite
#include "resource.h" #include "core/rendering/text.h" // for Text, TXT_CENTER, TXT_SHADOW
#include "screen.h" // for Screen, FILTER_LINEAL, FILTER_NEAREST #include "core/rendering/texture.h" // for Texture
#include "smartsprite.h" // for SmartSprite #include "core/resources/asset.h" // for Asset
#include "sprite.h" // for Sprite #include "core/resources/resource.h"
#include "text.h" // for Text, TXT_CENTER, TXT_SHADOW #include "game/defaults.hpp" // for GAMECANVAS_CENTER_X, SECTION_PROG_QUIT
#include "texture.h" // for Texture #include "game/game.h" // for Game
#include "game/options.hpp" // for Options
#include "game/ui/menu.h" // for Menu
// Constructor // Constructor
Title::Title(SDL_Renderer *renderer, Screen *screen, Input *input, Asset *asset, options_t *options, Lang *lang, section_t *section) { Title::Title(SDL_Renderer *renderer, section_t *section) {
// Copia las direcciones de los punteros // Copia las direcciones de los punteros
this->renderer = renderer; this->renderer = renderer;
this->screen = screen;
this->input = input;
this->asset = asset;
this->options = options;
this->lang = lang;
this->section = section; this->section = section;
// Reserva memoria para los punteros // Reserva memoria para los punteros
@@ -113,18 +110,18 @@ void Title::init() {
demoThenInstructions = false; demoThenInstructions = false;
// Pone valores por defecto a las opciones de control // Pone valores por defecto a las opciones de control
options->input.clear(); Options::inputs.clear();
input_t i; input_t inp;
i.id = 0; inp.id = 0;
i.name = "KEYBOARD"; inp.name = "KEYBOARD";
i.deviceType = INPUT_USE_KEYBOARD; inp.deviceType = INPUT_USE_KEYBOARD;
options->input.push_back(i); Options::inputs.push_back(inp);
i.id = 0; inp.id = 0;
i.name = "GAME CONTROLLER"; inp.name = "GAME CONTROLLER";
i.deviceType = INPUT_USE_GAMECONTROLLER; inp.deviceType = INPUT_USE_GAMECONTROLLER;
options->input.push_back(i); Options::inputs.push_back(inp);
// Comprueba si hay mandos conectados // Comprueba si hay mandos conectados
checkInputDevices(); checkInputDevices();
@@ -135,10 +132,10 @@ void Title::init() {
deviceIndex.push_back(0); // El primer mando encontrado. Si no ha encontrado ninguno es el teclado deviceIndex.push_back(0); // El primer mando encontrado. Si no ha encontrado ninguno es el teclado
// Si ha encontrado un mando se lo asigna al segundo jugador // Si ha encontrado un mando se lo asigna al segundo jugador
if (input->gameControllerFound()) { if (Input::get()->gameControllerFound()) {
options->input[1].id = availableInputDevices[deviceIndex[1]].id; Options::inputs[1].id = availableInputDevices[deviceIndex[1]].id;
options->input[1].name = availableInputDevices[deviceIndex[1]].name; Options::inputs[1].name = availableInputDevices[deviceIndex[1]].name;
options->input[1].deviceType = availableInputDevices[deviceIndex[1]].deviceType; Options::inputs[1].deviceType = availableInputDevices[deviceIndex[1]].deviceType;
} else { // Si no ha encontrado un mando, deshabilita la opción de jugar a 2 jugadores } else { // Si no ha encontrado un mando, deshabilita la opción de jugar a 2 jugadores
menu.title->setSelectable(1, false); menu.title->setSelectable(1, false);
menu.title->setGreyed(1, true); menu.title->setGreyed(1, true);
@@ -236,10 +233,14 @@ void Title::update() {
if (coffeeBitmap->hasFinished() && crisisBitmap->hasFinished()) { if (coffeeBitmap->hasFinished() && crisisBitmap->hasFinished()) {
section->subsection = SUBSECTION_TITLE_2; section->subsection = SUBSECTION_TITLE_2;
// Pantallazo blanco // Pantallazo blanco: pintar sobre el gameCanvas y dejar
// que Screen::blit() presente por la ruta activa (GPU o
// SDL_Renderer). Un `SDL_RenderPresent(renderer)` directe
// crasheja quan el SDL3 GPU ha reclamat la ventana.
Screen::get()->start();
SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF); SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
SDL_RenderClear(renderer); SDL_RenderClear(renderer);
SDL_RenderPresent(renderer); Screen::get()->blit();
// Reproduce el efecto sonoro // Reproduce el efecto sonoro
JA_PlaySound(crashSound); JA_PlaySound(crashSound);
@@ -343,7 +344,10 @@ void Title::update() {
case 2: // OPTIONS case 2: // OPTIONS
menu.active = menu.options; menu.active = menu.options;
optionsPrevious = *options; prevVideo = Options::video;
prevWindow = Options::window;
prevSettings = Options::settings;
prevInputs = Options::inputs;
break; break;
case 3: // QUIT case 3: // QUIT
@@ -365,13 +369,13 @@ void Title::update() {
case 1: // BAL1 case 1: // BAL1
postFade = 0; postFade = 0;
options->playerSelected = 0; Options::settings.player_selected = 0;
fade->activateFade(); fade->activateFade();
break; break;
case 2: // AROUNDER case 2: // AROUNDER
postFade = 0; postFade = 0;
options->playerSelected = 1; Options::settings.player_selected = 1;
fade->activateFade(); fade->activateFade();
break; break;
@@ -389,12 +393,12 @@ void Title::update() {
if (menu.active->getName() == "OPTIONS") { if (menu.active->getName() == "OPTIONS") {
switch (menu.active->getItemSelected()) { switch (menu.active->getItemSelected()) {
case 0: // Difficulty case 0: // Difficulty
if (options->difficulty == DIFFICULTY_EASY) if (Options::settings.difficulty == DIFFICULTY_EASY)
options->difficulty = DIFFICULTY_NORMAL; Options::settings.difficulty = DIFFICULTY_NORMAL;
else if (options->difficulty == DIFFICULTY_NORMAL) else if (Options::settings.difficulty == DIFFICULTY_NORMAL)
options->difficulty = DIFFICULTY_HARD; Options::settings.difficulty = DIFFICULTY_HARD;
else else
options->difficulty = DIFFICULTY_EASY; Options::settings.difficulty = DIFFICULTY_EASY;
updateMenuLabels(); updateMenuLabels();
break; break;
@@ -409,15 +413,15 @@ void Title::update() {
break; break;
case 5: // Language case 5: // Language
options->language++; Options::settings.language++;
if (options->language == 3) if (Options::settings.language == 3)
options->language = 0; Options::settings.language = 0;
updateMenuLabels(); updateMenuLabels();
break; break;
case 6: // Display mode case 6: // Display mode
switchFullScreenModeVar(); switchFullScreenModeVar();
if (options->videoMode != 0) { if (Options::video.fullscreen) {
menu.options->setSelectable(8, false); menu.options->setSelectable(8, false);
menu.options->setGreyed(8, true); menu.options->setGreyed(8, true);
} else { } else {
@@ -428,27 +432,23 @@ void Title::update() {
break; break;
case 8: // Windows size case 8: // Windows size
options->windowSize++; Options::window.zoom++;
if (options->windowSize == 5) if (Options::window.zoom > Options::window.max_zoom)
options->windowSize = 1; Options::window.zoom = 1;
updateMenuLabels(); updateMenuLabels();
break; break;
case 9: // FILTER case 9: // Scale mode
if (options->filter == FILTER_LINEAL) Options::video.scale_mode = (Options::video.scale_mode == SDL_SCALEMODE_NEAREST)
options->filter = FILTER_NEAREST; ? SDL_SCALEMODE_LINEAR
else : SDL_SCALEMODE_NEAREST;
options->filter = FILTER_LINEAL; Texture::setGlobalScaleMode(Options::video.scale_mode);
Texture::setGlobalScaleMode(options->filter == FILTER_NEAREST ? SDL_SCALEMODE_NEAREST : SDL_SCALEMODE_LINEAR);
reLoadTextures(); reLoadTextures();
updateMenuLabels(); updateMenuLabels();
break; break;
case 10: // VSYNC case 10: // VSYNC
if (options->vSync) Options::video.vsync = !Options::video.vsync;
options->vSync = false;
else
options->vSync = true;
updateMenuLabels(); updateMenuLabels();
break; break;
@@ -463,7 +463,10 @@ void Title::update() {
break; break;
case 13: // CANCEL case 13: // CANCEL
options = &optionsPrevious; Options::video = prevVideo;
Options::window = prevWindow;
Options::settings = prevSettings;
Options::inputs = prevInputs;
updateMenuLabels(); updateMenuLabels();
menu.active->reset(); menu.active->reset();
menu.active = menu.title; menu.active = menu.title;
@@ -506,10 +509,10 @@ void Title::render() {
// Sección 1 - Titulo desplazandose // Sección 1 - Titulo desplazandose
case SUBSECTION_TITLE_1: { case SUBSECTION_TITLE_1: {
// Prepara para empezar a dibujar en la textura de juego // Prepara para empezar a dibujar en la textura de juego
screen->start(); Screen::get()->start();
// Limpia la pantalla // Limpia la pantalla
screen->clean(bgColor); Screen::get()->clean(bgColor);
// Dibuja el tileado de fondo // Dibuja el tileado de fondo
{ {
@@ -525,16 +528,16 @@ void Title::render() {
crisisBitmap->render(); crisisBitmap->render();
// Vuelca el contenido del renderizador en pantalla // Vuelca el contenido del renderizador en pantalla
screen->blit(); Screen::get()->blit();
} break; } break;
// Sección 2 - Titulo vibrando // Sección 2 - Titulo vibrando
case SUBSECTION_TITLE_2: { case SUBSECTION_TITLE_2: {
// Prepara para empezar a dibujar en la textura de juego // Prepara para empezar a dibujar en la textura de juego
screen->start(); Screen::get()->start();
// Limpia la pantalla // Limpia la pantalla
screen->clean(bgColor); Screen::get()->clean(bgColor);
// Dibuja el tileado de fondo // Dibuja el tileado de fondo
{ {
@@ -553,15 +556,15 @@ void Title::render() {
dustBitmapL->render(); dustBitmapL->render();
// Vuelca el contenido del renderizador en pantalla // Vuelca el contenido del renderizador en pantalla
screen->blit(); Screen::get()->blit();
} break; } break;
// Sección 3 - La pantalla de titulo con el menú y la música // Sección 3 - La pantalla de titulo con el menú y la música
case SUBSECTION_TITLE_3: { // Prepara para empezar a dibujar en la textura de juego case SUBSECTION_TITLE_3: { // Prepara para empezar a dibujar en la textura de juego
screen->start(); Screen::get()->start();
// Limpia la pantalla // Limpia la pantalla
screen->clean(bgColor); Screen::get()->clean(bgColor);
// Dibuja el tileado de fondo // Dibuja el tileado de fondo
{ {
@@ -591,14 +594,14 @@ void Title::render() {
// PRESS ANY KEY! // PRESS ANY KEY!
if ((counter % 50 > 14) && (menuVisible == false)) { if ((counter % 50 > 14) && (menuVisible == false)) {
text1->writeDX(TXT_CENTER | TXT_SHADOW, GAMECANVAS_CENTER_X, PLAY_AREA_THIRD_QUARTER_Y + BLOCK, lang->getText(23), 1, noColor, 1, shdwTxtColor); text1->writeDX(TXT_CENTER | TXT_SHADOW, GAMECANVAS_CENTER_X, PLAY_AREA_THIRD_QUARTER_Y + BLOCK, Lang::get()->getText(23), 1, noColor, 1, shdwTxtColor);
} }
// Fade // Fade
fade->render(); fade->render();
// Vuelca el contenido del renderizador en pantalla // Vuelca el contenido del renderizador en pantalla
screen->blit(); Screen::get()->blit();
} break; } break;
default: default:
@@ -606,50 +609,15 @@ void Title::render() {
} }
} }
// Comprueba los eventos
void Title::checkEvents() {
// Comprueba los eventos que hay en la cola
while (SDL_PollEvent(eventHandler) != 0) {
// Evento de salida de la aplicación
if (eventHandler->type == SDL_EVENT_QUIT) {
section->name = SECTION_PROG_QUIT;
break;
}
else if (eventHandler->type == SDL_EVENT_RENDER_DEVICE_RESET || eventHandler->type == SDL_EVENT_RENDER_TARGETS_RESET) {
reLoadTextures();
}
if (section->subsection == SUBSECTION_TITLE_3) { // Si se pulsa alguna tecla durante la tercera sección del titulo
if ((eventHandler->type == SDL_EVENT_KEY_UP) || (eventHandler->type == SDL_EVENT_JOYSTICK_BUTTON_UP)) {
// Muestra el menu
menuVisible = true;
// Reinicia el contador
counter = TITLE_COUNTER;
}
}
}
}
// Comprueba las entradas // Comprueba las entradas
void Title::checkInput() { void Title::checkInput() {
#ifndef __EMSCRIPTEN__ #ifndef __EMSCRIPTEN__
if (input->checkInput(input_exit, REPEAT_FALSE)) { if (Input::get()->checkInput(input_exit, REPEAT_FALSE)) {
section->name = SECTION_PROG_QUIT; section->name = SECTION_PROG_QUIT;
} else return;
}
#endif #endif
if (input->checkInput(input_window_fullscreen, REPEAT_FALSE)) { GlobalInputs::handle();
screen->toggleVideoMode();
}
else if (input->checkInput(input_window_dec_size, REPEAT_FALSE)) {
screen->decWindowZoom();
}
else if (input->checkInput(input_window_inc_size, REPEAT_FALSE)) {
screen->incWindowZoom();
}
} }
// Actualiza el tileado de fondo // Actualiza el tileado de fondo
@@ -666,165 +634,142 @@ void Title::updateBG() {
// Cambia el valor de la variable de modo de pantalla completa // Cambia el valor de la variable de modo de pantalla completa
void Title::switchFullScreenModeVar() { void Title::switchFullScreenModeVar() {
switch (options->videoMode) { Options::video.fullscreen = !Options::video.fullscreen;
case 0:
options->videoMode = SDL_WINDOW_FULLSCREEN;
break;
case SDL_WINDOW_FULLSCREEN:
options->videoMode = 0;
break;
default:
options->videoMode = 0;
break;
}
} }
// Actualiza los elementos de los menus // Actualiza los elementos de los menus
void Title::updateMenuLabels() { void Title::updateMenuLabels() {
int i = 0; int i = 0;
// DIFFICULTY // DIFFICULTY
switch (options->difficulty) { switch (Options::settings.difficulty) {
case DIFFICULTY_EASY: case DIFFICULTY_EASY:
menu.options->setItemCaption(i, lang->getText(59) + ": " + lang->getText(66)); // EASY menu.options->setItemCaption(i, Lang::get()->getText(59) + ": " + Lang::get()->getText(66)); // EASY
break; break;
case DIFFICULTY_NORMAL: case DIFFICULTY_NORMAL:
menu.options->setItemCaption(i, lang->getText(59) + ": " + lang->getText(67)); // NORMAL menu.options->setItemCaption(i, Lang::get()->getText(59) + ": " + Lang::get()->getText(67)); // NORMAL
break; break;
case DIFFICULTY_HARD: case DIFFICULTY_HARD:
menu.options->setItemCaption(i, lang->getText(59) + ": " + lang->getText(68)); // HARD menu.options->setItemCaption(i, Lang::get()->getText(59) + ": " + Lang::get()->getText(68)); // HARD
break; break;
default: default:
menu.options->setItemCaption(i, lang->getText(59) + ": " + lang->getText(67)); // NORMAL menu.options->setItemCaption(i, Lang::get()->getText(59) + ": " + Lang::get()->getText(67)); // NORMAL
break; break;
} }
i++; i++;
// PLAYER 1 CONTROLS // PLAYER 1 CONTROLS
menu.options->setItemCaption(i, lang->getText(62)); menu.options->setItemCaption(i, Lang::get()->getText(62));
i++; i++;
// PLAYER 1 CONTROLS - OPTIONS // PLAYER 1 CONTROLS - OPTIONS
switch (options->input[0].deviceType) { switch (Options::inputs[0].deviceType) {
case INPUT_USE_KEYBOARD: case INPUT_USE_KEYBOARD:
menu.options->setItemCaption(i, lang->getText(69)); // KEYBOARD menu.options->setItemCaption(i, Lang::get()->getText(69)); // KEYBOARD
menu.options->setGreyed(i, false); menu.options->setGreyed(i, false);
break; break;
case INPUT_USE_GAMECONTROLLER: case INPUT_USE_GAMECONTROLLER:
menu.options->setItemCaption(i, lang->getText(70)); // GAME CONTROLLER menu.options->setItemCaption(i, Lang::get()->getText(70)); // GAME CONTROLLER
if (!input->gameControllerFound()) if (!Input::get()->gameControllerFound())
menu.options->setGreyed(i, true); menu.options->setGreyed(i, true);
else { else {
menu.options->setGreyed(i, false); menu.options->setGreyed(i, false);
menu.options->setItemCaption(i, options->input[0].name); menu.options->setItemCaption(i, Options::inputs[0].name);
} }
break; break;
default: default:
menu.options->setItemCaption(i, lang->getText(69)); // KEYBOARD menu.options->setItemCaption(i, Lang::get()->getText(69)); // KEYBOARD
break; break;
} }
i++; i++;
// PLAYER 2 CONTROLS // PLAYER 2 CONTROLS
menu.options->setItemCaption(i, lang->getText(63)); menu.options->setItemCaption(i, Lang::get()->getText(63));
i++; i++;
// PLAYER 2 CONTROLS - OPTIONS // PLAYER 2 CONTROLS - OPTIONS
switch (options->input[1].deviceType) { switch (Options::inputs[1].deviceType) {
case INPUT_USE_KEYBOARD: case INPUT_USE_KEYBOARD:
menu.options->setItemCaption(i, lang->getText(69)); // KEYBOARD menu.options->setItemCaption(i, Lang::get()->getText(69)); // KEYBOARD
menu.options->setGreyed(i, false); menu.options->setGreyed(i, false);
break; break;
case INPUT_USE_GAMECONTROLLER: case INPUT_USE_GAMECONTROLLER:
menu.options->setItemCaption(i, lang->getText(70)); // GAME CONTROLLER menu.options->setItemCaption(i, Lang::get()->getText(70)); // GAME CONTROLLER
if (!input->gameControllerFound()) if (!Input::get()->gameControllerFound())
menu.options->setGreyed(i, true); menu.options->setGreyed(i, true);
else { else {
menu.options->setGreyed(i, false); menu.options->setGreyed(i, false);
menu.options->setItemCaption(i, options->input[1].name); menu.options->setItemCaption(i, Options::inputs[1].name);
} }
break; break;
default: default:
menu.options->setItemCaption(i, lang->getText(69)); // KEYBOARD menu.options->setItemCaption(i, Lang::get()->getText(69)); // KEYBOARD
break; break;
} }
i++; i++;
// LANGUAGE // LANGUAGE
switch (options->language) { switch (Options::settings.language) {
case es_ES: case es_ES:
menu.options->setItemCaption(i, lang->getText(8) + ": " + lang->getText(24)); // SPANISH menu.options->setItemCaption(i, Lang::get()->getText(8) + ": " + Lang::get()->getText(24)); // SPANISH
break; break;
case ba_BA: case ba_BA:
menu.options->setItemCaption(i, lang->getText(8) + ": " + lang->getText(25)); // VALENCIAN menu.options->setItemCaption(i, Lang::get()->getText(8) + ": " + Lang::get()->getText(25)); // VALENCIAN
break; break;
case en_UK: case en_UK:
menu.options->setItemCaption(i, lang->getText(8) + ": " + lang->getText(26)); // ENGLISH menu.options->setItemCaption(i, Lang::get()->getText(8) + ": " + Lang::get()->getText(26)); // ENGLISH
break; break;
default: default:
menu.options->setItemCaption(i, lang->getText(8) + ": " + lang->getText(26)); // ENGLISH menu.options->setItemCaption(i, Lang::get()->getText(8) + ": " + Lang::get()->getText(26)); // ENGLISH
break; break;
} }
i++; i++;
// DISPLAY MODE // DISPLAY MODE
menu.options->setItemCaption(i, lang->getText(58)); menu.options->setItemCaption(i, Lang::get()->getText(58));
i++; i++;
// DISPLAY MODE - OPTIONS // DISPLAY MODE - OPTIONS
switch (options->videoMode) { menu.options->setItemCaption(i, Options::video.fullscreen ? Lang::get()->getText(5) : Lang::get()->getText(4));
case 0:
menu.options->setItemCaption(i, lang->getText(4)); // WINDOW
break;
case SDL_WINDOW_FULLSCREEN:
menu.options->setItemCaption(i, lang->getText(5)); // FULLSCREEN
break;
default:
menu.options->setItemCaption(i, lang->getText(4)); // WINDOW
break;
}
i++; i++;
// WINDOW SIZE // WINDOW SIZE
menu.options->setItemCaption(i, lang->getText(7) + " x" + std::to_string(options->windowSize)); // WINDOW SIZE menu.options->setItemCaption(i, Lang::get()->getText(7) + " x" + std::to_string(Options::window.zoom)); // WINDOW SIZE
i++; i++;
// FILTER // SCALE MODE
if (options->filter == FILTER_LINEAL) if (Options::video.scale_mode == SDL_SCALEMODE_LINEAR)
menu.options->setItemCaption(i, lang->getText(60) + ": " + lang->getText(71)); // BILINEAL menu.options->setItemCaption(i, Lang::get()->getText(60) + ": " + Lang::get()->getText(71)); // BILINEAL
else else
menu.options->setItemCaption(i, lang->getText(60) + ": " + lang->getText(72)); // LINEAL menu.options->setItemCaption(i, Lang::get()->getText(60) + ": " + Lang::get()->getText(72)); // LINEAL
i++; i++;
// VSYNC // VSYNC
if (options->vSync) if (Options::video.vsync)
menu.options->setItemCaption(i, lang->getText(61) + ": " + lang->getText(73)); // ON menu.options->setItemCaption(i, Lang::get()->getText(61) + ": " + Lang::get()->getText(73)); // ON
else else
menu.options->setItemCaption(i, lang->getText(61) + ": " + lang->getText(74)); // OFF menu.options->setItemCaption(i, Lang::get()->getText(61) + ": " + Lang::get()->getText(74)); // OFF
i++; i++;
// HOW TO PLAY // HOW TO PLAY
menu.options->setItemCaption(i, lang->getText(2)); menu.options->setItemCaption(i, Lang::get()->getText(2));
i++; i++;
// ACCEPT // ACCEPT
menu.options->setItemCaption(i, lang->getText(9)); // ACCEPT menu.options->setItemCaption(i, Lang::get()->getText(9)); // ACCEPT
i++; i++;
// CANCEL // CANCEL
menu.options->setItemCaption(i, lang->getText(10)); // CANCEL menu.options->setItemCaption(i, Lang::get()->getText(10)); // CANCEL
// Recoloca el menu de opciones // Recoloca el menu de opciones
menu.options->centerMenuOnX(GAMECANVAS_CENTER_X); menu.options->centerMenuOnX(GAMECANVAS_CENTER_X);
@@ -833,13 +778,13 @@ void Title::updateMenuLabels() {
// Establece las etiquetas del menu de titulo // Establece las etiquetas del menu de titulo
#ifdef GAME_CONSOLE #ifdef GAME_CONSOLE
menu.title->setItemCaption(0, lang->getText(0)); // PLAY menu.title->setItemCaption(0, Lang::get()->getText(0)); // PLAY
#else #else
menu.title->setItemCaption(0, lang->getText(51)); // 1 PLAYER menu.title->setItemCaption(0, Lang::get()->getText(51)); // 1 PLAYER
#endif #endif
menu.title->setItemCaption(1, lang->getText(52)); // 2 PLAYERS menu.title->setItemCaption(1, Lang::get()->getText(52)); // 2 PLAYERS
menu.title->setItemCaption(2, lang->getText(1)); // OPTIONS menu.title->setItemCaption(2, Lang::get()->getText(1)); // OPTIONS
menu.title->setItemCaption(3, lang->getText(3)); // QUIT menu.title->setItemCaption(3, Lang::get()->getText(3)); // QUIT
#ifdef __EMSCRIPTEN__ #ifdef __EMSCRIPTEN__
menu.title->setVisible(3, false); menu.title->setVisible(3, false);
menu.title->setSelectable(3, false); menu.title->setSelectable(3, false);
@@ -850,8 +795,8 @@ void Title::updateMenuLabels() {
menu.title->centerMenuElementsOnX(); menu.title->centerMenuElementsOnX();
// Establece las etiquetas del menu de seleccion de jugador // Establece las etiquetas del menu de seleccion de jugador
menu.playerSelect->setItemCaption(0, lang->getText(39)); // SELECT PLAYER menu.playerSelect->setItemCaption(0, Lang::get()->getText(39)); // SELECT PLAYER
menu.playerSelect->setItemCaption(3, lang->getText(40)); // BACK menu.playerSelect->setItemCaption(3, Lang::get()->getText(40)); // BACK
// Recoloca el menu de selección de jugador // Recoloca el menu de selección de jugador
menu.playerSelect->centerMenuOnX(GAMECANVAS_CENTER_X); menu.playerSelect->centerMenuOnX(GAMECANVAS_CENTER_X);
@@ -871,9 +816,11 @@ void Title::updateMenuLabels() {
// Aplica las opciones de menu seleccionadas // Aplica las opciones de menu seleccionadas
void Title::applyOptions() { void Title::applyOptions() {
screen->setVideoMode(options->videoMode != 0); Screen::get()->setVideoMode(Options::video.fullscreen);
Screen::get()->setWindowZoom(Options::window.zoom);
Screen::get()->setVSync(Options::video.vsync);
lang->setLang(options->language); Lang::get()->setLang(Options::settings.language);
updateMenuLabels(); updateMenuLabels();
createTiledBackground(); createTiledBackground();
@@ -942,7 +889,7 @@ void Title::iterate() {
} }
// Procesa un evento individual // Procesa un evento individual
void Title::handleEvent(SDL_Event *event) { void Title::handleEvent(const SDL_Event *event) {
// Si hay un sub-estado activo, delega el evento // Si hay un sub-estado activo, delega el evento
if (instructionsActive && instructions) { if (instructionsActive && instructions) {
// SDL_EVENT_QUIT ya lo maneja Director // SDL_EVENT_QUIT ya lo maneja Director
@@ -977,7 +924,7 @@ void Title::run() {
// Inicia la parte donde se muestran las instrucciones // Inicia la parte donde se muestran las instrucciones
void Title::runInstructions(mode_e mode) { void Title::runInstructions(mode_e mode) {
instructions = new Instructions(renderer, screen, asset, input, lang, section); instructions = new Instructions(renderer, section);
instructions->start(mode); instructions->start(mode);
instructionsActive = true; instructionsActive = true;
instructionsMode = mode; instructionsMode = mode;
@@ -988,7 +935,7 @@ void Title::runDemoGame() {
// Temporalmente ponemos section para que el constructor de Game funcione // Temporalmente ponemos section para que el constructor de Game funcione
section->name = SECTION_PROG_GAME; section->name = SECTION_PROG_GAME;
section->subsection = SUBSECTION_GAME_PLAY_1P; section->subsection = SUBSECTION_GAME_PLAY_1P;
demoGame = new Game(1, 0, renderer, screen, asset, lang, input, true, options, section); demoGame = new Game(1, 0, renderer, true, section);
demoGameActive = true; demoGameActive = true;
// Restauramos section para que Director no transicione fuera de Title // Restauramos section para que Director no transicione fuera de Title
section->name = SECTION_PROG_TITLE; section->name = SECTION_PROG_TITLE;
@@ -998,21 +945,21 @@ void Title::runDemoGame() {
bool Title::updatePlayerInputs(int numPlayer) { bool Title::updatePlayerInputs(int numPlayer) {
const int numDevices = availableInputDevices.size(); const int numDevices = availableInputDevices.size();
if (!input->gameControllerFound()) { // Si no hay mandos se deja todo de manera prefijada if (!Input::get()->gameControllerFound()) { // Si no hay mandos se deja todo de manera prefijada
deviceIndex[0] = 0; deviceIndex[0] = 0;
deviceIndex[1] = 0; deviceIndex[1] = 0;
options->input[0].id = -1; Options::inputs[0].id = -1;
options->input[0].name = "KEYBOARD"; Options::inputs[0].name = "KEYBOARD";
options->input[0].deviceType = INPUT_USE_KEYBOARD; Options::inputs[0].deviceType = INPUT_USE_KEYBOARD;
options->input[1].id = 0; Options::inputs[1].id = 0;
options->input[1].name = "GAME CONTROLLER"; Options::inputs[1].name = "GAME CONTROLLER";
options->input[1].deviceType = INPUT_USE_GAMECONTROLLER; Options::inputs[1].deviceType = INPUT_USE_GAMECONTROLLER;
return true; return true;
} else { // Si hay mas de un dispositivo, se recorre el vector } else { // Si hay mas de un dispositivo, se recorre el vector
if (options->console) { if (Options::settings.console) {
std::cout << "numplayer:" << numPlayer << std::endl; std::cout << "numplayer:" << numPlayer << std::endl;
std::cout << "deviceindex:" << deviceIndex[numPlayer] << std::endl; std::cout << "deviceindex:" << deviceIndex[numPlayer] << std::endl;
} }
@@ -1023,7 +970,7 @@ bool Title::updatePlayerInputs(int numPlayer) {
} else { } else {
deviceIndex[numPlayer] = 0; deviceIndex[numPlayer] = 0;
} }
if (options->console) { if (Options::settings.console) {
std::cout << "deviceindex:" << deviceIndex[numPlayer] << std::endl; std::cout << "deviceindex:" << deviceIndex[numPlayer] << std::endl;
} }
@@ -1037,8 +984,8 @@ bool Title::updatePlayerInputs(int numPlayer) {
} }
// Copia el dispositivo marcado por el indice a la variable de opciones de cada jugador // Copia el dispositivo marcado por el indice a la variable de opciones de cada jugador
options->input[0] = availableInputDevices[deviceIndex[0]]; Options::inputs[0] = availableInputDevices[deviceIndex[0]];
options->input[1] = availableInputDevices[deviceIndex[1]]; Options::inputs[1] = availableInputDevices[deviceIndex[1]];
return true; return true;
} }
@@ -1052,13 +999,13 @@ void Title::createTiledBackground() {
SDL_SetTextureScaleMode(background, Texture::currentScaleMode); SDL_SetTextureScaleMode(background, Texture::currentScaleMode);
} }
if (background == nullptr) { if (background == nullptr) {
if (options->console) { if (Options::settings.console) {
std::cout << "TitleSurface could not be created!\nSDL Error: " << SDL_GetError() << std::endl; std::cout << "TitleSurface could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
} }
} }
// Crea los objetos para pintar en la textura de fondo // Crea los objetos para pintar en la textura de fondo
Texture *bgTileTexture = new Texture(renderer, asset->get("title_bg_tile.png")); Texture *bgTileTexture = new Texture(renderer, Asset::get()->get("title_bg_tile.png"));
Sprite *tile = new Sprite({0, 0, 64, 64}, bgTileTexture, renderer); Sprite *tile = new Sprite({0, 0, 64, 64}, bgTileTexture, renderer);
// Prepara para dibujar sobre la textura // Prepara para dibujar sobre la textura
@@ -1089,10 +1036,10 @@ void Title::createTiledBackground() {
// El estado de Input lo mantiene al día Director via eventos SDL_EVENT_GAMEPAD_ADDED/REMOVED, // El estado de Input lo mantiene al día Director via eventos SDL_EVENT_GAMEPAD_ADDED/REMOVED,
// así que aquí solo leemos la lista actual sin reescanear. // así que aquí solo leemos la lista actual sin reescanear.
void Title::checkInputDevices() { void Title::checkInputDevices() {
if (options->console) { if (Options::settings.console) {
std::cout << "Filling devices for options menu..." << std::endl; std::cout << "Filling devices for options menu..." << std::endl;
} }
const int numControllers = input->getNumControllers(); const int numControllers = Input::get()->getNumControllers();
availableInputDevices.clear(); availableInputDevices.clear();
input_t temp; input_t temp;
@@ -1100,10 +1047,10 @@ void Title::checkInputDevices() {
if (numControllers > 0) if (numControllers > 0)
for (int i = 0; i < numControllers; ++i) { for (int i = 0; i < numControllers; ++i) {
temp.id = i; temp.id = i;
temp.name = input->getControllerName(i); temp.name = Input::get()->getControllerName(i);
temp.deviceType = INPUT_USE_GAMECONTROLLER; temp.deviceType = INPUT_USE_GAMECONTROLLER;
availableInputDevices.push_back(temp); availableInputDevices.push_back(temp);
if (options->console) { if (Options::settings.console) {
std::cout << "Device " << (int)availableInputDevices.size() << " - " << temp.name.c_str() << std::endl; std::cout << "Device " << (int)availableInputDevices.size() << " - " << temp.name.c_str() << std::endl;
} }
} }
@@ -1113,7 +1060,7 @@ void Title::checkInputDevices() {
temp.name = "KEYBOARD"; temp.name = "KEYBOARD";
temp.deviceType = INPUT_USE_KEYBOARD; temp.deviceType = INPUT_USE_KEYBOARD;
availableInputDevices.push_back(temp); availableInputDevices.push_back(temp);
if (options->console) { if (Options::settings.console) {
std::cout << "Device " << (int)availableInputDevices.size() << " - " << temp.name.c_str() << std::endl; std::cout << "Device " << (int)availableInputDevices.size() << " - " << temp.name.c_str() << std::endl;
std::cout << std::endl; std::cout << std::endl;
} }

View File

@@ -4,16 +4,13 @@
#include <vector> // for vector #include <vector> // for vector
#include "instructions.h" // for mode_e #include "game/options.hpp" // for Options::Video, Options::Window (per snapshot cancel)
#include "utils.h" // for input_t, options_t, section_t #include "game/scenes/instructions.h" // for mode_e
#include "utils/utils.h" // for input_t, section_t
class AnimatedSprite; class AnimatedSprite;
class Asset;
class Fade; class Fade;
class Game; class Game;
class Input;
class Lang;
class Menu; class Menu;
class Screen;
class SmartSprite; class SmartSprite;
class Sprite; class Sprite;
class Text; class Text;
@@ -42,10 +39,6 @@ class Title {
// Objetos y punteros // Objetos y punteros
SDL_Renderer *renderer; // El renderizador de la ventana SDL_Renderer *renderer; // El renderizador de la ventana
Screen *screen; // Objeto encargado de dibujar en pantalla
Asset *asset; // Objeto que gestiona todos los ficheros de recursos
Input *input; // Objeto para leer las entradas de teclado o mando
Lang *lang; // Objeto para gestionar los textos en diferentes idiomas
Instructions *instructions; // Objeto para la sección de las instrucciones Instructions *instructions; // Objeto para la sección de las instrucciones
Game *demoGame; // Objeto para lanzar la demo del juego Game *demoGame; // Objeto para lanzar la demo del juego
SDL_Event *eventHandler; // Manejador de eventos SDL_Event *eventHandler; // Manejador de eventos
@@ -72,21 +65,24 @@ class Title {
Fade *fade; // Objeto para realizar fundidos en pantalla Fade *fade; // Objeto para realizar fundidos en pantalla
// Variable // Variable
JA_Music_t *titleMusic; // Musica para el titulo JA_Music_t *titleMusic; // Musica para el titulo
JA_Sound_t *crashSound; // Sonido con el impacto del título JA_Sound_t *crashSound; // Sonido con el impacto del título
int backgroundCounter; // Temporizador para el fondo de tiles de la pantalla de titulo int backgroundCounter; // Temporizador para el fondo de tiles de la pantalla de titulo
int counter; // Temporizador para la pantalla de titulo int counter; // Temporizador para la pantalla de titulo
Uint32 ticks; // Contador de ticks para ajustar la velocidad del programa Uint32 ticks; // Contador de ticks para ajustar la velocidad del programa
Uint8 backgroundMode; // Variable para almacenar el tipo de efecto que hará el fondo de la pantalla de titulo Uint8 backgroundMode; // Variable para almacenar el tipo de efecto que hará el fondo de la pantalla de titulo
float sin[360]; // Vector con los valores del seno precalculados float sin[360]; // Vector con los valores del seno precalculados
bool menuVisible; // Indicador para saber si se muestra el menu del titulo o la frase intermitente bool menuVisible; // Indicador para saber si se muestra el menu del titulo o la frase intermitente
bool demo; // Indica si el modo demo estará activo bool demo; // Indica si el modo demo estará activo
section_t nextSection; // Indica cual es la siguiente sección a cargar cuando termine el contador del titulo section_t nextSection; // Indica cual es la siguiente sección a cargar cuando termine el contador del titulo
Uint32 ticksSpeed; // Velocidad a la que se repiten los bucles del programa Uint32 ticksSpeed; // Velocidad a la que se repiten los bucles del programa
Uint8 postFade; // Opción a realizar cuando termina el fundido Uint8 postFade; // Opción a realizar cuando termina el fundido
menu_t menu; // Variable con todos los objetos menus y sus variables menu_t menu; // Variable con todos los objetos menus y sus variables
struct options_t *options; // Variable con todas las variables de las opciones del programa // Snapshot per a permetre CANCEL al menú d'opcions.
options_t optionsPrevious; // Variable de respaldo para las opciones Options::Video prevVideo;
Options::Window prevWindow;
Options::Settings prevSettings;
std::vector<input_t> prevInputs;
std::vector<input_t> availableInputDevices; // Vector con todos los metodos de control disponibles std::vector<input_t> availableInputDevices; // Vector con todos los metodos de control disponibles
std::vector<int> deviceIndex; // Indice para el jugador [i] del vector de dispositivos de entrada disponibles std::vector<int> deviceIndex; // Indice para el jugador [i] del vector de dispositivos de entrada disponibles
@@ -111,9 +107,6 @@ class Title {
// Dibuja el objeto en pantalla // Dibuja el objeto en pantalla
void render(); void render();
// Comprueba los eventos
void checkEvents();
// Comprueba las entradas // Comprueba las entradas
void checkInput(); void checkInput();
@@ -149,7 +142,7 @@ class Title {
public: public:
// Constructor // Constructor
Title(SDL_Renderer *renderer, Screen *screen, Input *input, Asset *asset, options_t *options, Lang *lang, section_t *section); Title(SDL_Renderer *renderer, section_t *section);
// Destructor // Destructor
~Title(); ~Title();
@@ -161,5 +154,5 @@ class Title {
void iterate(); void iterate();
// Procesa un evento // Procesa un evento
void handleEvent(SDL_Event *event); void handleEvent(const SDL_Event *event);
}; };

View File

@@ -1,21 +1,23 @@
#include "menu.h" #include "game/ui/menu.h"
#include <algorithm> // for max, min #include <algorithm> // for max, min
#include <fstream> // for char_traits, basic_ifstream, basic_istream #include <fstream> // for char_traits, basic_ifstream, basic_istream
#include <numeric> // for accumulate
#include <sstream> // for basic_stringstream #include <sstream> // for basic_stringstream
#include "asset.h" // for Asset #include "core/audio/jail_audio.hpp" // for JA_LoadSound, JA_PlaySound, JA_DeleteSound
#include "input.h" // for Input, REPEAT_FALSE, inputs_e #include "core/input/input.h" // for Input, REPEAT_FALSE, inputs_e
#include "jail_audio.hpp" // for JA_LoadSound, JA_PlaySound, JA_DeleteSound #include "core/rendering/text.h" // for Text
#include "resource_helper.h" #include "core/resources/asset.h" // for Asset
#include "text.h" // for Text #include "core/resources/resource_helper.h"
// Constructor // Constructor
Menu::Menu(SDL_Renderer *renderer, Asset *asset, Input *input, std::string file) { Menu::Menu(SDL_Renderer *renderer, const std::string &file)
: colorGreyed{128, 128, 128},
font_png(""),
font_txt("") {
// Copia punteros // Copia punteros
this->renderer = renderer; this->renderer = renderer;
this->asset = asset;
this->input = input;
// Inicializa punteros // Inicializa punteros
soundMove = nullptr; soundMove = nullptr;
@@ -23,7 +25,6 @@ Menu::Menu(SDL_Renderer *renderer, Asset *asset, Input *input, std::string file)
soundCancel = nullptr; soundCancel = nullptr;
// Inicializa variables // Inicializa variables
name = "";
selector.index = 0; selector.index = 0;
selector.previousIndex = 0; selector.previousIndex = 0;
itemSelected = MENU_NO_OPTION; itemSelected = MENU_NO_OPTION;
@@ -40,10 +41,7 @@ Menu::Menu(SDL_Renderer *renderer, Asset *asset, Input *input, std::string file)
centerX = 0; centerX = 0;
centerY = 0; centerY = 0;
widestItem = 0; widestItem = 0;
colorGreyed = {128, 128, 128};
defaultActionWhenCancel = 0; defaultActionWhenCancel = 0;
font_png = "";
font_txt = "";
// Selector // Selector
selector.originY = 0; selector.originY = 0;
@@ -65,7 +63,7 @@ Menu::Menu(SDL_Renderer *renderer, Asset *asset, Input *input, std::string file)
// Inicializa las variables desde un fichero. Si no se pasa fichero, el // Inicializa las variables desde un fichero. Si no se pasa fichero, el
// llamante (p.ej. Resource::preloadAll) usará loadFromBytes después — // llamante (p.ej. Resource::preloadAll) usará loadFromBytes después —
// y ese método ya llama a setSelectorItemColors() y reset() al final. // y ese método ya llama a setSelectorItemColors() y reset() al final.
if (file != "") { if (!file.empty()) {
load(file); load(file);
setSelectorItemColors(); setSelectorItemColors();
reset(); reset();
@@ -74,8 +72,6 @@ Menu::Menu(SDL_Renderer *renderer, Asset *asset, Input *input, std::string file)
Menu::~Menu() { Menu::~Menu() {
renderer = nullptr; renderer = nullptr;
asset = nullptr;
input = nullptr;
if (soundMove) { if (soundMove) {
JA_DeleteSound(soundMove); JA_DeleteSound(soundMove);
@@ -103,24 +99,24 @@ bool Menu::parseFromStream(std::istream &file, const std::string &filename) {
while (std::getline(file, line)) { while (std::getline(file, line)) {
if (line == "[item]") { if (line == "[item]") {
item_t item; item_t newItem;
item.label = ""; newItem.label = "";
item.hPaddingDown = 1; newItem.hPaddingDown = 1;
item.selectable = true; newItem.selectable = true;
item.greyed = false; newItem.greyed = false;
item.linkedDown = false; newItem.linkedDown = false;
item.visible = true; newItem.visible = true;
item.line = false; newItem.line = false;
do { do {
std::getline(file, line); std::getline(file, line);
int pos = line.find("="); int pos = line.find("=");
if (!setItem(&item, line.substr(0, pos), line.substr(pos + 1, line.length()))) { if (!setItem(&newItem, line.substr(0, pos), line.substr(pos + 1, line.length()))) {
success = false; success = false;
} }
} while (line != "[/item]"); } while (line != "[/item]");
addItem(item); addItem(newItem);
} else { } else {
int pos = line.find("="); int pos = line.find("=");
if (!setVars(line.substr(0, pos), line.substr(pos + 1, line.length()))) { if (!setVars(line.substr(0, pos), line.substr(pos + 1, line.length()))) {
@@ -130,8 +126,8 @@ bool Menu::parseFromStream(std::istream &file, const std::string &filename) {
// Crea el objeto text tan pronto como se pueda. Necesario para añadir items. // Crea el objeto text tan pronto como se pueda. Necesario para añadir items.
// Carga via ResourceHelper para que funcione tanto con pack como con filesystem. // Carga via ResourceHelper para que funcione tanto con pack como con filesystem.
if (font_png != "" && font_txt != "" && !textAllocated) { if (font_png != "" && font_txt != "" && !textAllocated) {
auto pngBytes = ResourceHelper::loadFile(asset->get(font_png)); auto pngBytes = ResourceHelper::loadFile(Asset::get()->get(font_png));
auto txtBytes = ResourceHelper::loadFile(asset->get(font_txt)); auto txtBytes = ResourceHelper::loadFile(Asset::get()->get(font_txt));
text = new Text(pngBytes, txtBytes, renderer); text = new Text(pngBytes, txtBytes, renderer);
textAllocated = true; textAllocated = true;
} }
@@ -141,7 +137,7 @@ bool Menu::parseFromStream(std::istream &file, const std::string &filename) {
} }
// Carga la configuración del menu desde un archivo de texto // Carga la configuración del menu desde un archivo de texto
bool Menu::load(std::string file_path) { bool Menu::load(const std::string &file_path) {
const std::string filename = file_path.substr(file_path.find_last_of("\\/") + 1); const std::string filename = file_path.substr(file_path.find_last_of("\\/") + 1);
std::ifstream file(file_path); std::ifstream file(file_path);
if (!file.good()) { if (!file.good()) {
@@ -162,7 +158,7 @@ bool Menu::loadFromBytes(const std::vector<uint8_t> &bytes, const std::string &n
} }
// Asigna variables a partir de dos cadenas // Asigna variables a partir de dos cadenas
bool Menu::setItem(item_t *item, std::string var, std::string value) { bool Menu::setItem(item_t *item, const std::string &var, const std::string &value) {
// Indicador de éxito en la asignación // Indicador de éxito en la asignación
bool success = true; bool success = true;
@@ -205,7 +201,7 @@ bool Menu::setItem(item_t *item, std::string var, std::string value) {
} }
// Asigna variables a partir de dos cadenas // Asigna variables a partir de dos cadenas
bool Menu::setVars(std::string var, std::string value) { bool Menu::setVars(const std::string &var, const std::string &value) {
// Indicador de éxito en la asignación // Indicador de éxito en la asignación
bool success = true; bool success = true;
@@ -218,17 +214,17 @@ bool Menu::setVars(std::string var, std::string value) {
} }
else if (var == "sound_cancel") { else if (var == "sound_cancel") {
auto bytes = ResourceHelper::loadFile(asset->get(value)); auto bytes = ResourceHelper::loadFile(Asset::get()->get(value));
if (!bytes.empty()) soundCancel = JA_LoadSound(bytes.data(), (uint32_t)bytes.size()); if (!bytes.empty()) soundCancel = JA_LoadSound(bytes.data(), (uint32_t)bytes.size());
} }
else if (var == "sound_accept") { else if (var == "sound_accept") {
auto bytes = ResourceHelper::loadFile(asset->get(value)); auto bytes = ResourceHelper::loadFile(Asset::get()->get(value));
if (!bytes.empty()) soundAccept = JA_LoadSound(bytes.data(), (uint32_t)bytes.size()); if (!bytes.empty()) soundAccept = JA_LoadSound(bytes.data(), (uint32_t)bytes.size());
} }
else if (var == "sound_move") { else if (var == "sound_move") {
auto bytes = ResourceHelper::loadFile(asset->get(value)); auto bytes = ResourceHelper::loadFile(Asset::get()->get(value));
if (!bytes.empty()) soundMove = JA_LoadSound(bytes.data(), (uint32_t)bytes.size()); if (!bytes.empty()) soundMove = JA_LoadSound(bytes.data(), (uint32_t)bytes.size());
} }
@@ -323,7 +319,7 @@ bool Menu::setVars(std::string var, std::string value) {
} }
// Carga los ficheros de audio // Carga los ficheros de audio
void Menu::loadAudioFile(std::string file, int sound) { void Menu::loadAudioFile(const std::string &file, int sound) {
switch (sound) { switch (sound) {
case SOUND_ACCEPT: case SOUND_ACCEPT:
soundAccept = JA_LoadSound(file.c_str()); soundAccept = JA_LoadSound(file.c_str());
@@ -343,7 +339,7 @@ void Menu::loadAudioFile(std::string file, int sound) {
} }
// Obtiene el nombre del menu // Obtiene el nombre del menu
std::string Menu::getName() { const std::string &Menu::getName() const {
return name; return name;
} }
@@ -434,14 +430,8 @@ void Menu::setSelectorPos(int index) {
// Obtiene la anchura del elemento más ancho del menu // Obtiene la anchura del elemento más ancho del menu
int Menu::getWidestItem() { int Menu::getWidestItem() {
int result = 0; return std::accumulate(item.begin(), item.end(), 0,
[](int acc, const item_t &i) { return std::max(acc, i.rect.w); });
// Obtenemos la anchura del item mas ancho
for (auto &i : item) {
result = std::max(result, i.rect.w);
}
return result;
} }
// Deja el menu apuntando al primer elemento // Deja el menu apuntando al primer elemento
@@ -478,7 +468,7 @@ void Menu::reorganize() {
} }
// Deja el menu apuntando al siguiente elemento // Deja el menu apuntando al siguiente elemento
bool Menu::increaseSelectorIndex() { void Menu::increaseSelectorIndex() {
// Guarda el indice actual antes de modificarlo // Guarda el indice actual antes de modificarlo
selector.previousIndex = selector.index; selector.previousIndex = selector.index;
@@ -503,12 +493,10 @@ bool Menu::increaseSelectorIndex() {
if (selector.incH != 0) { if (selector.incH != 0) {
selector.resizing = true; selector.resizing = true;
} }
return true;
} }
// Deja el menu apuntando al elemento anterior // Deja el menu apuntando al elemento anterior
bool Menu::decreaseSelectorIndex() { void Menu::decreaseSelectorIndex() {
// Guarda el indice actual antes de modificarlo // Guarda el indice actual antes de modificarlo
selector.previousIndex = selector.index; selector.previousIndex = selector.index;
@@ -542,8 +530,6 @@ bool Menu::decreaseSelectorIndex() {
if (selector.incH != 0) { if (selector.incH != 0) {
selector.resizing = true; selector.resizing = true;
} }
return true;
} }
// Actualiza la logica del menu // Actualiza la logica del menu
@@ -756,7 +742,7 @@ void Menu::addItem(item_t temp) {
} }
// Cambia el texto de un item // Cambia el texto de un item
void Menu::setItemCaption(int index, std::string text) { void Menu::setItemCaption(int index, const std::string &text) {
item[index].label = text; item[index].label = text;
item[index].rect.w = this->text->lenght(item[index].label); item[index].rect.w = this->text->lenght(item[index].label);
item[index].rect.h = this->text->getCharacterSize(); item[index].rect.h = this->text->getCharacterSize();
@@ -770,30 +756,28 @@ void Menu::setDefaultActionWhenCancel(int item) {
// Gestiona la entrada de teclado y mando durante el menu // Gestiona la entrada de teclado y mando durante el menu
void Menu::checkInput() { void Menu::checkInput() {
if (input->checkInput(input_up, REPEAT_FALSE)) { if (Input::get()->checkInput(input_up, REPEAT_FALSE)) {
if (decreaseSelectorIndex()) { decreaseSelectorIndex();
if (soundMove) { if (soundMove) {
JA_PlaySound(soundMove); JA_PlaySound(soundMove);
}
} }
} }
if (input->checkInput(input_down, REPEAT_FALSE)) { if (Input::get()->checkInput(input_down, REPEAT_FALSE)) {
if (increaseSelectorIndex()) { increaseSelectorIndex();
if (soundMove) { if (soundMove) {
JA_PlaySound(soundMove); JA_PlaySound(soundMove);
}
} }
} }
if (input->checkInput(input_accept, REPEAT_FALSE)) { if (Input::get()->checkInput(input_accept, REPEAT_FALSE)) {
itemSelected = selector.index; itemSelected = selector.index;
if (soundAccept) { if (soundAccept) {
JA_PlaySound(soundAccept); JA_PlaySound(soundAccept);
} }
} }
if (input->checkInput(input_cancel, REPEAT_FALSE)) { if (Input::get()->checkInput(input_cancel, REPEAT_FALSE)) {
itemSelected = defaultActionWhenCancel; itemSelected = defaultActionWhenCancel;
if (soundCancel) { if (soundCancel) {
JA_PlaySound(soundCancel); JA_PlaySound(soundCancel);
@@ -808,12 +792,8 @@ int Menu::findWidth() {
// Calcula el alto del menu // Calcula el alto del menu
int Menu::findHeight() { int Menu::findHeight() {
int height = 0; const int height = std::accumulate(item.begin(), item.end(), 0,
[](int acc, const item_t &i) { return acc + i.rect.h + i.hPaddingDown; });
// Obtenemos la altura de la suma de alturas de los items
for (auto &i : item) {
height += i.rect.h + i.hPaddingDown;
}
return height - item.back().hPaddingDown; return height - item.back().hPaddingDown;
} }
@@ -857,7 +837,7 @@ int Menu::getSelectorHeight(int value) {
} }
// Establece el nombre del menu // Establece el nombre del menu
void Menu::setName(std::string name) { void Menu::setName(const std::string &name) {
this->name = name; this->name = name;
} }
@@ -873,9 +853,9 @@ void Menu::setBackgroundType(int value) {
} }
// Establece la fuente de texto que se utilizará // Establece la fuente de texto que se utilizará
void Menu::setText(std::string font_png, std::string font_txt) { void Menu::setText(const std::string &font_png, const std::string &font_txt) {
if (!text) { if (!text) {
text = new Text(asset->get(font_png), asset->get(font_txt), renderer); text = new Text(Asset::get()->get(font_png), Asset::get()->get(font_txt), renderer);
} }
} }

View File

@@ -6,9 +6,7 @@
#include <string> // for string, basic_string #include <string> // for string, basic_string
#include <vector> // for vector #include <vector> // for vector
#include "utils.h" // for color_t #include "utils/utils.h" // for color_t
class Asset;
class Input;
class Text; class Text;
struct JA_Sound_t; struct JA_Sound_t;
@@ -70,9 +68,7 @@ class Menu {
// Objetos y punteros // Objetos y punteros
SDL_Renderer *renderer; // Puntero al renderizador de la ventana SDL_Renderer *renderer; // Puntero al renderizador de la ventana
Asset *asset; // Objeto para gestionar los ficheros de recursos
Text *text; // Texto para poder escribir los items del menu Text *text; // Texto para poder escribir los items del menu
Input *input; // Gestor de eventos de entrada de teclado o gamepad
// Variables // Variables
std::string name; // Nombre del menu std::string name; // Nombre del menu
@@ -100,25 +96,25 @@ class Menu {
std::string font_txt; std::string font_txt;
// Carga la configuración del menu desde un archivo de texto // Carga la configuración del menu desde un archivo de texto
bool load(std::string file_path); bool load(const std::string &file_path);
// Parser compartido (recibe cualquier istream) // Parser compartido (recibe cualquier istream)
bool parseFromStream(std::istream &file, const std::string &filename); bool parseFromStream(std::istream &file, const std::string &filename);
// Asigna variables a partir de dos cadenas // Asigna variables a partir de dos cadenas
bool setVars(std::string var, std::string value); bool setVars(const std::string &var, const std::string &value);
// Asigna variables a partir de dos cadenas // Asigna variables a partir de dos cadenas
bool setItem(item_t *item, std::string var, std::string value); bool setItem(item_t *item, const std::string &var, const std::string &value);
// Actualiza el menu para recolocarlo correctamente y establecer el tamaño // Actualiza el menu para recolocarlo correctamente y establecer el tamaño
void reorganize(); void reorganize();
// Deja el menu apuntando al siguiente elemento // Deja el menu apuntando al siguiente elemento
bool increaseSelectorIndex(); void increaseSelectorIndex();
// Deja el menu apuntando al elemento anterior // Deja el menu apuntando al elemento anterior
bool decreaseSelectorIndex(); void decreaseSelectorIndex();
// Actualiza la posicion y el estado del selector // Actualiza la posicion y el estado del selector
void updateSelector(); void updateSelector();
@@ -146,7 +142,7 @@ class Menu {
public: public:
// Constructor // Constructor
Menu(SDL_Renderer *renderer, Asset *asset, Input *input, std::string file = ""); explicit Menu(SDL_Renderer *renderer, const std::string &file = "");
// Destructor // Destructor
~Menu(); ~Menu();
@@ -155,10 +151,10 @@ class Menu {
bool loadFromBytes(const std::vector<uint8_t> &bytes, const std::string &nameForLogs = ""); bool loadFromBytes(const std::vector<uint8_t> &bytes, const std::string &nameForLogs = "");
// Carga los ficheros de audio // Carga los ficheros de audio
void loadAudioFile(std::string file, int sound); void loadAudioFile(const std::string &file, int sound);
// Obtiene el nombre del menu // Obtiene el nombre del menu
std::string getName(); const std::string &getName() const;
// Obtiene el valor de la variable // Obtiene el valor de la variable
int getItemSelected(); int getItemSelected();
@@ -197,7 +193,7 @@ class Menu {
void addItem(item_t item); void addItem(item_t item);
// Cambia el texto de un item // Cambia el texto de un item
void setItemCaption(int index, std::string text); void setItemCaption(int index, const std::string &text);
// Establece el indice del item que se usará por defecto al cancelar el menu // Establece el indice del item que se usará por defecto al cancelar el menu
void setDefaultActionWhenCancel(int item); void setDefaultActionWhenCancel(int item);
@@ -218,7 +214,7 @@ class Menu {
void setVisible(int index, bool value); void setVisible(int index, bool value);
// Establece el nombre del menu // Establece el nombre del menu
void setName(std::string name); void setName(const std::string &name);
// Establece la posición del menu // Establece la posición del menu
void setPos(int x, int y); void setPos(int x, int y);
@@ -227,7 +223,7 @@ class Menu {
void setBackgroundType(int value); void setBackgroundType(int value);
// Establece la fuente de texto que se utilizará // Establece la fuente de texto que se utilizará
void setText(std::string font_png, std::string font_txt); void setText(const std::string &font_png, const std::string &font_txt);
// Establece el rectangulo de fondo del menu // Establece el rectangulo de fondo del menu
void setRectSize(int w = 0, int h = 0); void setRectSize(int w = 0, int h = 0);

View File

@@ -42,7 +42,7 @@ Reescribiendo el código el 27/09/2022
#define SDL_MAIN_USE_CALLBACKS 1 #define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL_main.h> #include <SDL3/SDL_main.h>
#include "director.h" #include "core/system/director.h"
#include "external/stb_vorbis.c" #include "external/stb_vorbis.c"
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) {

View File

@@ -1,369 +0,0 @@
#include "screen.h"
#include <SDL3/SDL.h>
#include <algorithm> // for max, min
#include <iostream> // for basic_ostream, operator<<, cout, endl
#include <string> // for basic_string, char_traits, string
#include "asset.h" // for Asset
#include "mouse.hpp" // for Mouse::cursorVisible, Mouse::lastMouseMoveTime
#include "resource.h"
#include "text.h" // for Text, TXT_CENTER, TXT_COLOR, TXT_STROKE
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/html5.h>
// --- Fix per a fullscreen/resize en Emscripten ---
//
// SDL3 + Emscripten no emet de forma fiable SDL_EVENT_WINDOW_LEAVE_FULLSCREEN
// (libsdl-org/SDL#13300) ni SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED /
// SDL_EVENT_DISPLAY_ORIENTATION (libsdl-org/SDL#11389). Quan l'usuari ix de
// fullscreen amb Esc o rota el mòbil, el canvas HTML torna al tamany correcte
// però l'estat intern de SDL creu que segueix en fullscreen amb la resolució
// anterior i el viewport queda desencuadrat.
//
// Solució: registrar callbacks natius d'Emscripten, diferir la feina un tick
// del event loop (el canvas encara no està estable en el moment del callback)
// i cridar setVideoMode() amb el flag de fullscreen actualitzat. La crida
// interna a SDL_SetWindowFullscreen(false) és la peça que realment fa eixir
// SDL del seu estat intern de fullscreen — sense això res més funciona.
namespace {
Screen *g_screen_instance = nullptr;
void deferredCanvasResize(void * /*userData*/) {
if (g_screen_instance) {
g_screen_instance->handleCanvasResized();
}
}
EM_BOOL onEmFullscreenChange(int /*eventType*/, const EmscriptenFullscreenChangeEvent *event, void * /*userData*/) {
if (g_screen_instance && event) {
g_screen_instance->syncFullscreenFlagFromBrowser(event->isFullscreen != 0);
}
emscripten_async_call(deferredCanvasResize, nullptr, 0);
return EM_FALSE;
}
EM_BOOL onEmOrientationChange(int /*eventType*/, const EmscriptenOrientationChangeEvent * /*event*/, void * /*userData*/) {
emscripten_async_call(deferredCanvasResize, nullptr, 0);
return EM_FALSE;
}
} // namespace
#endif // __EMSCRIPTEN__
// Constructor
Screen::Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options_t *options) {
// Inicializa variables
this->window = window;
this->renderer = renderer;
this->options = options;
this->asset = asset;
gameCanvasWidth = options->gameWidth;
gameCanvasHeight = options->gameHeight;
borderWidth = options->borderWidth * 2;
borderHeight = options->borderHeight * 2;
// Define el color del borde para el modo de pantalla completa
borderColor = {0x00, 0x00, 0x00};
// Crea la textura donde se dibujan los graficos del juego
gameCanvas = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, gameCanvasWidth, gameCanvasHeight);
if (gameCanvas != nullptr) {
SDL_SetTextureScaleMode(gameCanvas, options->filter == FILTER_NEAREST ? SDL_SCALEMODE_NEAREST : SDL_SCALEMODE_LINEAR);
}
if (gameCanvas == nullptr) {
if (options->console) {
std::cout << "gameCanvas could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
}
}
// Establece el modo de video
setVideoMode(options->videoMode != 0);
// Inicializa el sistema de notificaciones (Text compartido de Resource)
notificationText = Resource::get()->getText("8bithud");
notificationMessage = "";
notificationTextColor = {0xFF, 0xFF, 0xFF};
notificationOutlineColor = {0x00, 0x00, 0x00};
notificationEndTime = 0;
notificationY = 2;
// Registra callbacks natius d'Emscripten per a fullscreen/orientation
registerEmscriptenEventCallbacks();
}
// Destructor
Screen::~Screen() {
// notificationText es propiedad de Resource — no liberar.
SDL_DestroyTexture(gameCanvas);
}
// Limpia la pantalla
void Screen::clean(color_t color) {
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, 0xFF);
SDL_RenderClear(renderer);
}
// Prepara para empezar a dibujar en la textura de juego
void Screen::start() {
SDL_SetRenderTarget(renderer, gameCanvas);
}
// Vuelca el contenido del renderizador en pantalla
void Screen::blit() {
// Dibuja la notificación activa sobre el gameCanvas antes de presentar
SDL_SetRenderTarget(renderer, gameCanvas);
renderNotification();
// Vuelve a dejar el renderizador en modo normal
SDL_SetRenderTarget(renderer, nullptr);
// Borra el contenido previo
SDL_SetRenderDrawColor(renderer, borderColor.r, borderColor.g, borderColor.b, 0xFF);
SDL_RenderClear(renderer);
// Copia la textura de juego en el renderizador en la posición adecuada
SDL_FRect fdest = {(float)dest.x, (float)dest.y, (float)dest.w, (float)dest.h};
SDL_RenderTexture(renderer, gameCanvas, nullptr, &fdest);
// Muestra por pantalla el renderizador
SDL_RenderPresent(renderer);
}
// ============================================================================
// Video y ventana
// ============================================================================
// Establece el modo de video
void Screen::setVideoMode(bool fullscreen) {
applyFullscreen(fullscreen);
if (fullscreen) {
applyFullscreenLayout();
} else {
applyWindowedLayout();
}
applyLogicalPresentation(fullscreen);
}
// Cambia entre pantalla completa y ventana
void Screen::toggleVideoMode() {
setVideoMode(options->videoMode == 0);
}
// Reduce el zoom de la ventana
auto Screen::decWindowZoom() -> bool {
if (options->videoMode != 0) { return false; }
const int PREV = options->windowSize;
options->windowSize = std::max(options->windowSize - 1, WINDOW_ZOOM_MIN);
if (options->windowSize == PREV) { return false; }
setVideoMode(false);
return true;
}
// Aumenta el zoom de la ventana
auto Screen::incWindowZoom() -> bool {
if (options->videoMode != 0) { return false; }
const int PREV = options->windowSize;
options->windowSize = std::min(options->windowSize + 1, WINDOW_ZOOM_MAX);
if (options->windowSize == PREV) { return false; }
setVideoMode(false);
return true;
}
// Establece el zoom de la ventana directamente
auto Screen::setWindowZoom(int zoom) -> bool {
if (options->videoMode != 0) { return false; }
if (zoom < WINDOW_ZOOM_MIN || zoom > WINDOW_ZOOM_MAX) { return false; }
if (zoom == options->windowSize) { return false; }
options->windowSize = zoom;
setVideoMode(false);
return true;
}
// Establece el escalado entero
void Screen::setIntegerScale(bool enabled) {
if (options->integerScale == enabled) { return; }
options->integerScale = enabled;
setVideoMode(options->videoMode != 0);
}
// Alterna el escalado entero
void Screen::toggleIntegerScale() {
setIntegerScale(!options->integerScale);
}
// Establece el V-Sync
void Screen::setVSync(bool enabled) {
options->vSync = enabled;
SDL_SetRenderVSync(renderer, enabled ? 1 : SDL_RENDERER_VSYNC_DISABLED);
}
// Alterna el V-Sync
void Screen::toggleVSync() {
setVSync(!options->vSync);
}
// Cambia el color del borde
void Screen::setBorderColor(color_t color) {
borderColor = color;
}
// ============================================================================
// Helpers privados de setVideoMode
// ============================================================================
// SDL_SetWindowFullscreen + visibilidad del cursor
void Screen::applyFullscreen(bool fullscreen) {
SDL_SetWindowFullscreen(window, fullscreen);
if (fullscreen) {
SDL_HideCursor();
Mouse::cursorVisible = false;
} else {
SDL_ShowCursor();
Mouse::cursorVisible = true;
Mouse::lastMouseMoveTime = SDL_GetTicks();
}
}
// Calcula windowWidth/Height/dest para el modo ventana y aplica SDL_SetWindowSize
void Screen::applyWindowedLayout() {
if (options->borderEnabled) {
windowWidth = gameCanvasWidth + borderWidth;
windowHeight = gameCanvasHeight + borderHeight;
dest = {0 + (borderWidth / 2), 0 + (borderHeight / 2), gameCanvasWidth, gameCanvasHeight};
} else {
windowWidth = gameCanvasWidth;
windowHeight = gameCanvasHeight;
dest = {0, 0, gameCanvasWidth, gameCanvasHeight};
}
#ifdef __EMSCRIPTEN__
windowWidth *= WASM_RENDER_SCALE;
windowHeight *= WASM_RENDER_SCALE;
dest.w *= WASM_RENDER_SCALE;
dest.h *= WASM_RENDER_SCALE;
#endif
// Modifica el tamaño de la ventana
SDL_SetWindowSize(window, windowWidth * options->windowSize, windowHeight * options->windowSize);
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
}
// Obtiene el tamaño de la ventana en fullscreen y calcula el rect del juego
void Screen::applyFullscreenLayout() {
SDL_GetWindowSize(window, &windowWidth, &windowHeight);
computeFullscreenGameRect();
}
// Calcula el rectángulo dest para fullscreen: integerScale / keepAspect / stretched
void Screen::computeFullscreenGameRect() {
if (options->integerScale) {
// Calcula el tamaño de la escala máxima
int scale = 0;
while (((gameCanvasWidth * (scale + 1)) <= windowWidth) && ((gameCanvasHeight * (scale + 1)) <= windowHeight)) {
scale++;
}
dest.w = gameCanvasWidth * scale;
dest.h = gameCanvasHeight * scale;
dest.x = (windowWidth - dest.w) / 2;
dest.y = (windowHeight - dest.h) / 2;
} else if (options->keepAspect) {
float ratio = (float)gameCanvasWidth / (float)gameCanvasHeight;
if ((windowWidth - gameCanvasWidth) >= (windowHeight - gameCanvasHeight)) {
dest.h = windowHeight;
dest.w = (int)((windowHeight * ratio) + 0.5f);
dest.x = (windowWidth - dest.w) / 2;
dest.y = (windowHeight - dest.h) / 2;
} else {
dest.w = windowWidth;
dest.h = (int)((windowWidth / ratio) + 0.5f);
dest.x = (windowWidth - dest.w) / 2;
dest.y = (windowHeight - dest.h) / 2;
}
} else {
dest.w = windowWidth;
dest.h = windowHeight;
dest.x = dest.y = 0;
}
}
// Aplica la logical presentation y persiste el estado en options
void Screen::applyLogicalPresentation(bool fullscreen) {
SDL_SetRenderLogicalPresentation(renderer, windowWidth, windowHeight, SDL_LOGICAL_PRESENTATION_LETTERBOX);
// Actualiza las opciones
options->videoMode = fullscreen ? SDL_WINDOW_FULLSCREEN : 0;
options->screen.windowWidth = windowWidth;
options->screen.windowHeight = windowHeight;
}
// ============================================================================
// Notificaciones
// ============================================================================
// Muestra una notificación en la línea superior durante durationMs
void Screen::notify(const std::string &text, color_t textColor, color_t outlineColor, Uint32 durationMs) {
notificationMessage = text;
notificationTextColor = textColor;
notificationOutlineColor = outlineColor;
notificationEndTime = SDL_GetTicks() + durationMs;
}
// Limpia la notificación actual
void Screen::clearNotification() {
notificationEndTime = 0;
notificationMessage.clear();
}
// Dibuja la notificación activa (si la hay) sobre el gameCanvas
void Screen::renderNotification() {
if (SDL_GetTicks() >= notificationEndTime) {
return;
}
notificationText->writeDX(TXT_CENTER | TXT_COLOR | TXT_STROKE,
gameCanvasWidth / 2,
notificationY,
notificationMessage,
1,
notificationTextColor,
1,
notificationOutlineColor);
}
// ============================================================================
// Emscripten — fix per a fullscreen/resize (veure el bloc de comentaris al
// principi del fitxer i l'anonymous namespace amb els callbacks natius).
// ============================================================================
void Screen::handleCanvasResized() {
#ifdef __EMSCRIPTEN__
// La crida a SDL_SetWindowFullscreen + SDL_SetRenderLogicalPresentation
// que fa setVideoMode és l'única manera de resincronitzar l'estat intern
// de SDL amb el canvas HTML real.
setVideoMode(options->videoMode != 0);
#endif
}
void Screen::syncFullscreenFlagFromBrowser(bool isFullscreen) {
#ifdef __EMSCRIPTEN__
options->videoMode = isFullscreen ? SDL_WINDOW_FULLSCREEN : 0;
#else
(void)isFullscreen;
#endif
}
void Screen::registerEmscriptenEventCallbacks() {
#ifdef __EMSCRIPTEN__
// IMPORTANT: NO registrem resize callback. En mòbil, fer scroll fa que el
// navegador oculti/mostri la barra d'URL, disparant un resize del DOM per
// cada scroll. Això portava a cridar setVideoMode per cada scroll, que
// re-aplicava la logical presentation i corrompia el viewport intern de SDL.
g_screen_instance = this;
emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_TRUE, onEmFullscreenChange);
emscripten_set_orientationchange_callback(nullptr, EM_TRUE, onEmOrientationChange);
#endif
}

View File

@@ -1,8 +1,9 @@
#include "utils.h" #include "utils/utils.h"
#include <stdlib.h> // for abs, free, malloc #include <algorithm> // for transform
#include <cctype> // for tolower
#include <cmath> // for round, abs #include <cmath> // for round, abs
#include <cstdlib> // for abs
// Calcula el cuadrado de la distancia entre dos puntos // Calcula el cuadrado de la distancia entre dos puntos
double distanceSquared(int x1, int y1, int x2, int y2) { double distanceSquared(int x1, int y1, int x2, int y2) {
@@ -12,7 +13,7 @@ double distanceSquared(int x1, int y1, int x2, int y2) {
} }
// Detector de colisiones entre dos circulos // Detector de colisiones entre dos circulos
bool checkCollision(circle_t &a, circle_t &b) { bool checkCollision(const circle_t &a, const circle_t &b) {
// Calcula el radio total al cuadrado // Calcula el radio total al cuadrado
int totalRadiusSquared = a.r + b.r; int totalRadiusSquared = a.r + b.r;
totalRadiusSquared = totalRadiusSquared * totalRadiusSquared; totalRadiusSquared = totalRadiusSquared * totalRadiusSquared;
@@ -28,7 +29,7 @@ bool checkCollision(circle_t &a, circle_t &b) {
} }
// Detector de colisiones entre un circulo y un rectangulo // Detector de colisiones entre un circulo y un rectangulo
bool checkCollision(circle_t &a, SDL_Rect &b) { bool checkCollision(const circle_t &a, const SDL_Rect &b) {
// Closest point on collision box // Closest point on collision box
int cX, cY; int cX, cY;
@@ -61,7 +62,7 @@ bool checkCollision(circle_t &a, SDL_Rect &b) {
} }
// Detector de colisiones entre dos rectangulos // Detector de colisiones entre dos rectangulos
bool checkCollision(SDL_Rect &a, SDL_Rect &b) { bool checkCollision(const SDL_Rect &a, const SDL_Rect &b) {
// Calcula las caras del rectangulo a // Calcula las caras del rectangulo a
const int leftA = a.x; const int leftA = a.x;
const int rightA = a.x + a.w; const int rightA = a.x + a.w;
@@ -96,7 +97,7 @@ bool checkCollision(SDL_Rect &a, SDL_Rect &b) {
} }
// Detector de colisiones entre un punto y un rectangulo // Detector de colisiones entre un punto y un rectangulo
bool checkCollision(SDL_Point &p, SDL_Rect &r) { bool checkCollision(const SDL_Point &p, const SDL_Rect &r) {
// Comprueba si el punto está a la izquierda del rectangulo // Comprueba si el punto está a la izquierda del rectangulo
if (p.x < r.x) { if (p.x < r.x) {
return false; return false;
@@ -122,7 +123,7 @@ bool checkCollision(SDL_Point &p, SDL_Rect &r) {
} }
// Detector de colisiones entre una linea horizontal y un rectangulo // Detector de colisiones entre una linea horizontal y un rectangulo
bool checkCollision(h_line_t &l, SDL_Rect &r) { bool checkCollision(const h_line_t &l, const SDL_Rect &r) {
// Comprueba si la linea esta por encima del rectangulo // Comprueba si la linea esta por encima del rectangulo
if (l.y < r.y) { if (l.y < r.y) {
return false; return false;
@@ -148,7 +149,7 @@ bool checkCollision(h_line_t &l, SDL_Rect &r) {
} }
// Detector de colisiones entre una linea vertical y un rectangulo // Detector de colisiones entre una linea vertical y un rectangulo
bool checkCollision(v_line_t &l, SDL_Rect &r) { bool checkCollision(const v_line_t &l, const SDL_Rect &r) {
// Comprueba si la linea esta por la izquierda del rectangulo // Comprueba si la linea esta por la izquierda del rectangulo
if (l.x < r.x) { if (l.x < r.x) {
return false; return false;
@@ -174,7 +175,7 @@ bool checkCollision(v_line_t &l, SDL_Rect &r) {
} }
// Detector de colisiones entre una linea horizontal y un punto // Detector de colisiones entre una linea horizontal y un punto
bool checkCollision(h_line_t &l, SDL_Point &p) { bool checkCollision(const h_line_t &l, const SDL_Point &p) {
// Comprueba si el punto esta sobre la linea // Comprueba si el punto esta sobre la linea
if (p.y > l.y) { if (p.y > l.y) {
return false; return false;
@@ -200,7 +201,7 @@ bool checkCollision(h_line_t &l, SDL_Point &p) {
} }
// Detector de colisiones entre dos lineas // Detector de colisiones entre dos lineas
SDL_Point checkCollision(line_t &l1, line_t &l2) { SDL_Point checkCollision(const line_t &l1, const line_t &l2) {
const float x1 = l1.x1; const float x1 = l1.x1;
const float y1 = l1.y1; const float y1 = l1.y1;
const float x2 = l1.x2; const float x2 = l1.x2;
@@ -227,7 +228,7 @@ SDL_Point checkCollision(line_t &l1, line_t &l2) {
} }
// Detector de colisiones entre dos lineas // Detector de colisiones entre dos lineas
SDL_Point checkCollision(d_line_t &l1, v_line_t &l2) { SDL_Point checkCollision(const d_line_t &l1, const v_line_t &l2) {
const float x1 = l1.x1; const float x1 = l1.x1;
const float y1 = l1.y1; const float y1 = l1.y1;
const float x2 = l1.x2; const float x2 = l1.x2;
@@ -290,7 +291,7 @@ void normalizeLine(d_line_t &l) {
} }
// Detector de colisiones entre un punto y una linea diagonal // Detector de colisiones entre un punto y una linea diagonal
bool checkCollision(SDL_Point &p, d_line_t &l) { bool checkCollision(const SDL_Point &p, const d_line_t &l) {
// Comprueba si el punto está en alineado con la linea // Comprueba si el punto está en alineado con la linea
if (abs(p.x - l.x1) != abs(p.y - l.y1)) { if (abs(p.x - l.x1) != abs(p.y - l.y1)) {
return false; return false;
@@ -330,7 +331,7 @@ bool checkCollision(SDL_Point &p, d_line_t &l) {
} }
// Devuelve un color_t a partir de un string // Devuelve un color_t a partir de un string
color_t stringToColor(palette_e pal, std::string str) { color_t stringToColor(palette_e pal, const std::string &str) {
if (pal == p_zxspectrum) { if (pal == p_zxspectrum) {
if (str == "black") { if (str == "black") {
return {0x00, 0x00, 0x00}; return {0x00, 0x00, 0x00};
@@ -467,7 +468,7 @@ color_t stringToColor(palette_e pal, std::string str) {
} }
// Convierte una cadena en un valor booleano // Convierte una cadena en un valor booleano
bool stringToBool(std::string str) { bool stringToBool(const std::string &str) {
if (str == "true") { if (str == "true") {
return true; return true;
} else { } else {
@@ -485,15 +486,9 @@ std::string boolToString(bool value) {
} }
// Convierte una cadena a minusculas // Convierte una cadena a minusculas
std::string toLower(std::string str) { std::string toLower(const std::string &str) {
const char *original = str.c_str(); std::string result;
char *lower = (char *)malloc(str.size() + 1); result.reserve(str.size());
for (int i = 0; i < (int)str.size(); ++i) { std::transform(str.begin(), str.end(), std::back_inserter(result), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
char c = original[i]; return result;
lower[i] = (c >= 65 && c <= 90) ? c + 32 : c;
}
lower[str.size()] = 0;
std::string nova(lower);
free(lower);
return nova;
} }

View File

@@ -82,80 +82,50 @@ struct input_t {
Uint8 deviceType; // Tipo de dispositivo (teclado o mando) Uint8 deviceType; // Tipo de dispositivo (teclado o mando)
}; };
// Estructura con opciones de la pantalla
struct op_screen_t {
int windowWidth; // Ancho de la ventana
int windowHeight; // Alto de la ventana
};
// Estructura con todas las opciones de configuración del programa
struct options_t {
Uint8 difficulty; // Dificultad del juego
Uint8 playerSelected; // Jugador seleccionado para el modo 1P
std::vector<input_t> input; // Modo de control (teclado o mando)
Uint8 language; // Idioma usado en el juego
Uint32 videoMode; // Contiene el valor del modo de pantalla completa
int windowSize; // Contiene el valor por el que se multiplica el tamaño de la ventana
Uint32 filter; // Filtro usado para el escalado de la imagen
bool vSync; // Indica si se quiere usar vsync o no
int gameWidth; // Ancho de la resolucion nativa del juego
int gameHeight; // Alto de la resolucion nativa del juego
bool integerScale; // Indica si el escalado de la imagen ha de ser entero en el modo a pantalla completa
bool keepAspect; // Indica si se ha de mantener la relación de aspecto al poner el modo a pantalla completa
bool borderEnabled; // Indica si ha de mostrar el borde en el modo de ventana
int borderWidth; // Cantidad de pixels que se añade en el borde de la ventana
int borderHeight; // Cantidad de pixels que se añade en el borde de la ventana
palette_e palette; // Paleta de colores a usar en el juego
bool console; // Indica si ha de mostrar información por la consola de texto
op_screen_t screen; // Opciones relativas a la clase screen
};
// Calcula el cuadrado de la distancia entre dos puntos // Calcula el cuadrado de la distancia entre dos puntos
double distanceSquared(int x1, int y1, int x2, int y2); double distanceSquared(int x1, int y1, int x2, int y2);
// Detector de colisiones entre dos circulos // Detector de colisiones entre dos circulos
bool checkCollision(circle_t &a, circle_t &b); bool checkCollision(const circle_t &a, const circle_t &b);
// Detector de colisiones entre un circulo y un rectangulo // Detector de colisiones entre un circulo y un rectangulo
bool checkCollision(circle_t &a, SDL_Rect &b); bool checkCollision(const circle_t &a, const SDL_Rect &b);
// Detector de colisiones entre un dos rectangulos // Detector de colisiones entre un dos rectangulos
bool checkCollision(SDL_Rect &a, SDL_Rect &b); bool checkCollision(const SDL_Rect &a, const SDL_Rect &b);
// Detector de colisiones entre un punto y un rectangulo // Detector de colisiones entre un punto y un rectangulo
bool checkCollision(SDL_Point &p, SDL_Rect &r); bool checkCollision(const SDL_Point &p, const SDL_Rect &r);
// Detector de colisiones entre una linea horizontal y un rectangulo // Detector de colisiones entre una linea horizontal y un rectangulo
bool checkCollision(h_line_t &l, SDL_Rect &r); bool checkCollision(const h_line_t &l, const SDL_Rect &r);
// Detector de colisiones entre una linea vertical y un rectangulo // Detector de colisiones entre una linea vertical y un rectangulo
bool checkCollision(v_line_t &l, SDL_Rect &r); bool checkCollision(const v_line_t &l, const SDL_Rect &r);
// Detector de colisiones entre una linea horizontal y un punto // Detector de colisiones entre una linea horizontal y un punto
bool checkCollision(h_line_t &l, SDL_Point &p); bool checkCollision(const h_line_t &l, const SDL_Point &p);
// Detector de colisiones entre dos lineas // Detector de colisiones entre dos lineas
SDL_Point checkCollision(line_t &l1, line_t &l2); SDL_Point checkCollision(const line_t &l1, const line_t &l2);
// Detector de colisiones entre dos lineas // Detector de colisiones entre dos lineas
SDL_Point checkCollision(d_line_t &l1, v_line_t &l2); SDL_Point checkCollision(const d_line_t &l1, const v_line_t &l2);
// Detector de colisiones entre un punto y una linea diagonal // Detector de colisiones entre un punto y una linea diagonal
bool checkCollision(SDL_Point &p, d_line_t &l); bool checkCollision(const SDL_Point &p, const d_line_t &l);
// Normaliza una linea diagonal // Normaliza una linea diagonal
void normalizeLine(d_line_t &l); void normalizeLine(d_line_t &l);
// Devuelve un color_t a partir de un string // Devuelve un color_t a partir de un string
color_t stringToColor(palette_e pal, std::string str); color_t stringToColor(palette_e pal, const std::string &str);
// Convierte una cadena en un valor booleano // Convierte una cadena en un valor booleano
bool stringToBool(std::string str); bool stringToBool(const std::string &str);
// Convierte un valor booleano en una cadena // Convierte un valor booleano en una cadena
std::string boolToString(bool value); std::string boolToString(bool value);
// Convierte una cadena a minusculas // Convierte una cadena a minusculas
std::string toLower(std::string str); std::string toLower(const std::string &str);

View File

@@ -1,7 +1,7 @@
#include <filesystem> #include <filesystem>
#include <iostream> #include <iostream>
#include "resource_pack.h" #include "core/resources/resource_pack.h"
static constexpr const char* APP_NAME = "Coffee Crisis"; static constexpr const char* APP_NAME = "Coffee Crisis";

View File

@@ -0,0 +1,57 @@
# compile_shader.cmake
# Compila un shader GLSL a header C++ embebible con datos SPIR-V.
# Funciona en Windows, Linux y macOS sin bash ni herramientas Unix.
#
# Variables requeridas (pasar con -D al invocar cmake -P):
# GLSLC - ruta al ejecutable glslc
# SRC - archivo fuente GLSL
# OUT_H - archivo header de salida
# VAR - nombre base de la variable C++ (e.g. "postfx_vert_spv")
# STAGE - (opcional) stage explícito para glslc (e.g. "fragment")
cmake_minimum_required(VERSION 3.10)
get_filename_component(SRC_NAME "${SRC}" NAME_WE)
get_filename_component(OUT_DIR "${OUT_H}" DIRECTORY)
set(OUT_SPV "${OUT_DIR}/${SRC_NAME}.spv")
# Compilar GLSL → SPIR-V
if(DEFINED STAGE AND NOT STAGE STREQUAL "")
execute_process(
COMMAND "${GLSLC}" "-fshader-stage=${STAGE}" "${SRC}" -o "${OUT_SPV}"
RESULT_VARIABLE RESULT
ERROR_VARIABLE ERROR_MSG
)
else()
execute_process(
COMMAND "${GLSLC}" "${SRC}" -o "${OUT_SPV}"
RESULT_VARIABLE RESULT
ERROR_VARIABLE ERROR_MSG
)
endif()
if(NOT RESULT EQUAL 0)
message(FATAL_ERROR "glslc falló compilando ${SRC}:\n${ERROR_MSG}")
endif()
# Leer binario SPIR-V como cadena hexadecimal
file(READ "${OUT_SPV}" SPV_HEX HEX)
file(REMOVE "${OUT_SPV}")
string(LENGTH "${SPV_HEX}" HEX_LEN)
math(EXPR BYTE_COUNT "${HEX_LEN} / 2")
# Convertir a array C++ con formato " 0xXX,\n" por byte
string(REGEX REPLACE "([0-9a-f][0-9a-f])" " 0x\\1,\n" SPV_BYTES "${SPV_HEX}")
# Eliminar la última coma y sustituir por el "}" de cierre
string(REGEX REPLACE ",\n$" "}" SPV_BYTES "${SPV_BYTES}")
# Escribir el header
file(WRITE "${OUT_H}"
"#pragma once\n"
"#include <cstddef>\n"
"#include <cstdint>\n"
"static const uint8_t k${VAR}[] = {\n"
"${SPV_BYTES};\n"
"static const size_t k${VAR}_size = ${BYTE_COUNT};\n"
)