Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd513f3bdb | ||
|
|
ebb15d32be | ||
|
|
53174f11f9 | ||
|
|
da923ae421 | ||
|
|
ef496bd971 | ||
|
|
3db0ed4457 | ||
|
|
0c6bebe8dc | ||
|
|
a8ccb3fae1 | ||
|
|
774874deb0 | ||
|
|
24afdef7cd | ||
|
|
f29cc7318a | ||
|
|
7a859c1e44 | ||
|
|
822bed0748 | ||
|
|
d7db02bafd | ||
|
|
b55473ae0b | ||
|
|
35d3b359eb | ||
|
|
b81a453b32 | ||
|
|
7a1261202c | ||
|
|
f0c118e6fa | ||
|
|
bc5a6abf53 | ||
|
|
4daeb0e15c | ||
|
|
b6403e8de5 | ||
|
|
0e8a195ddd | ||
|
|
80140434e7 | ||
|
|
fd3da8cb99 | ||
|
|
885af944c5 | ||
|
|
2e41815d3c | ||
|
|
125cdd0c74 | ||
|
|
5f206e03c8 |
No files matched your search
+5
-2
@@ -65,6 +65,7 @@ set(ENTITY_SOURCES
|
||||
set(APP_SOURCES
|
||||
source/main.cpp
|
||||
source/core/system/director.cpp
|
||||
source/core/system/relaunch.cpp
|
||||
source/core/audio/audio.cpp
|
||||
source/core/audio/audio_adapter.cpp
|
||||
source/core/audio/audio_effects.cpp
|
||||
@@ -74,7 +75,6 @@ set(APP_SOURCES
|
||||
source/core/ui/ui_config.cpp
|
||||
source/core/input/key_config.cpp
|
||||
source/core/input/global_inputs.cpp
|
||||
source/core/input/input_state.cpp
|
||||
source/core/input/gamepad_manager.cpp
|
||||
source/core/input/gamepad_bindings.cpp
|
||||
source/core/input/define_buttons.cpp
|
||||
@@ -131,6 +131,7 @@ set(APP_SOURCES
|
||||
# al TilemapWriter. Només Debug.
|
||||
source/game/map/map_writer.cpp
|
||||
source/game/map/paint_layer.cpp
|
||||
source/game/map/room_yaml.cpp
|
||||
source/game/levels/level_manager.cpp
|
||||
# Mòdul editor (només activa contingut en Debug via `#ifdef _DEBUG`).
|
||||
# En Release els fitxers compilen a object buit i l'enllaçador no
|
||||
@@ -204,7 +205,7 @@ add_executable(${PROJECT_NAME} ${APP_SOURCES})
|
||||
set_source_files_properties(
|
||||
source/external/stb_image_write_impl.cpp
|
||||
source/external/stb_vorbis_impl.cpp
|
||||
PROPERTIES COMPILE_OPTIONS "-Wno-missing-field-initializers;-Wno-deprecated-declarations"
|
||||
PROPERTIES COMPILE_OPTIONS "-Wno-missing-field-initializers;-Wno-deprecated-declarations;-Wno-tautological-compare"
|
||||
)
|
||||
|
||||
if(NOT APPLE AND GLSLC_EXE)
|
||||
@@ -374,6 +375,7 @@ if(BUILD_TESTS)
|
||||
source/core/resources/resource_helper.cpp
|
||||
source/core/resources/resource_pack.cpp
|
||||
source/game/map/map.cpp
|
||||
source/game/map/room_yaml.cpp
|
||||
source/utils/log.cpp
|
||||
${ENTITY_SOURCES_BASE}
|
||||
)
|
||||
@@ -405,6 +407,7 @@ if(BUILD_TESTS)
|
||||
source/core/resources/resource_pack.cpp
|
||||
source/game/gameplay_config.cpp
|
||||
source/game/map/map.cpp
|
||||
source/game/map/room_yaml.cpp
|
||||
source/utils/log.cpp
|
||||
${ENTITY_SOURCES_BASE}
|
||||
source/game/entities/concrete/whip_attack.cpp
|
||||
|
||||
@@ -27,8 +27,15 @@ ifeq ($(OS),Windows_NT)
|
||||
else
|
||||
NULDEV := /dev/null
|
||||
JOBS ?= $(shell nproc 2>/dev/null || echo 4)
|
||||
# Si Ninja està al PATH, l'usem (més ràpid i paral·lelitza millor).
|
||||
# Si no, deixem que CMake usi el generador per defecte (Unix Makefiles).
|
||||
HAS_NINJA := $(shell ninja --version 2>/dev/null)
|
||||
ifneq ($(HAS_NINJA),)
|
||||
CMAKE_GEN := -G "Ninja"
|
||||
else
|
||||
CMAKE_GEN :=
|
||||
endif
|
||||
endif
|
||||
|
||||
# VERSION (única font de veritat: source/utils/defines.hpp).
|
||||
# Lazy (=): només es calcula quan s'invoca un target que la usa
|
||||
@@ -84,7 +91,7 @@ else
|
||||
MKDIR := mkdir -p
|
||||
endif
|
||||
|
||||
.PHONY: all debug release _windows_release _macos_release _linux_release run run-debug clean rebuild show-version pack controllerdb format format-check tidy tidy-fix cppcheck hooks-install help
|
||||
.PHONY: all debug release _windows-release _macos-release _linux-release run run-debug clean rebuild show-version pack controllerdb format format-check tidy tidy-fix cppcheck hooks-install help
|
||||
|
||||
# ==============================================================================
|
||||
# COMPILACIÓ
|
||||
@@ -115,19 +122,19 @@ run-debug: debug
|
||||
# resultat a $(DIST_DIR)/.
|
||||
release:
|
||||
ifeq ($(OS),Windows_NT)
|
||||
@"$(MAKE)" _windows_release
|
||||
@"$(MAKE)" _windows-release
|
||||
else
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
@$(MAKE) _macos_release
|
||||
@$(MAKE) _macos-release
|
||||
else
|
||||
@$(MAKE) _linux_release
|
||||
@$(MAKE) _linux-release
|
||||
endif
|
||||
endif
|
||||
|
||||
# ==============================================================================
|
||||
# RELEASE — Linux (.tar.gz)
|
||||
# ==============================================================================
|
||||
_linux_release:
|
||||
_linux-release:
|
||||
@echo "Creando release para Linux - Version: $(VERSION)"
|
||||
|
||||
# Compila Release (genera shaders, resources.pack i binari)
|
||||
@@ -158,7 +165,7 @@ _linux_release:
|
||||
# ==============================================================================
|
||||
# RELEASE — Windows (.zip)
|
||||
# ==============================================================================
|
||||
_windows_release:
|
||||
_windows-release:
|
||||
@echo off
|
||||
@echo Creando release para Windows - Version: $(VERSION)
|
||||
|
||||
@@ -191,7 +198,7 @@ _windows_release:
|
||||
# ==============================================================================
|
||||
# RELEASE — macOS (.dmg per Intel + Apple Silicon)
|
||||
# ==============================================================================
|
||||
_macos_release:
|
||||
_macos-release:
|
||||
@echo "Creando release para macOS - Version: $(VERSION)"
|
||||
|
||||
# Verificar/instal·lar create-dmg si cal
|
||||
|
||||
+43
-7
@@ -9,13 +9,6 @@ colors:
|
||||
notification_bg: 64
|
||||
exit_text: 71
|
||||
exit_bg: 2
|
||||
menu_bg: 2
|
||||
menu_border: 232
|
||||
menu_title: 237
|
||||
menu_label: 58
|
||||
menu_value: 232
|
||||
menu_cursor: 71
|
||||
menu_disabled: 16
|
||||
hud_bg: 9
|
||||
hud_flash: 63
|
||||
# Rètol de transició de sala (ROOM_INTRO i ROOM_OUTRO). Independents
|
||||
@@ -27,6 +20,49 @@ colors:
|
||||
room_text: 55 # or brillant (#FFDA00, = hud_center.value)
|
||||
room_text_outline: 10 # marró cremat (#482400, = hud_center.shadow)
|
||||
|
||||
# Tema actiu del menú de servei. Ha de coincidir amb una clau de
|
||||
# `menu_themes`; si manca o no existeix, cada slot cau al default
|
||||
# compilat a Defaults::UI (= tema "classic"). Canviar de tema requereix
|
||||
# reiniciar el joc — la càrrega de paleta es fa només en arrencar.
|
||||
menu_theme: egyptian
|
||||
|
||||
# Conjunts de colors del menú de servei. Cada tema defineix els 7 slots
|
||||
# (sense prefix `menu_` perquè ja som dins del bloc del tema). Si manca
|
||||
# alguna clau dins d'un tema, cau al default compilat. Per afegir un
|
||||
# tema nou, només cal una nova entrada aquí i posar-ne el nom a
|
||||
# `menu_theme`.
|
||||
menu_themes:
|
||||
# Tema històric (defaults compilats). Cian + verd llima + magenta —
|
||||
# paleta arcade clàssica, freda i amb molt contrast.
|
||||
classic:
|
||||
bg: 2 # marró-vermell fosc (#480000)
|
||||
border: 232 # cian brillant (#00B6FF)
|
||||
title: 237 # lavanda (#B6B6FF)
|
||||
title_outline: 64 # navy fosc (#0000AA) — halo fred del títol
|
||||
label: 58 # verd llima (#48FF00)
|
||||
label_outline: 17 # verd molt fosc (#244800) — bord same-family del label
|
||||
value: 232 # cian brillant (#00B6FF) — coincideix amb el border
|
||||
value_outline: 64 # navy fosc (#0000AA) — halo cian
|
||||
cursor: 71 # magenta-vermell (#FF0055)
|
||||
cursor_outline: 1 # granat fosc (#240000) — halo càlid del cursor
|
||||
disabled: 16 # verd fosc (#004800)
|
||||
# Tema "egípci": família or/ambre/marró coherent amb el HUD i el
|
||||
# rètol de transició de sala (room_text). El bg comparteix to amb
|
||||
# `room_text_outline` i `hud_center.shadow` perquè el menú "creixi"
|
||||
# del mateix món visual que el joc.
|
||||
egyptian:
|
||||
bg: 10 # marró cremat (#482400)
|
||||
border: 55 # or brillant (#FFDA00)
|
||||
title: 127 # groc llimona viu (#FFFF55)
|
||||
title_outline: 19 # oliva fosc (#6D4800) — halo càlid del groc
|
||||
label: 47 # ambre (#FFB600)
|
||||
label_outline: 11 # marró (#6D2400) — bord càlid del label
|
||||
value: 191 # crema clar (#FFFFAA)
|
||||
value_outline: 19 # oliva fosc (#6D4800) — recolza el crema
|
||||
cursor: 23 # vermilló (#FF4800)
|
||||
cursor_outline: 9 # marró molt fosc (#240000) — halo profund del cursor
|
||||
disabled: 19 # oliva fosc (#6D4800)
|
||||
|
||||
timings:
|
||||
notification_ms: 4000
|
||||
exit_ms: 4000
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# Configuració compartida per totes les sales de la piràmide 1. Cada sala
|
||||
# (r1..r6) hereta aquests camps via el merge recursiu de `RoomYaml::loadMerged`:
|
||||
# el YAML de sala només declara el que canvia (room, geometry, data, entities,
|
||||
# i opcionalment overrides puntuals de `lighting`).
|
||||
|
||||
pyramid: 1
|
||||
tileset: data/tilesets/pyramid_1.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Il·luminació per defecte: additive amb halos càlids subtils a les
|
||||
# antorxes. Les sales que vulguin l'estil "fosc amb forats" (subtractive)
|
||||
# poden redeclarar `lighting:` sencer; el merge no fusiona "additive"
|
||||
# amb "subtractive", la sala guanya.
|
||||
lighting:
|
||||
mode: additive
|
||||
level: 1.0
|
||||
emitters:
|
||||
torches:
|
||||
radius: 23
|
||||
color: [255, 170, 60]
|
||||
flicker_params:
|
||||
wave_amplitude_ratio: 0.04
|
||||
wave_frequency_hz: 3.0
|
||||
noise_amplitude_ratio: 0.025
|
||||
noise_frequency_hz: 0
|
||||
players: false
|
||||
mummies: false
|
||||
@@ -1,60 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_1.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 1
|
||||
room: 2
|
||||
tileset: data/tilesets/pyramid_1.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Banc de proves del mode `additive` d'il·luminació. La sala es veu
|
||||
# "normal" (level 1.0, el GIF intacte fora del cercle de les torxes)
|
||||
# i només les antorxes hi sumen un halo càlid suau. Player i mummies
|
||||
# desactivats explícitament perquè aquí no volem cap halo al voltant
|
||||
# d'actors — el toc cromàtic és només l'antorxa. La corba additiva
|
||||
# (intensitats baixes a propòsit perquè additive satura ràpid) viu a
|
||||
# `gradient_additive:` de data/config/lighting.yaml.
|
||||
lighting:
|
||||
mode: additive
|
||||
level: 1.0
|
||||
emitters:
|
||||
torches:
|
||||
radius: 20
|
||||
color: [255, 170, 60]
|
||||
# Valors default del flicker explicitats per anar-los ajustant
|
||||
# in-situ. Coincideixen amb els del global a data/config/lighting.yaml;
|
||||
# baixar wave_frequency_hz fa el "palpitar" més lent, baixar les
|
||||
# ratios fa l'oscil·lació més subtil. Qualsevol camp es pot
|
||||
# eliminar i caurà al valor del global.
|
||||
flicker_params:
|
||||
wave_amplitude_ratio: 0.04
|
||||
wave_frequency_hz: 3.0
|
||||
noise_amplitude_ratio: 0.025
|
||||
noise_frequency_hz: 3.0
|
||||
players: false
|
||||
mummies: false
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
+12
-30
@@ -1,19 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_1.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 1
|
||||
room: 1
|
||||
tileset: data/tilesets/pyramid_1.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Il·luminació de la sala. Bloc opcional; si manca, la sala queda
|
||||
# totalment il·luminada (1.0) i tots els tipus d'entitat emeten amb els
|
||||
@@ -37,24 +22,21 @@ origin: [-8, 0]
|
||||
# (`light_level:` plà a l'arrel) també segueix sent vàlida per
|
||||
# retrocompatibilitat.
|
||||
lighting:
|
||||
# Sala fosca de proves. La piràmide declara additive per defecte; aquí
|
||||
# tornem a subtractive perquè volem la dinàmica "negre amb forats de
|
||||
# llum". `level: 0.15` és l'atenuació base de la sala fora dels emissors.
|
||||
mode: subtractive
|
||||
level: 0.15
|
||||
emitters:
|
||||
players: true
|
||||
mummies: false
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
torches:
|
||||
radius: 40
|
||||
color: [255, 170, 60]
|
||||
flicker_params:
|
||||
wave_amplitude_ratio: 0.05
|
||||
wave_frequency_hz: 3.0
|
||||
noise_amplitude_ratio: 0.025
|
||||
noise_frequency_hz: 7.0
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_1.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 1
|
||||
room: 3
|
||||
tileset: data/tilesets/pyramid_1.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_1.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 1
|
||||
room: 4
|
||||
tileset: data/tilesets/pyramid_1.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_1.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 1
|
||||
room: 5
|
||||
tileset: data/tilesets/pyramid_1.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_1.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 1
|
||||
room: 6
|
||||
tileset: data/tilesets/pyramid_1.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# Configuració compartida per totes les sales de la piràmide 2. Cada sala
|
||||
# (r1..r6) hereta aquests camps via el merge recursiu de `RoomYaml::loadMerged`:
|
||||
# el YAML de sala només declara el que canvia (room, geometry, data, entities,
|
||||
# i opcionalment overrides puntuals de `lighting`).
|
||||
|
||||
pyramid: 2
|
||||
tileset: data/tilesets/pyramid_2.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Il·luminació per defecte: additive amb halos càlids subtils a les
|
||||
# antorxes. Les sales que vulguin l'estil "fosc amb forats" (subtractive)
|
||||
# poden redeclarar `lighting:` sencer; el merge no fusiona "additive"
|
||||
# amb "subtractive", la sala guanya.
|
||||
lighting:
|
||||
mode: additive
|
||||
level: 1.0
|
||||
emitters:
|
||||
torches:
|
||||
radius: 23
|
||||
color: [255, 170, 60]
|
||||
flicker_params:
|
||||
wave_amplitude_ratio: 0.04
|
||||
wave_frequency_hz: 3.0
|
||||
noise_amplitude_ratio: 0.025
|
||||
noise_frequency_hz: 0
|
||||
players: false
|
||||
mummies: false
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_2.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 2
|
||||
room: 1
|
||||
tileset: data/tilesets/pyramid_2.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_2.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 2
|
||||
room: 2
|
||||
tileset: data/tilesets/pyramid_2.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_2.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 2
|
||||
room: 3
|
||||
tileset: data/tilesets/pyramid_2.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_2.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 2
|
||||
room: 4
|
||||
tileset: data/tilesets/pyramid_2.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_2.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 2
|
||||
room: 5
|
||||
tileset: data/tilesets/pyramid_2.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_2.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 2
|
||||
room: 6
|
||||
tileset: data/tilesets/pyramid_2.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# Configuració compartida per totes les sales de la piràmide 3. Cada sala
|
||||
# (r1..r6) hereta aquests camps via el merge recursiu de `RoomYaml::loadMerged`:
|
||||
# el YAML de sala només declara el que canvia (room, geometry, data, entities,
|
||||
# i opcionalment overrides puntuals de `lighting`).
|
||||
|
||||
pyramid: 3
|
||||
tileset: data/tilesets/pyramid_3.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Il·luminació per defecte: additive amb halos càlids subtils a les
|
||||
# antorxes. Les sales que vulguin l'estil "fosc amb forats" (subtractive)
|
||||
# poden redeclarar `lighting:` sencer; el merge no fusiona "additive"
|
||||
# amb "subtractive", la sala guanya.
|
||||
lighting:
|
||||
mode: additive
|
||||
level: 1.0
|
||||
emitters:
|
||||
torches:
|
||||
radius: 23
|
||||
color: [255, 170, 60]
|
||||
flicker_params:
|
||||
wave_amplitude_ratio: 0.04
|
||||
wave_frequency_hz: 3.0
|
||||
noise_amplitude_ratio: 0.025
|
||||
noise_frequency_hz: 0
|
||||
players: false
|
||||
mummies: false
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_3.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 3
|
||||
room: 1
|
||||
tileset: data/tilesets/pyramid_3.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_3.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 3
|
||||
room: 2
|
||||
tileset: data/tilesets/pyramid_3.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_3.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 3
|
||||
room: 3
|
||||
tileset: data/tilesets/pyramid_3.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_3.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 3
|
||||
room: 4
|
||||
tileset: data/tilesets/pyramid_3.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_3.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 3
|
||||
room: 5
|
||||
tileset: data/tilesets/pyramid_3.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_3.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 3
|
||||
room: 6
|
||||
tileset: data/tilesets/pyramid_3.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# Configuració compartida per totes les sales de la piràmide 4. Cada sala
|
||||
# (r1..r6) hereta aquests camps via el merge recursiu de `RoomYaml::loadMerged`:
|
||||
# el YAML de sala només declara el que canvia (room, geometry, data, entities,
|
||||
# i opcionalment overrides puntuals de `lighting`).
|
||||
|
||||
pyramid: 4
|
||||
tileset: data/tilesets/pyramid_4.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Il·luminació per defecte: additive amb halos càlids subtils a les
|
||||
# antorxes. Les sales que vulguin l'estil "fosc amb forats" (subtractive)
|
||||
# poden redeclarar `lighting:` sencer; el merge no fusiona "additive"
|
||||
# amb "subtractive", la sala guanya.
|
||||
lighting:
|
||||
mode: additive
|
||||
level: 1.0
|
||||
emitters:
|
||||
torches:
|
||||
radius: 23
|
||||
color: [255, 170, 60]
|
||||
flicker_params:
|
||||
wave_amplitude_ratio: 0.04
|
||||
wave_frequency_hz: 3.0
|
||||
noise_amplitude_ratio: 0.025
|
||||
noise_frequency_hz: 0
|
||||
players: false
|
||||
mummies: false
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_4.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 4
|
||||
room: 1
|
||||
tileset: data/tilesets/pyramid_4.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_4.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 4
|
||||
room: 2
|
||||
tileset: data/tilesets/pyramid_4.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_4.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 4
|
||||
room: 3
|
||||
tileset: data/tilesets/pyramid_4.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_4.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 4
|
||||
room: 4
|
||||
tileset: data/tilesets/pyramid_4.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_4.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 4
|
||||
room: 5
|
||||
tileset: data/tilesets/pyramid_4.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_4.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 4
|
||||
room: 6
|
||||
tileset: data/tilesets/pyramid_4.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# Configuració compartida per totes les sales de la piràmide 5. Cada sala
|
||||
# (r1..r6) hereta aquests camps via el merge recursiu de `RoomYaml::loadMerged`:
|
||||
# el YAML de sala només declara el que canvia (room, geometry, data, entities,
|
||||
# i opcionalment overrides puntuals de `lighting`).
|
||||
|
||||
pyramid: 5
|
||||
tileset: data/tilesets/pyramid_5.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Il·luminació per defecte: additive amb halos càlids subtils a les
|
||||
# antorxes. Les sales que vulguin l'estil "fosc amb forats" (subtractive)
|
||||
# poden redeclarar `lighting:` sencer; el merge no fusiona "additive"
|
||||
# amb "subtractive", la sala guanya.
|
||||
lighting:
|
||||
mode: additive
|
||||
level: 1.0
|
||||
emitters:
|
||||
torches:
|
||||
radius: 23
|
||||
color: [255, 170, 60]
|
||||
flicker_params:
|
||||
wave_amplitude_ratio: 0.04
|
||||
wave_frequency_hz: 3.0
|
||||
noise_amplitude_ratio: 0.025
|
||||
noise_frequency_hz: 0
|
||||
players: false
|
||||
mummies: false
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_5.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 5
|
||||
room: 1
|
||||
tileset: data/tilesets/pyramid_5.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_5.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 5
|
||||
room: 2
|
||||
tileset: data/tilesets/pyramid_5.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_5.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 5
|
||||
room: 3
|
||||
tileset: data/tilesets/pyramid_5.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_5.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 5
|
||||
room: 4
|
||||
tileset: data/tilesets/pyramid_5.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_5.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 5
|
||||
room: 5
|
||||
tileset: data/tilesets/pyramid_5.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,33 +1,4 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_5.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
pyramid: 5
|
||||
room: 6
|
||||
tileset: data/tilesets/pyramid_5.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
||||
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||
#
|
||||
# Format del tilemap:
|
||||
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||
|
||||
tileset: data/tilesets/tomb.gif
|
||||
tile_size: 16
|
||||
origin: [-8, 0]
|
||||
|
||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||
# per desactivar el pintat per a aquest jugador.
|
||||
player1_paint_tile: 0
|
||||
player2_paint_tile: 1
|
||||
|
||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||
# s'anul·len (sense push, sense moviment).
|
||||
push_factor: 0.30
|
||||
|
||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||
# la seua pròpia forma sense recompilar.
|
||||
geometry:
|
||||
grid_cols: 4
|
||||
grid_rows: 5
|
||||
sarc_w: 48
|
||||
sarc_h: 16
|
||||
pitch_x: 64
|
||||
pitch_y: 32
|
||||
hud_h: 24
|
||||
back_wall_h: 24
|
||||
side_wall_w: 24
|
||||
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||
door_col: 10
|
||||
|
||||
data:
|
||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
||||
|
||||
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||
#
|
||||
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||
# superior esquerre per verificar render d'una entitat decorativa.
|
||||
entities:
|
||||
- type: torch
|
||||
position: [4, 2]
|
||||
- type: torch
|
||||
position: [8, 2]
|
||||
- type: torch
|
||||
position: [12, 2]
|
||||
- type: torch
|
||||
position: [16, 2]
|
||||
- type: sarcophagus
|
||||
sarc_position: [0, 0]
|
||||
- type: sarcophagus
|
||||
sarc_position: [1, 0]
|
||||
- type: sarcophagus
|
||||
sarc_position: [2, 0]
|
||||
- type: sarcophagus
|
||||
sarc_position: [3, 0]
|
||||
- type: sarcophagus
|
||||
sarc_position: [0, 1]
|
||||
- type: sarcophagus
|
||||
sarc_position: [1, 1]
|
||||
- type: sarcophagus
|
||||
sarc_position: [2, 1]
|
||||
- type: sarcophagus
|
||||
sarc_position: [3, 1]
|
||||
- type: sarcophagus
|
||||
sarc_position: [0, 2]
|
||||
- type: sarcophagus
|
||||
sarc_position: [1, 2]
|
||||
- type: sarcophagus
|
||||
sarc_position: [2, 2]
|
||||
- type: sarcophagus
|
||||
sarc_position: [3, 2]
|
||||
- type: sarcophagus
|
||||
sarc_position: [0, 3]
|
||||
- type: sarcophagus
|
||||
sarc_position: [1, 3]
|
||||
- type: sarcophagus
|
||||
sarc_position: [2, 3]
|
||||
- type: sarcophagus
|
||||
sarc_position: [3, 3]
|
||||
- type: sarcophagus
|
||||
sarc_position: [0, 4]
|
||||
- type: sarcophagus
|
||||
sarc_position: [1, 4]
|
||||
- type: sarcophagus
|
||||
sarc_position: [2, 4]
|
||||
- type: sarcophagus
|
||||
sarc_position: [3, 4]
|
||||
- type: door
|
||||
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||
- type: mummy
|
||||
position: [10, 7]
|
||||
- type: mummy
|
||||
position: [7, 9]
|
||||
@@ -17,7 +17,7 @@ layout(set = 2, binding = 0) uniform sampler2D scene;
|
||||
|
||||
layout(set = 3, binding = 0) uniform PostFXUniforms {
|
||||
float vignette_strength;
|
||||
float chroma_strength;
|
||||
float chroma_min; // intensitat mínima de l'aberració cromàtica
|
||||
float scanline_strength;
|
||||
float screen_height;
|
||||
float mask_strength;
|
||||
@@ -27,7 +27,7 @@ layout(set = 3, binding = 0) uniform PostFXUniforms {
|
||||
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
|
||||
float time; // seconds since SDL init
|
||||
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz
|
||||
float pad2; // padding per tancar vec4 #2
|
||||
float chroma_max; // intensitat màxima; si == chroma_min → chroma estàtic
|
||||
// vec4 #3 — paràmetres de scanlines (exposats per preset YAML)
|
||||
float scan_dark_ratio; // fracció de subfila fosca per fila lògica (1/3 ≈ 0.333)
|
||||
float scan_dark_floor; // multiplicador de brillantor de la subfila fosca
|
||||
@@ -35,6 +35,19 @@ layout(set = 3, binding = 0) uniform PostFXUniforms {
|
||||
float pad3; // padding per tancar a 64 bytes (4 × vec4)
|
||||
} u;
|
||||
|
||||
// Mostreig bilinear horitzontal d'un canal RGB. Evita el "tic-tac" del sampler
|
||||
// NEAREST quan l'offset de chroma és subpíxel: sense interpolar, l'offset
|
||||
// arrodonia entre 1 i 2 píxels i el drift temporal feia un parpelleig discret.
|
||||
float sampleBilinearX(vec2 uv_target, int channel) {
|
||||
vec2 tex_size = vec2(textureSize(scene, 0));
|
||||
float px = uv_target.x * tex_size.x - 0.5;
|
||||
float p_floor = floor(px);
|
||||
float f = px - p_floor;
|
||||
vec4 c0 = texture(scene, vec2((p_floor + 0.5) / tex_size.x, uv_target.y));
|
||||
vec4 c1 = texture(scene, vec2((p_floor + 1.5) / tex_size.x, uv_target.y));
|
||||
return mix(c0[channel], c1[channel], f);
|
||||
}
|
||||
|
||||
// YCbCr helpers for NTSC bleeding
|
||||
vec3 rgb_to_ycc(vec3 rgb) {
|
||||
return vec3(
|
||||
@@ -92,10 +105,14 @@ void main() {
|
||||
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;
|
||||
// Aberración cromática — intensitat varia entre chroma_min i chroma_max amb
|
||||
// una sinusoidal (si min == max, queda estàtica). Mostreig bilinear horitzontal
|
||||
// per evitar el "tic-tac" del NEAREST sampler quan l'offset és subpíxel.
|
||||
if (u.chroma_min > 0.0 || u.chroma_max > 0.0) {
|
||||
float ca = mix(u.chroma_min, u.chroma_max, 0.5 + 0.5 * sin(u.time * 7.3)) * 0.005;
|
||||
colour.r = sampleBilinearX(uv + vec2(ca, 0.0), 0);
|
||||
colour.b = sampleBilinearX(uv - vec2(ca, 0.0), 2);
|
||||
}
|
||||
|
||||
// Corrección gamma (linealizar antes de scanlines, codificar después)
|
||||
if (u.gamma_strength > 0.0) {
|
||||
|
||||
@@ -0,0 +1,907 @@
|
||||
# Arquitectura de **AEE Arcade**
|
||||
|
||||
> Guía de orientación para un desarrollador nuevo en el proyecto.
|
||||
>
|
||||
> Cada afirmación de este documento está anclada a código real: se cita el
|
||||
> fichero (y, cuando ayuda, la función o el número de línea) que la respalda.
|
||||
> Donde no he encontrado algo, lo digo explícitamente en lugar de inventarlo.
|
||||
>
|
||||
> El proyecto es *"Aventures en Egipte ARCADE"* (AEE), un juego 2D escrito en
|
||||
> C++ sobre **SDL3**. Los comentarios del código están en valenciano/catalán;
|
||||
> este documento está en castellano por petición.
|
||||
|
||||
---
|
||||
|
||||
## Índice
|
||||
|
||||
1. [Visión general](#1-visión-general)
|
||||
2. [Punto de entrada y bucle principal](#2-punto-de-entrada-y-bucle-principal)
|
||||
3. [Gestión de escenas](#3-gestión-de-escenas)
|
||||
4. [Renderizado: de la lógica al píxel](#4-renderizado-de-la-lógica-al-píxel)
|
||||
5. [Entrada](#5-entrada)
|
||||
6. [Comunicación entre módulos](#6-comunicación-entre-módulos)
|
||||
7. [Lógica del juego](#7-lógica-del-juego)
|
||||
8. [Entidades](#8-entidades)
|
||||
9. [Mapa y niveles](#9-mapa-y-niveles)
|
||||
10. [Recursos](#10-recursos)
|
||||
11. [Configuración y constantes](#11-configuración-y-constantes)
|
||||
12. [Modo demo / IA](#12-modo-demo--ia)
|
||||
13. [Convenciones y patrones recurrentes](#13-convenciones-y-patrones-recurrentes)
|
||||
14. [Guía de navegación: "si quieres tocar X, mira Y"](#14-guía-de-navegación-si-quieres-tocar-x-mira-y)
|
||||
|
||||
---
|
||||
|
||||
## 1. Visión general
|
||||
|
||||
AEE Arcade está organizado en **dos capas claramente separadas** dentro de
|
||||
`source/`:
|
||||
|
||||
- **`source/core/`** — el motor genérico, sin conocimiento del juego concreto:
|
||||
sistema/arranque, rendering, audio, input, recursos, escena base, locale y
|
||||
UI base.
|
||||
- **`source/game/`** — el juego concreto: escenas (LOGO, TITLE, INTRO, GAME),
|
||||
entidades (momias, sarcófagos, puertas…), mapa, progresión de niveles,
|
||||
editor (solo Debug) y HUD.
|
||||
|
||||
La separación no es solo de carpetas: el código de `core/` nunca incluye
|
||||
cabeceras de `game/`. La conexión va siempre de `game/` hacia `core/`, salvo
|
||||
algún *hook* genérico (p.ej. el overlay del menú, ver §6).
|
||||
|
||||
Filosofía dominante: **data-driven**. Casi todo el comportamiento (geometría
|
||||
de salas, plantillas de entidades, timings de transición, controles, paletas,
|
||||
shaders, idioma…) se carga desde ficheros **YAML** en `data/`, con valores de
|
||||
*fallback* compilados en `source/utils/defaults.hpp`. El parser es
|
||||
[fkYAML](https://github.com/fktn-k/fkYAML), vendorizado en
|
||||
`source/external/fkyaml_node.hpp`.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
SDL[SDL3 callbacks · main.cpp] --> DIR[Director · core/system]
|
||||
DIR -->|update / render| SC[Scene activa]
|
||||
DIR --> EVT[EventBuffer]
|
||||
EVT --> MENU[ServiceMenu] & GI["GlobalInputs (F1–F12)"] & GP[GamepadManager]
|
||||
SC --> GAME[GameScene · game/scenes]
|
||||
GAME --> EM[EntityManager] --> BUS[EventBus]
|
||||
GAME --> TM[Tilemap] & MAP[Map] & LM[LevelManager]
|
||||
GAME --> PL[Players] -->|polling teclado/mando| INPUT[core/input]
|
||||
EM --> ENT["Entity + concrete/*"]
|
||||
GAME -->|blit ARGB| SURF[Surface]
|
||||
SURF --> CANVAS["Canvas 320×240 ARGB (CPU)"]
|
||||
CANVAS --> SCREEN[Screen] --> BACKEND["SDL3 GPU + shaders / fallback SDL_Renderer"]
|
||||
RES[Resource::Cache] -.-> SURF & AUDIO[Audio]
|
||||
```
|
||||
|
||||
Tamaño aproximado: ~188 ficheros C++ y ~30.000 líneas en `source/` (excluyendo
|
||||
`external/`).
|
||||
|
||||
**Dos ideas-fuerza que conviene interiorizar desde el principio**, porque
|
||||
condicionan todo lo demás:
|
||||
|
||||
1. **El render es 100% por CPU sobre un único canvas ARGB de 320×240.** La GPU
|
||||
solo escala ese canvas a la ventana y aplica post-procesado (CRT). Ver §4.
|
||||
2. **La paleta es por-`Surface`, pero el canvas final es ARGB de 24 bits.** No
|
||||
es un juego de paleta global limitada: cada GIF lleva su propia paleta y se
|
||||
compone sobre un framebuffer de color completo
|
||||
(`source/core/rendering/surface.hpp:13`).
|
||||
|
||||
---
|
||||
|
||||
## 2. Punto de entrada y bucle principal
|
||||
|
||||
### 2.1. No hay `while` propio: SDL conduce el bucle
|
||||
|
||||
`source/main.cpp` define `SDL_MAIN_USE_CALLBACKS` (línea 1). Eso significa que
|
||||
**no existe un game loop escrito a mano**: es SDL quien llama a cuatro
|
||||
funciones de *callback*:
|
||||
|
||||
| Callback | Qué hace en este proyecto |
|
||||
|---|---|
|
||||
| `SDL_AppInit` | Guarda `argv` (para reinicio en caliente), inicia el logger y **crea el `Director`** (`main.cpp:13`). |
|
||||
| `SDL_AppEvent` | Empuja cada `SDL_Event` a `EventBuffer::events` (`main.cpp:31`). |
|
||||
| `SDL_AppIterate` | Ejecuta **un frame**: `director->iterate()` (`main.cpp:37`). |
|
||||
| `SDL_AppQuit` | Destruye el `Director` y, si se pidió, hace `execv` para reiniciar (`main.cpp:42`). |
|
||||
|
||||
El `Director` se entrega a SDL como puntero crudo (`appstate`) y se reabsorbe
|
||||
en `SDL_AppQuit` con un `unique_ptr` para limpieza RAII ordenada.
|
||||
|
||||
### 2.2. El `Director`: orquestador del frame
|
||||
|
||||
`source/core/system/director.hpp` / `.cpp` es el corazón del sistema.
|
||||
|
||||
**Constructor (`director.cpp:47`)** — inicializa *todo* en un orden cuidadoso y
|
||||
documentado en comentarios. Resumen del orden:
|
||||
|
||||
1. `SDL_Init(0)` (sin subsistemas todavía).
|
||||
2. Sistema de recursos: localiza `resources.pack` junto al binario; en `_DEBUG`
|
||||
activa *fallback* a disco apuntando al árbol fuente (`AEE_SOURCE_DIR`) para
|
||||
editar YAML/GIF sin re-empaquetar; en Release el pack es obligatorio
|
||||
(`director.cpp:52`).
|
||||
3. `createSystemFolder()` — carpeta de configuración por plataforma (ver §11).
|
||||
4. `GamepadManager::get().init()` **antes** de cargar opciones (las opciones
|
||||
re-vinculan mandos a jugadores; `director.cpp:86`).
|
||||
5. Carga de `config.yaml`, `gamepad_configs.yaml`, `gameplay.yaml`, presets de
|
||||
shaders (`postfx.yaml`, `crtpi.yaml`).
|
||||
6. `Screen::get().configure(...)` — crea ventana + backend GPU.
|
||||
7. `Locale`, `KeyConfig`, `UiConfig`, `Lighting`, `EditorConfig` (Debug).
|
||||
8. Pre-carga de recursos de UI: paleta, fuentes, sombra compartida, pistas de
|
||||
música, sonidos de menú.
|
||||
9. `Audio::init(...)`.
|
||||
10. Construcción de los *overlays* compartidos (`Notifier`, `RenderInfo`,
|
||||
`ServiceMenu`) y de las `Surface`/`Text` compartidas entre escenas.
|
||||
11. Registro del *overlay hook* del menú en `Screen` (`director.cpp:291`).
|
||||
12. Elección de la escena inicial (ver §3.3) y `last_ticks_ms_ = SDL_GetTicks()`.
|
||||
|
||||
**Destructor (`director.cpp:336`)** — el orden de liberación es crítico y está
|
||||
fuertemente comentado: primero la escena, luego el audio del `Cache` (mientras
|
||||
SDL sigue vivo), luego `Audio`, `Screen` y por fin `SDL_Quit()`. Se hace
|
||||
explícitamente (no vía destructores estáticos `atexit`) para evitar crashes en
|
||||
`vkDestroyInstance` cuando el runtime descarga librerías antes de tiempo.
|
||||
|
||||
### 2.3. Gestión del tiempo y orden update/render
|
||||
|
||||
El "frame" vive en `Director::iterate()` (`director.cpp:509`). **No hay
|
||||
timestep fijo**: se usa el delta real entre frames.
|
||||
|
||||
```cpp
|
||||
const std::uint64_t NOW_MS = SDL_GetTicks();
|
||||
int delta_ms = static_cast<int>(NOW_MS - last_ticks_ms_);
|
||||
delta_ms = std::max(delta_ms, 0); // nunca negativo
|
||||
last_ticks_ms_ = NOW_MS;
|
||||
```
|
||||
|
||||
Orden exacto de un frame (`director.cpp:509`–`549`):
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant SDL
|
||||
participant Dir as Director::iterate
|
||||
participant Scene
|
||||
SDL->>Dir: SDL_AppIterate
|
||||
Note over Dir: Mouse::handleEvent(ev) para todos los eventos
|
||||
Dir->>Dir: processGamepadEvents() (hot-plug + botón SERVICE)
|
||||
Dir->>Dir: processMenuInput() (ServiceMenu consume teclas)
|
||||
Dir->>Dir: processEvents() (detecta SDL_EVENT_QUIT)
|
||||
Dir->>Dir: delta_ms = now - last
|
||||
Dir->>Dir: Audio::update()
|
||||
Dir->>Dir: Mouse::updateCursorVisibility()
|
||||
Dir->>Dir: ServiceMenu::update(delta_ms)
|
||||
Dir->>Scene: update(delta_ms)
|
||||
Dir->>Scene: render()
|
||||
Dir->>Dir: switchSceneIfRequested()
|
||||
Dir->>Dir: EventBuffer::events.clear()
|
||||
```
|
||||
|
||||
El orden de procesamiento de eventos es deliberado y está comentado en
|
||||
`director.cpp:514`: gamepad → menú → quit, para que un `SDL_EVENT_QUIT`
|
||||
inyectado por el menú ("Salir del juego") se vea en el mismo frame antes de
|
||||
limpiar el buffer.
|
||||
|
||||
El bucle termina (`SDL_APP_SUCCESS`) cuando la escena se reinicia a `nullptr`
|
||||
(quit) o cuando `current_scene_->wantsQuit()` es `true`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Gestión de escenas
|
||||
|
||||
### 3.1. La base `Scene`
|
||||
|
||||
`source/core/scene/scene.hpp` es minimalista:
|
||||
|
||||
```cpp
|
||||
class Scene {
|
||||
public:
|
||||
virtual void update(int delta_ms) = 0;
|
||||
virtual void render() = 0;
|
||||
auto wantsQuit() const -> bool { return wants_quit_; }
|
||||
auto takeExitRequest() -> std::any { return std::exchange(exit_request_, {}); }
|
||||
protected:
|
||||
bool wants_quit_{false};
|
||||
std::any exit_request_;
|
||||
};
|
||||
```
|
||||
|
||||
El detalle clave: la petición de transición viaja dentro de un **`std::any`**.
|
||||
Así, `core/scene/scene.hpp` **no necesita conocer los tipos del juego**. La
|
||||
escena deposita un `ExitRequest` (tipo definido en `game/`) y el `Director`
|
||||
hace el `std::any_cast` cuando le toca.
|
||||
|
||||
### 3.2. El flujo de escenas: descentralizado al pedir, centralizado al construir
|
||||
|
||||
`source/game/scenes/scene_flow.hpp` define el contrato:
|
||||
|
||||
- `enum class SceneId { LOGO, TITLE, INTRO, GAME }`.
|
||||
- Structs de parámetros por escena (`LogoParams`, `TitleParams`,
|
||||
`IntroParams`, `GameParams`) reunidos en un `std::variant SceneParams`.
|
||||
- `struct ExitRequest { SceneId next; SceneParams params; }`.
|
||||
|
||||
Una escena **no construye la siguiente**: solo declara a dónde quiere ir. El
|
||||
único punto que sabe cómo instanciar cada escena concreta es
|
||||
`Director::buildScene()` (`director.cpp:484`), que tiene a mano todos los
|
||||
recursos compartidos pre-cargados en el constructor.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Escena activa] -->|deposita ExitRequest en std::any| B[Director::switchSceneIfRequested]
|
||||
B -->|std::any_cast| C[Director::buildScene]
|
||||
C -->|make_unique| D[Nueva escena]
|
||||
```
|
||||
|
||||
Esto desacopla las escenas entre sí: ninguna `#include` a otra. Para **añadir
|
||||
una escena nueva** (documentado en `scene_flow.hpp:14`): (1) una entrada en el
|
||||
`enum`, (2) un struct `*Params` si necesita datos, (3) un `case` en
|
||||
`buildScene`.
|
||||
|
||||
> Nota de migración: en `buildScene` se ve que algunas escenas todavía reciben
|
||||
> recursos directamente del `Director` por el constructor en vez de pasar por
|
||||
> el canal `ExitRequest`. Es un estado intermedio, comentado en
|
||||
> `director.cpp:484`.
|
||||
|
||||
### 3.3. Flujo del juego y punto de arranque
|
||||
|
||||
El flujo "completo" que ve el jugador final es **LOGO → TITLE → INTRO → GAME**.
|
||||
|
||||
- **Release**: siempre arranca en `LOGO` (`director.cpp:314`).
|
||||
- **Debug**: lee `debug.yaml` de la carpeta de sistema; si no existe, lo crea
|
||||
con `start_scene: GAME` para no tragarse los placeholders en el día a día
|
||||
(`director.cpp:301`). `debug.yaml` también puede fijar `starting_players` y
|
||||
`starting_level` (`director.cpp:304`).
|
||||
|
||||
Las escenas concretas viven en `source/game/scenes/`:
|
||||
`logo_scene`, `title_scene`, `intro_scene`, `game_scene`. La transición entre
|
||||
salas dentro de GAME **no** es un cambio de escena: la `GameScene` persiste y
|
||||
recarga la sala internamente (ver §7).
|
||||
|
||||
---
|
||||
|
||||
## 4. Renderizado: de la lógica al píxel
|
||||
|
||||
Esta es la sección más importante para entender el proyecto, porque el render
|
||||
es poco convencional: **todo el dibujo lo hace la CPU sobre un buffer de
|
||||
píxeles**, y la GPU solo interviene al final.
|
||||
|
||||
### 4.1. El canvas: un único framebuffer ARGB en RAM
|
||||
|
||||
- La resolución lógica del juego es **320×240** (`source/utils/defines.hpp:10`,
|
||||
`GameCanvas::WIDTH/HEIGHT`).
|
||||
- `Screen` mantiene `std::vector<std::uint32_t> canvas_` — un array plano de
|
||||
píxeles ARGB8888 (`screen.hpp:143`).
|
||||
- El acceso de los blitters no va por `Screen::get()` directamente, sino por
|
||||
`Rendering::activeCanvas()` (`source/core/rendering/canvas.hpp:38`), para no
|
||||
acoplarse al ciclo de vida de `Screen`. También existe un sistema de
|
||||
**clipping** apilable (`pushClip`/`popClip`, `canvas.hpp:46`).
|
||||
|
||||
El bit alfa es siempre opaco: `Rendering::OPAQUE_ALPHA = 0xFF000000`
|
||||
(`canvas.hpp:15`). El canal alfa no se usa para transparencia del frame final
|
||||
— la transparencia de sprites se resuelve por *color index transparente*
|
||||
durante el blit (ver §4.2).
|
||||
|
||||
### 4.2. `Surface`: sprites paletizados → píxeles ARGB
|
||||
|
||||
`source/core/rendering/surface.hpp` / `.cpp` es el blitter base.
|
||||
|
||||
- Una `Surface` guarda los píxeles como **índices de 8 bits** (`Uint8`) más una
|
||||
**paleta propia** de 256 colores ARGB (`Palette = std::array<Uint32, 256>`).
|
||||
- Se carga desde un GIF (`Surface(const std::string&)`, `surface.cpp:38`):
|
||||
decodifica píxeles y paleta con `GIF::Gif` (`core/rendering/gif.*`).
|
||||
- El índice 0 es transparente por convención de todos los GIF del proyecto
|
||||
(`surface.hpp:78`); los tilesets que usen otro índice llaman a
|
||||
`setTransparentColor()`.
|
||||
|
||||
El núcleo del dibujo es `renderWithColorReplace()` (`surface.cpp:90`). Para
|
||||
cada píxel del rectángulo fuente:
|
||||
|
||||
1. Comprueba el *clip* y descarta fuera de él.
|
||||
2. Lee el índice fuente; si es el transparente, **salta** (no escribe).
|
||||
3. Aplica `flip` horizontal/vertical reindexando la coordenada fuente.
|
||||
4. Opcionalmente reemplaza un índice por otro (`source_color → target_color`),
|
||||
usado por `Text` para recolorear glifos (la "tinta" se guarda con un único
|
||||
índice).
|
||||
5. Escribe `palette_[remapped]` en el canvas.
|
||||
|
||||
`render()` (`surface.cpp:82`) es un caso particular de lo anterior (delega con
|
||||
`source==target==transparent`, evitando duplicar el bucle).
|
||||
|
||||
> Hay además una `SubPalette` (`std::array<Uint8,256>`, identidad por defecto
|
||||
> vía `std::iota`) que permite remapear índices antes de la paleta; se usa para
|
||||
> efectos como el parpadeo de inmunidad del Player.
|
||||
|
||||
Sobre `Surface` se construyen abstracciones de mayor nivel en
|
||||
`core/rendering/sprite/`:
|
||||
|
||||
- `Sprite` → `AnimatedSprite` → `MovingSprite` (herencia).
|
||||
- `AnimatedSprite` reproduce tiras de fotogramas; su parser está en
|
||||
`animated_sprite_parser.cpp`. **El `Player` hereda de `AnimatedSprite`**
|
||||
(`player.hpp:18`).
|
||||
|
||||
### 4.3. El orden de pintado de un frame de juego
|
||||
|
||||
`GameScene::render()` (`source/game/scenes/game_scene.cpp:1682`) es la
|
||||
referencia canónica de cómo se compone una imagen. Orden exacto:
|
||||
|
||||
1. **Fondo**: `Screen::fillRect` con el color de fondo de la paleta UI.
|
||||
2. **Tilemap**: `tilemap_.render()` — copia de la textura prerenderizada
|
||||
(ver §9.2). Las celdas de sarcófago quedan transparentes.
|
||||
3. **(Editor Debug)**: si está activo, pinta entidades congeladas y vuelve.
|
||||
4. **Capa de pintado** (`PaintLayer`): el "rastro" del jugador por el suelo.
|
||||
5. **Sombras**: `p->renderShadow()` de cada jugador en el aire — por debajo de
|
||||
todos los actores.
|
||||
6. **Z-sort de actores**: entidades + jugadores se fusionan en un vector y se
|
||||
ordenan con `std::ranges::stable_sort` por **`getFeetY()` ascendente**
|
||||
(`game_scene.cpp:1755`). Quien tiene los pies más arriba se pinta antes (más
|
||||
al fondo). Esto da la falsa perspectiva 2D.
|
||||
7. **Pasada de iluminación**: `applyLightingPass()` (ver §4.4).
|
||||
8. **Fade de sala** (`renderRoomFade`): negro entre actores y HUD.
|
||||
9. **HUD**: `hud_->render()`.
|
||||
10. **Texto de transición de sala** y **rótulo de game over**.
|
||||
11. **Fade final de game over**.
|
||||
12. `Notifier::render()` y `RenderInfo::render()` (overlays de debug).
|
||||
13. **`Screen::get().present()`**.
|
||||
|
||||
El criterio recurrente está comentado: la UI (HUD, notificaciones, menú) se
|
||||
pinta **después** de los fades para quedar siempre legible.
|
||||
|
||||
### 4.4. Iluminación por CPU
|
||||
|
||||
`source/core/rendering/lighting.hpp` / `.cpp`. Es una pasada que **modifica
|
||||
cada píxel del canvas** después de pintar el mundo. Dos modos seleccionables
|
||||
por sala (`lighting.mode` en el YAML):
|
||||
|
||||
- **SUBTRACTIVE** (histórico, por defecto): `out = src × max(room_light,
|
||||
max_emitter_intensity)`. Los emisores "rompen" una capa oscura. El color del
|
||||
emisor se ignora; solo cuenta la intensidad.
|
||||
- **ADDITIVE**: `out = saturate(src × room_light + Σ emitter_color × intensity)`.
|
||||
Los emisores suman halo cromático.
|
||||
|
||||
Cada emisor (jugadores, antorchas, momias con `light_radius > 0`) proyecta un
|
||||
círculo radial de 3 anillos configurables en `data/config/lighting.yaml`. Las
|
||||
antorchas pueden tener *flicker* (`light_flicker`), con parámetros resueltos
|
||||
por entidad (`Entity::getFlickerParams`, `entity.hpp:306`).
|
||||
|
||||
### 4.5. `Screen::present()` y el backend GPU
|
||||
|
||||
`Screen::present()` (`source/core/rendering/screen.cpp:306`):
|
||||
|
||||
1. Llama al **overlay hook** (menú de servicio) para que pinte *sobre* el
|
||||
canvas antes de subirlo (`screen.cpp:310`).
|
||||
2. Actualiza el contador de FPS.
|
||||
3. Si hay **backend GPU** (`shader_backend_`): `uploadPixels(canvas)` +
|
||||
`render()`. Este es el camino por defecto.
|
||||
4. Si no (fallback): `SDL_UpdateTexture` de una textura STREAMING +
|
||||
`SDL_RenderTexture` + `SDL_RenderPresent`.
|
||||
|
||||
El backend GPU vive en `source/core/rendering/sdl3gpu/` (SDL3 GPU API +
|
||||
shaders SPIR-V compilados). En `configure()` (`screen.cpp:108`) se intenta el
|
||||
path GPU primero; si falla, hay **fallback automático a `SDL_Renderer`**. El
|
||||
escalado a la ventana usa modos *letterbox / integer / stretch / overscan*
|
||||
(`Defaults::ScalingMode`).
|
||||
|
||||
**Post-procesado**: shaders en `data/shaders/` (`postfx.frag`, `postfx.vert`,
|
||||
`crtpi_frag.glsl`). El efecto CRT y similares se aplican aquí, en GPU, sobre la
|
||||
textura del canvas. El juego no sabe nada de esto: solo escribe píxeles en el
|
||||
canvas. Los presets se gestionan desde `Options` y se ciclan con hotkeys (§5).
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
LOG["Lógica del juego (game/)"] -->|Surface::render| CV["Canvas ARGB 320×240 (CPU)"]
|
||||
HUD[HUD / Notifier / RenderInfo] --> CV
|
||||
LIGHT[Lighting pass CPU] --> CV
|
||||
MENU[ServiceMenu overlay] -->|hook en present| CV
|
||||
CV -->|uploadPixels| GPU["ShaderBackend (SDL3 GPU)"]
|
||||
GPU -->|shader postfx/crtpi| WIN[Ventana]
|
||||
CV -.fallback.-> SDLR[SDL_Renderer] --> WIN
|
||||
```
|
||||
|
||||
> **Resumen honesto**: la GPU en este proyecto **no dibuja geometría del
|
||||
> juego**. Solo recibe un mapa de bits ya compuesto y lo escala + filtra. Todo
|
||||
> el "render" del juego es manipulación de un array de `uint32_t` en RAM.
|
||||
|
||||
---
|
||||
|
||||
## 5. Entrada
|
||||
|
||||
Hay **dos vías de entrada totalmente distintas**, y entender la diferencia
|
||||
evita mucha confusión:
|
||||
|
||||
### 5.1. Vía por eventos (`EventBuffer`)
|
||||
|
||||
Los `SDL_Event` se acumulan en `EventBuffer::events`
|
||||
(`source/core/system/event_buffer.hpp`) durante `SDL_AppEvent`. Los consumen,
|
||||
en orden, dentro de `Director::iterate()`:
|
||||
|
||||
- **`Mouse`** (`core/input/mouse.*`): solo reacciona a `MOUSE_MOTION` para
|
||||
reaparecer el cursor (auto-ocultación).
|
||||
- **Gamepad hot-plug y botón SERVICE** (`processGamepadEvents`,
|
||||
`director.cpp:417`): ADDED/REMOVED van a `GamepadManager`; el botón SERVICE
|
||||
(por defecto BACK, remapeable) abre/cierra el menú.
|
||||
- **Menú de servicio** (`processMenuInput`, `director.cpp:446`): con el menú
|
||||
abierto consume todas las teclas salvo F1–F12 y ESC.
|
||||
- **`GlobalInputs`** (`core/input/global_inputs.cpp:190`): hotkeys de sistema
|
||||
F1–F12 + ESC. Itera `EventBuffer` buscando `KEY_DOWN`. Las acciones se leen
|
||||
de `KeyConfig` (`data/config/input.yaml`):
|
||||
|
||||
| Acción | Efecto |
|
||||
|---|---|
|
||||
| `dec_zoom` / `inc_zoom` | cambia zoom de ventana |
|
||||
| `fullscreen` | alterna pantalla completa |
|
||||
| `toggle_vsync` | alterna VSync |
|
||||
| `screenshot` | guarda captura |
|
||||
| `toggle_shader` / `next_shader` / `next_preset` | post-FX |
|
||||
| `next_scaling_mode` | cicla letterbox/integer/… |
|
||||
| `toggle_render_info` | cicla overlay de debug OFF→TOP→BOTTOM |
|
||||
| `exit` (ESC) | **salida en dos pasos** (ver abajo) |
|
||||
|
||||
La salida con ESC es en dos pulsaciones: la primera muestra una notificación
|
||||
de confirmación y arma un temporizador; la segunda, dentro de la ventana,
|
||||
activa `wantsQuit()` (`global_inputs.cpp:176`, `global_inputs.hpp:1`).
|
||||
|
||||
### 5.2. Vía por *polling* (la del gameplay)
|
||||
|
||||
El **`Player` lee su input directamente cada frame**, no por eventos:
|
||||
|
||||
- Teclado: `SDL_GetKeyboardState(nullptr)` (`player.cpp:629`, `764`, `784`).
|
||||
Cada `Player` tiene su propio `Controls` (struct de `SDL_Scancode`,
|
||||
`player.hpp:23`). Por defecto P1 usa flechas + M/N (`Defaults::KeysGame`,
|
||||
`defaults.hpp:75`) y P2 usa WASD (`Defaults::KeysGameP2`).
|
||||
- Mando: `GamepadManager::get().getForPlayer(id)` +
|
||||
`GamepadBindings::isPressed(pad, btn)` (`player.cpp:769`).
|
||||
|
||||
Esto encaja con que el movimiento se calcula en `Player::tick(dt)` durante el
|
||||
bloque de mundo de `GameScene::update` (§7), donde importa el estado *actual*
|
||||
de las teclas, no la secuencia de eventos.
|
||||
|
||||
### 5.3. Detección puntual de pulsaciones en escena
|
||||
|
||||
Para "¿se ha pulsado START este frame?" hay un helper compartido,
|
||||
`Input::Detect` (`source/game/scenes/start_input.hpp`):
|
||||
|
||||
- `pressedStart(player_id)` — combina eventos de teclado y botón de mando.
|
||||
Lo usan `TitleScene` (empezar partida) y `GameScene` (*join in progress*).
|
||||
- `pressedActionButton(player_id)` — salto o ataque, usado para saltar el
|
||||
*countdown* de CONTINUE?.
|
||||
|
||||
### 5.4. Mandos
|
||||
|
||||
`core/input/`:
|
||||
|
||||
- `gamepad_manager.*` — descubre, abre/cierra mandos y asigna mando↔jugador.
|
||||
- `gamepad_bindings.*` — vinculaciones por dispositivo `(name, path)`,
|
||||
persistidas en `gamepad_configs.yaml` (fichero aparte de `config.yaml`
|
||||
porque es por hardware, no por perfil; `director.cpp:93`).
|
||||
- `define_buttons.*`, `key_config.*` — catálogo de botones y mapeo de scancodes
|
||||
de hotkeys.
|
||||
|
||||
---
|
||||
|
||||
## 6. Comunicación entre módulos
|
||||
|
||||
El proyecto combina tres mecanismos, cada uno para su propósito:
|
||||
|
||||
### 6.1. `EventBus` — pub/sub tipado dentro de un nivel
|
||||
|
||||
`source/game/entities/event_bus.hpp`. Hay **un `EventBus` por mundo**, propiedad
|
||||
del `EntityManager` (`entity_manager.hpp:199`). Las entidades reciben un
|
||||
puntero vía `Entity::setEventBus`.
|
||||
|
||||
- El *payload* es un **`std::variant`** tipado (`event_bus.hpp:98`), no
|
||||
`void*` ni `std::any`: `int`, `std::string`, `Entity*`, `TilePos`,
|
||||
`PaintedTile`, `WorldPos`, `WhipSpawnPayload`, `PlayerStatsPayload`.
|
||||
- API: `subscribe(name, handler) → SubId`, `unsubscribe(id)`,
|
||||
`publish(Event)` (+ sobrecargas ergonómicas por tipo de payload).
|
||||
|
||||
Eventos reales que circulan (vistos en el código): `sarcophagus_opened`,
|
||||
`level_complete`, `player_hit`, `player_killed`, `player_disabled`,
|
||||
`player_jumped`, `player_landed`, `on_tile_entered`, `tile_painted`,
|
||||
`whip_spawn`, `player_stats_changed`.
|
||||
|
||||
Ejemplo de flujo desacoplado: el `Player` no conoce ni `WhipAttack` ni
|
||||
`EntityManager`; al atacar publica `whip_spawn` con un `WhipSpawnPayload`, y
|
||||
`GameScene` lo escucha y crea la entidad (`event_bus.hpp:66`).
|
||||
|
||||
Otro: cuando el `Player` muta vidas/score/tesoros, publica
|
||||
`player_stats_changed` con un *snapshot completo*; el HUD lo consume como
|
||||
estado absoluto (no delta). Así cualquier vía que cambie stats se refleja sin
|
||||
que el caller recuerde notificar al HUD (`event_bus.hpp:28`).
|
||||
|
||||
### 6.2. Llamadas directas e inyección de dependencias
|
||||
|
||||
Donde la relación es estructural y estable, se usa llamada directa o inyección
|
||||
por *setter*: p.ej. `Entity::setEntityManager` (una momia consulta el mundo
|
||||
para no caer en un `Hole`, `entity.hpp:180`), o `Map::setEntityCollider` (el
|
||||
`EntityManager` registra un predicado de colisión para muros dinámicos,
|
||||
`map.hpp:171`).
|
||||
|
||||
### 6.3. *Hooks* / callbacks para invertir dependencias
|
||||
|
||||
`core/` no conoce `game/`, así que cuando `core/` necesita algo del juego se
|
||||
inyecta un `std::function`:
|
||||
|
||||
- `Screen::setOverlayRenderer(callback)` — el `Director` registra una lambda
|
||||
que pinta el `ServiceMenu` sobre el canvas en `present()`. Así `Screen` puede
|
||||
poner el menú por encima de cualquier escena sin incluir `game/ui`
|
||||
(`screen.hpp:81`, `director.cpp:291`).
|
||||
|
||||
### 6.4. Singletons
|
||||
|
||||
Varios subsistemas son **Meyers singletons** (`get()`): `Screen`,
|
||||
`Resource::Cache`, `GamepadManager`, `GamepadBindings`, `Locale`, `UiConfig`,
|
||||
`Lighting`, `LevelManager`, `ServiceMenu`, `Notifier`, `RenderInfo`. Ver §13
|
||||
para el matiz de su ciclo de vida.
|
||||
|
||||
---
|
||||
|
||||
## 7. Lógica del juego
|
||||
|
||||
El grueso vive en `source/game/scenes/game_scene.cpp` (la clase más grande del
|
||||
proyecto, ~1800 líneas). `GameScene` coordina mapa, entidades, jugadores, HUD,
|
||||
y **dos máquinas de estado**.
|
||||
|
||||
### 7.1. FSM de sala — `RoomFsm`
|
||||
|
||||
`source/game/scenes/room_fsm.hpp`. Lógica **pura** (no toca SDL/Audio; solo
|
||||
`Locale` para formatear texto), extraída para ser testeable.
|
||||
|
||||
```
|
||||
ROOM_INTRO → PLAYING → ROOM_OUTRO → (siguiente sala) ROOM_INTRO
|
||||
```
|
||||
|
||||
Cada estado tiene sub-fases con timings cargados de
|
||||
`data/scenes/game/game.yaml` (`room_fsm.hpp:33`). Por ejemplo, `ROOM_INTRO`
|
||||
tiene: `fade_in → text_delay → text_enter → text_hold → text_exit →
|
||||
music_tail`. La fase `music_tail` es una "cola silenciosa" calculada para que
|
||||
el *timeline* visual termine exactamente cuando drena el `.ogg` de música —
|
||||
**no hay callback de fin de música**; la transición la dispara
|
||||
`GameScene::updateRoomTransition` cuando la FSM consume la última fase
|
||||
(`room_fsm.hpp:27`).
|
||||
|
||||
La función central es `RoomFsm::advance(current, transition, cfg, delta_ms)`
|
||||
→ devuelve el estado resultante; si cambia, el caller resetea la transición.
|
||||
|
||||
### 7.2. FSM de game over — `GameOverFsm`
|
||||
|
||||
`source/game/scenes/game_over_fsm.*`. Sub-FSM independiente
|
||||
(`CONTINUE? → countdown → GAME OVER → FADE_OUT → DONE`) que se superpone a la
|
||||
`RoomFsm` y puede dispararse durante `PLAYING` cuando llegan a 0 los jugadores
|
||||
en partida. Al terminar pide la transición a `LOGO`
|
||||
(`game_scene.hpp:280`–`333`). El *countdown* puede saltarse pulsando acción
|
||||
(`handleCountdownSkip`).
|
||||
|
||||
### 7.3. FSM del `Player`
|
||||
|
||||
`source/game/entities/player.hpp:66` define `enum class State`:
|
||||
`GROUNDED, AIRBORNE, LANDING_STUN, HURT, ATTACKING, EXITED, DISABLED, DYING`.
|
||||
Cada estado define qué input acepta y cómo se mueve/colisiona. Detalles
|
||||
relevantes:
|
||||
|
||||
- El **salto** usa `jump_z_` (altura): saltar por encima de la `height_` de
|
||||
una entidad permite esquivar momias/rocas (`entity.hpp:219`).
|
||||
- `DISABLED` = slot inactivo (P2 en single-player, o todos durante
|
||||
transiciones); `EXITED` = ha salido por la puerta; `DYING` = animación de
|
||||
muerte + delay antes de respawn.
|
||||
- El array `players_` **siempre tiene 2 entradas** (P1, P2); el slot inactivo
|
||||
queda `DISABLED` y puede entrar a media partida ("join in progress") pulsando
|
||||
START (`game_scene.hpp:87`, `handleJoinInput`).
|
||||
|
||||
### 7.4. El bloque de mundo: orden de update
|
||||
|
||||
Dentro de `GameScene::update` (`game_scene.cpp:1519`), el mundo solo avanza si
|
||||
**no** está abierto el menú ni el editor y `delta_ms > 0`. El pipeline de
|
||||
movimiento es de **tres pasadas** para intercalar la resolución jugador↔jugador
|
||||
(`game_scene.cpp:1629`):
|
||||
|
||||
1. `player->tick(DT)` — cada jugador calcula su velocidad intencionada (teclado
|
||||
+ mando + *facing*).
|
||||
2. `resolvePlayerVsPlayer(...)` — anula velocidades en choque frontal o las
|
||||
escala a `push_factor_` (empuje).
|
||||
3. `player->applyMovement(DT)` — en orden "empujado primero".
|
||||
|
||||
Después: `entity_manager_.update(DT)` (tras los jugadores, para que un
|
||||
`on_tile_entered` emitido este frame se procese ya) y
|
||||
`checkPlayerCollisions(...)`.
|
||||
|
||||
### 7.5. Reglas del juego
|
||||
|
||||
- **Sarcófagos** (`concrete/sarcophagus.hpp`): el jugador debe "pintar"
|
||||
(recorrer) todo el perímetro de tiles del sarcófago para abrirlo. FSM:
|
||||
`CLOSED → OPENING → OPEN`. Al abrirse publica `sarcophagus_opened` y reparte
|
||||
recompensa (`points`/`treasures`) al jugador atribuido.
|
||||
- **Puerta** (`concrete/door.hpp`): FSM `CLOSED → OPENING → OPEN`. El
|
||||
`EntityManager` la abre automáticamente cuando el nº de sarcófagos abiertos
|
||||
iguala el total (`entity_manager.hpp:21`). Al abrirse libera el pasadizo
|
||||
(`Map::door_open = true`). Cruzarla dispara `Player::exit()` →
|
||||
`player_exited` → `level_complete` → OUTRO.
|
||||
- **Pintado del suelo** (`game/map/paint_layer.*`): escucha `on_tile_entered`,
|
||||
filtra pintabilidad (`Map::isPaintable`) y re-publica `tile_painted` para los
|
||||
sarcófagos (`game_scene.hpp:219`).
|
||||
- **Hazards**: momias, rocas rodantes (`rolling_rock`), trampas de suelo/pared
|
||||
(`floor_trap`, `wall_trap`), agujeros (`hole`). Los letales publican
|
||||
`player_killed`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Entidades
|
||||
|
||||
### 8.1. `Entity`: composición, no herencia de sprite
|
||||
|
||||
`source/game/entities/entity.hpp`. Decisión de diseño explícita
|
||||
(`entity.hpp:34`): **`Entity` NO hereda de `AnimatedSprite`**. La animación es
|
||||
un *componente opcional* (`std::optional<AnimationComponent> anim_`), porque
|
||||
no todas las entidades la necesitan (trampas invisibles, triggers) y para no
|
||||
imponer semántica de movimiento continuo a actores estáticos.
|
||||
|
||||
`Entity` reúne lo común: posición en tiles y píxeles, caja de colisión
|
||||
opcional (`CollisionBox`), flags de comportamiento (`collides_`,
|
||||
`blocks_movement_`, `triggers_on_overlap_`, `is_floor_decal_`, `dead_`),
|
||||
parámetros de luz, y un conjunto de **hooks polimórficos**:
|
||||
|
||||
| Hook | Para qué |
|
||||
|---|---|
|
||||
| `update(dt)` / `render()` | tick lógico / dibujo |
|
||||
| `onPlayerContact(Player*)` | el jugador toca la entidad (daño, salida…) |
|
||||
| `onWhipHit(WhipAttack*)` | recibe un latigazo (override en `Mummy`) |
|
||||
| `notifyTilePainted(col,row,id)` | un tile se pintó (lo usa `Sarcophagus`) |
|
||||
| `onJustSpawned()` | recién nacida (dispara animación EMERGING) |
|
||||
| `onRemoved()` | antes de borrarse (mantener invariantes) |
|
||||
| `shouldReceiveContact()` | filtro de si recibe contacto este frame |
|
||||
| `getFeetY()` | Y de los pies, para el z-sort (§4.3) |
|
||||
|
||||
El polimorfismo evita "sacos" separados por tipo: el `EntityManager` itera
|
||||
todas y cada entidad decide qué hace (`entity_manager.hpp:78`).
|
||||
|
||||
### 8.2. `EntityManager`: contenedor y coordinador por nivel
|
||||
|
||||
`source/game/entities/entity_manager.hpp`. **No es singleton**: una instancia
|
||||
por nivel, vive dentro de `GameScene` (`game_scene.hpp:80`).
|
||||
|
||||
Responsabilidades: poseer las entidades (`std::vector<unique_ptr<Entity>>`),
|
||||
propagar `update`, contar sarcófagos abiertos y abrir la puerta, hacer *fan-out*
|
||||
de `notifyTilePainted`, comprobar colisiones jugador↔entidad, y cargar el nivel
|
||||
desde YAML (`loadFromLevel`). También gestiona una **cola de actores pendientes**
|
||||
(`pending_actors_`): los hazards dinámicos declarados en el YAML no se instancian
|
||||
de inmediato, sino que `spawnPendingActors()` los materializa en una sub-fase
|
||||
concreta del ROOM_INTRO con su cinemática de aparición (`entity_manager.hpp:130`).
|
||||
|
||||
### 8.3. `EntityFactory`: registro abierto a extensión
|
||||
|
||||
`source/game/entities/entity_factory.hpp`. Factory por nombre con
|
||||
**auto-registro**: cada `concrete/*.cpp` declara su creador con la macro
|
||||
`REGISTER_ENTITY(name, Class)`, que genera un objeto estático cuyo constructor
|
||||
se ejecuta antes de `main` y se registra en la factory (`entity_factory.hpp:56`).
|
||||
|
||||
> **Añadir un tipo de entidad = nueva clase en `concrete/` + una línea
|
||||
> `REGISTER_ENTITY`.** No se toca la factory ni el manager.
|
||||
|
||||
Aviso importante recogido en el código: para que el registrador se ejecute, el
|
||||
`.cpp` debe enlazarse directamente al ejecutable (no oculto en una static lib
|
||||
sin `--whole-archive`); todos los `concrete/*.cpp` entran en `APP_SOURCES`
|
||||
(`entity_factory.hpp:53`).
|
||||
|
||||
Tipos concretos existentes (`source/game/entities/concrete/`): `door`, `dust`,
|
||||
`floor_trap`, `hole`, `mummy`, `rolling_rock`, `sarcophagus`, `solid_props`,
|
||||
`torch`, `wall_trap`, `whip_attack`.
|
||||
|
||||
### 8.4. Jerarquía intermedia
|
||||
|
||||
Entre `Entity` y las concretas hay clases intermedias que aportan semántica:
|
||||
`decoration.hpp`, `solid_decoration.hpp`, `interactable.hpp`, `hazard.hpp`
|
||||
(con `DynamicHazard` para actores con fase de aparición). `Sarcophagus` hereda
|
||||
de `Interactable`, por ejemplo.
|
||||
|
||||
---
|
||||
|
||||
## 9. Mapa y niveles
|
||||
|
||||
### 9.1. `Map`: geometría y colisión (un namespace, no una clase)
|
||||
|
||||
`source/game/map/map.hpp`. **`Map` es un `namespace`**, no una clase, porque no
|
||||
hay estado de instancia: la geometría de la sala activa vive como variables
|
||||
`inline` del namespace (`map.hpp:32`). Se carga con `Map::loadFrom(yaml)` desde
|
||||
el bloque `geometry:` del YAML de la pirámide.
|
||||
|
||||
Contiene la geometría de la sala (graella de sarcófagos, *pitch*, anchos de
|
||||
muro, playfield, posición de la puerta `door_col`) y las funciones de colisión:
|
||||
|
||||
- `collides(box)` — ¿la caja choca con sarcófago, pasadizo de puerta cerrada o
|
||||
entidad sólida? (`map.hpp:164`).
|
||||
- `insidePlayfield(box)`, `isPaintable(col,row)`, `pixelToTile(px,py)`.
|
||||
|
||||
Para no acoplar a todos al namespace global, existe `Map::Info` — un *snapshot*
|
||||
POD copiable de la geometría + punteros a funciones de colisión, que se inyecta
|
||||
a entidades (hoy el `Player`, decoupling COUP-02; `map.hpp:201`).
|
||||
|
||||
### 9.2. `Tilemap`: prerenderizado una vez
|
||||
|
||||
`source/game/map/tilemap.hpp`. La cuadrícula de tiles 16×16 se **prerrenderiza
|
||||
una sola vez a un buffer ARGB propio** en el constructor; cada frame es una
|
||||
copia clipada al canvas (`tilemap.hpp:8`). Así no se recorre la cuadrícula cada
|
||||
frame. En Debug guarda además `data_` (la rejilla de índices) para el editor.
|
||||
|
||||
### 9.3. `LevelManager`: progresión
|
||||
|
||||
`source/game/levels/level_manager.hpp`. Meyers singleton, **fuente de verdad de
|
||||
la progresión**. Carga `data/maps/levels.yaml` (lista de salas con `pyramid`,
|
||||
`room`, `file`). API: `currentPath()`, `advance()`, `restart()`, `setIndex()`,
|
||||
`setStartingPoint(pyramid, room)`. No hace *loop* por sí mismo: la política la
|
||||
decide `GameScene` (`level_manager.hpp:49`).
|
||||
|
||||
Los mapas reales viven en `data/maps/p1/`…`p5/` (5 pirámides × ~6 salas), cada
|
||||
una con sus `rN.yaml` y un `pyramid.yaml` con la geometría.
|
||||
|
||||
---
|
||||
|
||||
## 10. Recursos
|
||||
|
||||
### 10.1. `Resource::Cache`
|
||||
|
||||
`source/core/resources/resource_cache.hpp`. Meyers singleton que cachea por
|
||||
nombre: `Surface`, animaciones, paletas, música (`Ja::Music`) y sonidos
|
||||
(`Ja::Sound`). API `loadX(name, path)` + `getX(name)`. La búsqueda es lineal
|
||||
sobre vectores (suficiente para el tamaño actual).
|
||||
|
||||
Tiene un `clearAudio()` que **el `Director` llama explícitamente** antes de
|
||||
destruir `Audio` y `SDL_Quit`, porque al ser Meyers el Cache se destruiría
|
||||
tarde, con los streams SDL ya invalidados (`resource_cache.hpp:43`).
|
||||
|
||||
### 10.2. Pack vs disco
|
||||
|
||||
`source/core/resources/resource_pack.*` + `resource_helper.*`. Toda lectura de
|
||||
fichero pasa por `Resource::Helper::loadFile(path)`. En **Release** los datos
|
||||
salen de `resources.pack` (empaquetado por `tools/pack_resources/` en cada
|
||||
build). En **Debug** se activa *fallback* a disco apuntando al árbol fuente, de
|
||||
modo que editar un YAML o un GIF en `data/` se ve sin re-empaquetar
|
||||
(`director.cpp:52`).
|
||||
|
||||
### 10.3. Formatos de arte
|
||||
|
||||
- **GIF** es el formato de sprites, tilesets y fuentes (`data/**/*.gif`),
|
||||
decodificado por `core/rendering/gif.*`. Cada GIF lleva su paleta.
|
||||
- **Fuentes**: pares `.fnt` + `.gif` (`data/fonts/`), consumidos por `Text`
|
||||
(`core/rendering/text.*`).
|
||||
- **Música**: `.ogg` decodificado con `stb_vorbis` (`source/external/`).
|
||||
- **Paletas sueltas**: `.pal` JASC o `.gif` (`palette_loader.*`,
|
||||
`data/palettes/`).
|
||||
|
||||
---
|
||||
|
||||
## 11. Configuración y constantes
|
||||
|
||||
### 11.1. La cadena YAML → `Options` → subsistema
|
||||
|
||||
El patrón es uniforme: un YAML en `data/config/` (o en la carpeta de sistema
|
||||
del usuario) se carga, y si falta una clave cae al valor compilado en
|
||||
`source/utils/defaults.hpp`. `defaults.hpp` es el **catálogo central de
|
||||
constantes** organizado en sub-namespaces (`Window`, `Video`, `Audio`,
|
||||
`KeysGame`, `KeysGameP2`, `I18n`, `Paths`, `UI`, …).
|
||||
|
||||
`source/game/options.*` + `options_apply.*` traducen esas opciones a structs
|
||||
tipados que los subsistemas consumen sin conocer YAML (p.ej.
|
||||
`Options::buildScreenConfig()` → `Screen::Config`). Esto mantiene `Screen`,
|
||||
`Audio`, etc. desacoplados de la capa de configuración.
|
||||
|
||||
Ficheros de configuración por categoría:
|
||||
- `data/config/`: `ui.yaml`, `input.yaml`, `lighting.yaml`, `sounds.yaml`,
|
||||
`editor.yaml`.
|
||||
- En la carpeta de sistema del usuario: `config.yaml`, `gameplay.yaml`,
|
||||
`gamepad_configs.yaml`, `debug.yaml` (Debug), y `shaders/postfx.yaml`,
|
||||
`shaders/crtpi.yaml`.
|
||||
|
||||
### 11.2. Carpeta de sistema por plataforma
|
||||
|
||||
`Director::createSystemFolder()` (`director.cpp:353`):
|
||||
- Windows: `%APPDATA%\jailgames\aee_arcade`
|
||||
- macOS: `~/Library/Application Support/jailgames/aee_arcade`
|
||||
- Linux: `$XDG_CONFIG_HOME/jailgames/aee_arcade` (por defecto
|
||||
`~/.config/jailgames/aee_arcade`)
|
||||
|
||||
Las capturas de pantalla y los presets de shader cuelgan de ahí.
|
||||
|
||||
### 11.3. Localización
|
||||
|
||||
`source/core/locale/locale.*` + `data/locale/{ca,en}.yaml`. Todo texto visible
|
||||
pasa por `Locale::get().text(clave)`; si falta el fichero o la clave, devuelve
|
||||
la propia clave como *fallback* (`director.cpp:124`). Idioma por defecto: `ca`
|
||||
(`defaults.hpp:41`).
|
||||
|
||||
---
|
||||
|
||||
## 12. Modo demo / IA
|
||||
|
||||
**No existe un modo demo, *attract mode*, autoplay ni IA que controle a los
|
||||
jugadores.** Lo he buscado explícitamente y no lo hay.
|
||||
|
||||
Aclaración para evitar malentendidos: la única aparición de la palabra "ai" en
|
||||
el código es un **bloque YAML `ai:`** en las plantillas de `mummy` y
|
||||
`rolling_rock`, que solo fija la **dirección inicial** del hazard:
|
||||
|
||||
```yaml
|
||||
ai:
|
||||
initial_direction: east # mummy.cpp:96, rolling_rock.cpp:41
|
||||
```
|
||||
|
||||
No es un sistema de IA: es un parámetro de spawn. El movimiento de las momias
|
||||
es lógica fija en `mummy.cpp` (patrullaje/persecución simple con consulta al
|
||||
mundo para no caer en agujeros), no un módulo de IA separado ni dirigido por el
|
||||
modo de juego.
|
||||
|
||||
Si en el futuro se quisiera un *attract mode*, el punto natural de enganche
|
||||
sería el flujo de escenas (`scene_flow.hpp`) y/o sustituir el *polling* de
|
||||
input del `Player` por una fuente sintética, pero **hoy nada de eso está
|
||||
implementado**.
|
||||
|
||||
---
|
||||
|
||||
## 13. Convenciones y patrones recurrentes
|
||||
|
||||
Patrones que verás una y otra vez (reconocerlos acelera mucho la lectura):
|
||||
|
||||
- **Idioma de los comentarios**: valenciano/catalán. Los identificadores son en
|
||||
inglés; las constantes locales suelen ir en `MAYÚSCULAS`.
|
||||
- **Estilo C++ moderno**: `[[nodiscard]]` en casi todo getter, trailing return
|
||||
types (`auto f() -> T`), `enum class` con tipo subyacente explícito,
|
||||
`std::optional`/`std::variant` en vez de punteros nulos o `void*`. Hay
|
||||
`.clang-format` y `.clang-tidy` en la raíz.
|
||||
- **Meyers singleton con ciclo de vida manual**: los singletons que dependen de
|
||||
SDL (`Screen`, `Audio`, `Cache` para audio) exponen `get()` pero su
|
||||
`configure()`/`shutdown()` los llama explícitamente el `Director`, **no** el
|
||||
destructor estático. El motivo (crashes en `vkDestroyInstance` al descargar
|
||||
Vulkan/Wayland antes de tiempo) está documentado en
|
||||
`screen.hpp:28` y `director.cpp:336`.
|
||||
- **Inyección de dependencias por `std::function` / setters** para invertir la
|
||||
dirección `core/` ↛ `game/` (ver §6.3).
|
||||
- **Data-driven con fallback compilado**: nunca asumas que un valor es una
|
||||
constante de código sin más; suele venir de un YAML con default en
|
||||
`defaults.hpp`.
|
||||
- **`std::any` solo en la frontera `Scene`↔`Director`** para no acoplar la base
|
||||
de escenas a los tipos del juego (§3.1).
|
||||
- **Composición sobre herencia para entidades** (§8.1).
|
||||
- **Separación render-de-colisión**: el `Tilemap` que se ve y `Map::collides`
|
||||
que bloquea son cosas distintas (un sarcófago se dibuja como actor pero el
|
||||
`Map` lo trata como obstáculo).
|
||||
- **Bloques `#ifdef _DEBUG`**: el editor de tilemap (`game/editor/`), los saltos
|
||||
de sala (`[`/`]`), forzar apertura de puerta, y el arranque configurable por
|
||||
`debug.yaml` solo existen en builds Debug.
|
||||
- **Tests**: en `tests/` hay smoke tests y tests de FSM/animación
|
||||
(`test_scene_flow`, `test_game_fsm`, `test_whip_attack`, etc.) con *stubs*
|
||||
para aislar dependencias pesadas (audio, player).
|
||||
- **Política de commits y branches**: ver `docs/convencions.md` (mensajes
|
||||
cortos en valenciano, trabajo en ramas de feature, merge `--no-ff`).
|
||||
|
||||
---
|
||||
|
||||
## 14. Guía de navegación: "si quieres tocar X, mira Y"
|
||||
|
||||
| Quiero… | Empieza por… |
|
||||
|---|---|
|
||||
| Entender el arranque y el orden de init | `source/core/system/director.cpp` (ctor + `iterate()`) |
|
||||
| Cambiar el orden update/render del frame | `Director::iterate()` (`director.cpp:509`) |
|
||||
| Añadir una escena | `source/game/scenes/scene_flow.hpp` + `Director::buildScene` |
|
||||
| Cómo se dibuja un sprite | `source/core/rendering/surface.cpp` (`renderWithColorReplace`) |
|
||||
| El orden de pintado del juego | `GameScene::render()` (`game_scene.cpp:1682`) |
|
||||
| El z-order / falsa perspectiva | `getFeetY()` + `stable_sort` en `game_scene.cpp:1755` |
|
||||
| Iluminación / antorchas | `core/rendering/lighting.*` + `data/config/lighting.yaml` |
|
||||
| Shaders / efecto CRT | `core/rendering/sdl3gpu/` + `data/shaders/` + `Options` |
|
||||
| Escalado de ventana / fullscreen | `core/rendering/screen.cpp` (`adjustWindowSize`, `setScalingMode`) |
|
||||
| Controles del jugador | `Player::Controls` (`player.hpp:23`) + `Player::tick` (`player.cpp`) |
|
||||
| Hotkeys de sistema (F1–F12, ESC) | `core/input/global_inputs.cpp` + `data/config/input.yaml` |
|
||||
| Menú de servicio (F9/SERVICE) | `source/game/ui/service_menu.*` |
|
||||
| Mandos / vinculaciones | `core/input/gamepad_manager.*`, `gamepad_bindings.*` |
|
||||
| Añadir un tipo de entidad | nueva clase en `game/entities/concrete/` + `REGISTER_ENTITY` |
|
||||
| Eventos entre entidades | `game/entities/event_bus.hpp` |
|
||||
| Lógica de una sala (intro/outro) | `RoomFsm` (`game/scenes/room_fsm.*`) + `data/scenes/game/game.yaml` |
|
||||
| Game over / continue | `GameOverFsm` (`game/scenes/game_over_fsm.*`) |
|
||||
| Sarcófagos / puerta / pintado | `concrete/sarcophagus.*`, `concrete/door.*`, `game/map/paint_layer.*` |
|
||||
| Geometría/colisión de la sala | `source/game/map/map.hpp` |
|
||||
| El tileset que se ve | `source/game/map/tilemap.*` + `data/tilesets/` |
|
||||
| Progresión de salas | `game/levels/level_manager.*` + `data/maps/levels.yaml` |
|
||||
| Editar mapas (Debug) | `source/game/editor/` |
|
||||
| Cargar un recurso | `core/resources/resource_cache.*` + `resource_helper.*` |
|
||||
| Empaquetar datos | `tools/pack_resources/` |
|
||||
| Valores por defecto / constantes | `source/utils/defaults.hpp` |
|
||||
| Textos / traducciones | `core/locale/locale.*` + `data/locale/*.yaml` |
|
||||
| HUD (vidas/score/tesoros) | `source/game/ui/hud.*` (escucha `player_stats_changed`) |
|
||||
| Audio (música/sfx) | `core/audio/` (`audio.*`, `jail_audio.*`) |
|
||||
|
||||
---
|
||||
|
||||
*Documento generado a partir de la lectura directa del código en el commit
|
||||
actual de la rama `main`. Si algo aquí no cuadra con el código, el código
|
||||
manda: actualiza este documento.*
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 361 KiB After Width: | Height: | Size: 361 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 164 KiB |
@@ -90,7 +90,6 @@ void Audio::playMusicInternal(Ja::Music* music, const int loop, const int crossf
|
||||
}
|
||||
|
||||
// Pausa la música (l'estat el transiciona Engine::pauseMusic)
|
||||
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_; unique_ptr::operator-> no propaga const
|
||||
void Audio::pauseMusic() {
|
||||
if (music_enabled_ && getMusicState() == MusicState::PLAYING) {
|
||||
engine_->pauseMusic();
|
||||
@@ -98,7 +97,6 @@ void Audio::pauseMusic() {
|
||||
}
|
||||
|
||||
// Continua la música pausada (l'estat el transiciona Engine::resumeMusic)
|
||||
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_; unique_ptr::operator-> no propaga const
|
||||
void Audio::resumeMusic() {
|
||||
if (music_enabled_ && getMusicState() == MusicState::PAUSED) {
|
||||
engine_->resumeMusic();
|
||||
@@ -106,14 +104,12 @@ void Audio::resumeMusic() {
|
||||
}
|
||||
|
||||
// Atura la música (l'estat el transiciona Engine::stopMusic)
|
||||
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_; unique_ptr::operator-> no propaga const
|
||||
void Audio::stopMusic() {
|
||||
if (music_enabled_) {
|
||||
engine_->stopMusic();
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-make-member-function-const)
|
||||
void Audio::setMusicSpeed(float ratio) {
|
||||
if (music_enabled_) {
|
||||
engine_->setMusicSpeed(ratio);
|
||||
@@ -121,7 +117,6 @@ void Audio::setMusicSpeed(float ratio) {
|
||||
}
|
||||
|
||||
// Reprodueix un so per nom
|
||||
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_; unique_ptr::operator-> no propaga const
|
||||
void Audio::playSound(const std::string& name, Group group) {
|
||||
if (sound_enabled_) {
|
||||
engine_->playSound(AudioResource::getSound(name), 0, static_cast<int>(group));
|
||||
@@ -129,7 +124,6 @@ void Audio::playSound(const std::string& name, Group group) {
|
||||
}
|
||||
|
||||
// Reprodueix un so per punter directe
|
||||
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_; unique_ptr::operator-> no propaga const
|
||||
void Audio::playSound(Ja::Sound* sound, Group group) {
|
||||
if (sound_enabled_ && sound != nullptr) {
|
||||
engine_->playSound(sound, 0, static_cast<int>(group));
|
||||
@@ -141,7 +135,6 @@ void Audio::playSound(Ja::Sound* sound, Group group) {
|
||||
// processa tot el sample amb el ratio des del primer pull del callback.
|
||||
// Si l'engine torna -1 (sense canal lliure) o el so no existeix, no fem
|
||||
// la crida al ratio — sense efectes col·laterals.
|
||||
// NOLINTNEXTLINE(readability-make-member-function-const)
|
||||
void Audio::playSound(const std::string& name, Group group, float speed) {
|
||||
if (!sound_enabled_) { return; }
|
||||
auto* sound = AudioResource::getSound(name);
|
||||
@@ -155,7 +148,6 @@ void Audio::playSound(const std::string& name, Group group, float speed) {
|
||||
// Reprodueix un so processat per un eco definit a sounds.yaml. Si el preset no
|
||||
// existeix o l'engine retorna -1 (cap de canals d'efecte plé), cau a playSound
|
||||
// sec — l'usuari sent el so encara que la cua no s'apliqui.
|
||||
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_
|
||||
void Audio::playSoundWithEcho(const std::string& name, const std::string& preset_name, Group group) {
|
||||
if (!sound_enabled_) { return; }
|
||||
auto* sound = AudioResource::getSound(name);
|
||||
@@ -175,7 +167,6 @@ void Audio::playSoundWithEcho(const std::string& name, const std::string& preset
|
||||
|
||||
// Reprodueix un so processat per un reverb definit a sounds.yaml. Mateix
|
||||
// fallback que playSoundWithEcho.
|
||||
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_
|
||||
void Audio::playSoundWithReverb(const std::string& name, const std::string& preset_name, Group group) {
|
||||
if (!sound_enabled_) { return; }
|
||||
auto* sound = AudioResource::getSound(name);
|
||||
@@ -194,7 +185,6 @@ void Audio::playSoundWithReverb(const std::string& name, const std::string& pres
|
||||
}
|
||||
|
||||
// Atura tots els sons
|
||||
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_; unique_ptr::operator-> no propaga const
|
||||
void Audio::stopAllSounds() {
|
||||
if (sound_enabled_) {
|
||||
engine_->stopChannel(-1);
|
||||
@@ -202,7 +192,6 @@ void Audio::stopAllSounds() {
|
||||
}
|
||||
|
||||
// Fa una fosa de sortida de la música
|
||||
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_; unique_ptr::operator-> no propaga const
|
||||
void Audio::fadeOutMusic(int milliseconds) {
|
||||
if (music_enabled_ && getMusicState() == MusicState::PLAYING) {
|
||||
engine_->fadeOutMusic(milliseconds);
|
||||
@@ -212,7 +201,6 @@ void Audio::fadeOutMusic(int milliseconds) {
|
||||
// Registra un callback que el motor dispararà quan la pista actual acabi de
|
||||
// drenar (times == 0 + stream buit). S'executa al mateix thread que
|
||||
// Audio::update (render loop); els consumidors no poden fer I/O blocant.
|
||||
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_
|
||||
void Audio::setOnMusicEnded(std::function<void()> callback) {
|
||||
if (engine_) { engine_->setOnMusicEnded(std::move(callback)); }
|
||||
}
|
||||
@@ -251,13 +239,11 @@ auto Audio::effectiveVolume(float volume, bool channel_enabled) const -> float {
|
||||
}
|
||||
|
||||
// Estableix el volum dels sons (float 0.0..1.0)
|
||||
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_; unique_ptr::operator-> no propaga const
|
||||
void Audio::setSoundVolume(float sound_volume, Group group) {
|
||||
engine_->setSoundVolume(effectiveVolume(sound_volume, sound_enabled_), static_cast<int>(group));
|
||||
}
|
||||
|
||||
// Estableix el volum de la música (float 0.0..1.0)
|
||||
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_; unique_ptr::operator-> no propaga const
|
||||
void Audio::setMusicVolume(float music_volume) {
|
||||
engine_->setMusicVolume(effectiveVolume(music_volume, music_enabled_));
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <functional> // Per a std::function
|
||||
#include <memory> // Per a std::unique_ptr
|
||||
#include <string> // Per a string
|
||||
#include <utility> // Per a move
|
||||
|
||||
// Forward-declares per no incloure core/audio/jail_audio.hpp al header. Els
|
||||
// tres símbols (Music/Sound per al punter que exposa la API i Engine per al
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// (TU aïllat perquè clang-analyzer no dispari fals positius al nostre codi).
|
||||
#define STB_VORBIS_HEADER_ONLY
|
||||
// clang-format off
|
||||
// NOLINTNEXTLINE(bugprone-suspicious-include)
|
||||
// NOLINTNEXTLINE(bugprone-suspicious-include) -- stb_vorbis és single-file: la macro de dalt limita aquest TU a només-declaracions; la implementació viu a external/stb_vorbis_impl.cpp.
|
||||
#include "external/stb_vorbis.c"
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -198,42 +198,50 @@ void DefineButtons::render() const {
|
||||
|
||||
// Títol: nom del mando.
|
||||
const std::string TITLE = pad_name_;
|
||||
title_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
|
||||
title_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG | Text::STROKE_FLAG,
|
||||
CENTER_X,
|
||||
cursor_y,
|
||||
TITLE,
|
||||
TITLE_KERNING,
|
||||
UiConfig::get().menuTitle());
|
||||
UiConfig::get().menuTitle(),
|
||||
1,
|
||||
UiConfig::get().menuTitleOutline());
|
||||
cursor_y += TITLE_H + LINE_GAP;
|
||||
|
||||
if (message_shown_) {
|
||||
// Pantalla final: missatge de configuració completa.
|
||||
items_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
|
||||
items_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG | Text::STROKE_FLAG,
|
||||
CENTER_X,
|
||||
cursor_y,
|
||||
Locale::get().text("menu.values.define_buttons_complete"),
|
||||
ITEM_KERNING,
|
||||
UiConfig::get().menuValue());
|
||||
UiConfig::get().menuValue(),
|
||||
1,
|
||||
UiConfig::get().menuValueOutline());
|
||||
return;
|
||||
}
|
||||
|
||||
// Instrucció + acció pendent. Replica visualment el WindowMessage de
|
||||
// coffee_crisis_arcade_edition.
|
||||
items_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
|
||||
items_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG | Text::STROKE_FLAG,
|
||||
CENTER_X,
|
||||
cursor_y,
|
||||
Locale::get().text("menu.values.press_button"),
|
||||
ITEM_KERNING,
|
||||
UiConfig::get().menuLabel());
|
||||
UiConfig::get().menuLabel(),
|
||||
1,
|
||||
UiConfig::get().menuLabelOutline());
|
||||
cursor_y += ITEM_H + LINE_GAP;
|
||||
|
||||
if (index_ < sequence_.size()) {
|
||||
const auto& current = sequence_[index_];
|
||||
items_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
|
||||
items_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG | Text::STROKE_FLAG,
|
||||
CENTER_X,
|
||||
cursor_y,
|
||||
Locale::get().text(current.label_key),
|
||||
ITEM_KERNING,
|
||||
UiConfig::get().menuCursor());
|
||||
UiConfig::get().menuCursor(),
|
||||
1,
|
||||
UiConfig::get().menuCursorOutline());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "core/input/gamepad_bindings.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
@@ -14,9 +14,8 @@
|
||||
//
|
||||
// El polling real de botons i eixos NO es fa a través d'aquesta classe:
|
||||
// els consumidors obtenen el `SDL_Gamepad*` primari (o l'enèsim per a
|
||||
// multi-jugador futur) i el passen a `InputState::getGamepadButton/Axis`,
|
||||
// que ho filtra pel flag de bloqueig de la UI (mateixa estratègia que
|
||||
// amb el teclat).
|
||||
// multi-jugador futur) i el passen directament a
|
||||
// `SDL_GetGamepadButton`/`SDL_GetGamepadAxis`.
|
||||
class GamepadManager {
|
||||
public:
|
||||
static auto get() -> GamepadManager&;
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
#include "core/input/key_config.hpp"
|
||||
#include "core/locale/locale.hpp"
|
||||
#include "core/rendering/notification_presets.hpp"
|
||||
#include "core/rendering/notifier.hpp"
|
||||
#include "core/rendering/render_info.hpp"
|
||||
#include "core/rendering/screen.hpp"
|
||||
#include "core/rendering/screenshot.hpp"
|
||||
#include "core/system/event_buffer.hpp"
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
#include "core/input/input_state.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace InputState {
|
||||
|
||||
namespace {
|
||||
|
||||
bool blocked = false;
|
||||
|
||||
// Buffer retornat quan el bloqueig és actiu. `SDL_GetKeyboardState`
|
||||
// retorna un pointer d'una mida igual a `SDL_SCANCODE_COUNT`; el
|
||||
// nostre buffer ha de ser com a mínim igual de gran perquè els
|
||||
// consumidors puguin indexar-lo amb qualsevol scancode sense
|
||||
// llegir fora de rang.
|
||||
auto zeroKeyboard() -> const bool* {
|
||||
static const std::array<bool, SDL_SCANCODE_COUNT> ZEROS{};
|
||||
return ZEROS.data();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void setBlocked(bool value) { blocked = value; }
|
||||
|
||||
auto isBlocked() -> bool { return blocked; }
|
||||
|
||||
auto getKeyboardState() -> const bool* {
|
||||
return blocked ? zeroKeyboard() : SDL_GetKeyboardState(nullptr);
|
||||
}
|
||||
|
||||
auto getGamepadButton(SDL_Gamepad* gp, SDL_GamepadButton b) -> bool {
|
||||
return (blocked || gp == nullptr) ? false : SDL_GetGamepadButton(gp, b);
|
||||
}
|
||||
|
||||
auto getGamepadAxis(SDL_Gamepad* gp, SDL_GamepadAxis ax) -> Sint16 {
|
||||
return (blocked || gp == nullptr) ? static_cast<Sint16>(0) : SDL_GetGamepadAxis(gp, ax);
|
||||
}
|
||||
|
||||
} // namespace InputState
|
||||
@@ -1,46 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
// Wrapper de l'estat d'input (polling) amb gating de bloqueig.
|
||||
//
|
||||
// El service menu (i qualsevol UI modal futura) ha de poder "amagar" l'estat
|
||||
// d'entrada als consumidors de polling — el jugador, entitats controlades
|
||||
// per l'usuari, etc. — mentre la UI captura l'atenció, sense interrompre la
|
||||
// resta del joc. Per això tots els consumidors de polling han de llegir a
|
||||
// través d'aquest mòdul i no cridar `SDL_GetKeyboardState` directament. Si
|
||||
// `setBlocked(true)` està actiu, el wrapper retorna un buffer tot-a-zero;
|
||||
// altrament, la crida és transparent (mateix pointer que SDL).
|
||||
//
|
||||
// El menú NO passa per aquí: consumeix events (`SDL_EVENT_KEY_DOWN`) abans
|
||||
// que l'escena els vegi, així que la seva navegació segueix funcionant per
|
||||
// molt que el flag estigui posat.
|
||||
//
|
||||
// El mateix patró aplica al gamepad: `getGamepadButton`/`getGamepadAxis`
|
||||
// retornen zero/false quan el bloqueig és actiu. Els mandos concrets els
|
||||
// obtén el caller via `GamepadManager::get().getPrimary()` o similars.
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
namespace InputState {
|
||||
|
||||
// Activa/desactiva el bloqueig global de polling. El ServiceMenu el
|
||||
// crida a `toggle()`/`close()`. No es toca des de cap altre lloc.
|
||||
void setBlocked(bool value);
|
||||
|
||||
[[nodiscard]] auto isBlocked() -> bool;
|
||||
|
||||
// Equivalent a `SDL_GetKeyboardState(nullptr)`: retorna un buffer de
|
||||
// `SDL_SCANCODE_COUNT` bools. Si l'input està bloquejat, tots zero —
|
||||
// el consumidor veu com si cap tecla estigués premuda, sense branques
|
||||
// explícites. El buffer és estable mentre dura la sessió; no cal
|
||||
// guardar-lo frame a frame.
|
||||
[[nodiscard]] auto getKeyboardState() -> const bool*;
|
||||
|
||||
// Versió filtrada de `SDL_GetGamepadButton`. Retorna false si el bloqueig
|
||||
// és actiu o si `gp` és nullptr (cas habitual: no hi ha cap mand).
|
||||
[[nodiscard]] auto getGamepadButton(SDL_Gamepad* gp, SDL_GamepadButton b) -> bool;
|
||||
|
||||
// Versió filtrada de `SDL_GetGamepadAxis`. Retorna 0 si el bloqueig
|
||||
// és actiu o si `gp` és nullptr.
|
||||
[[nodiscard]] auto getGamepadAxis(SDL_Gamepad* gp, SDL_GamepadAxis ax) -> Sint16;
|
||||
|
||||
} // namespace InputState
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "core/input/key_config.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#include "core/resources/resource_helper.hpp"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "core/locale/locale.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#include "core/resources/resource_helper.hpp"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <random>
|
||||
|
||||
class Surface;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "core/rendering/surface.hpp"
|
||||
#include "core/resources/resource_cache.hpp"
|
||||
|
||||
namespace Effects::Shadow {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
|
||||
#include "core/rendering/hud_constants.hpp"
|
||||
#include "core/rendering/screen.hpp"
|
||||
#include "core/rendering/text.hpp"
|
||||
#include "core/ui/ui_config.hpp"
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "core/rendering/hud_constants.hpp"
|
||||
#include "core/rendering/surface.hpp"
|
||||
#include "utils/defines.hpp"
|
||||
|
||||
class Text;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <ranges>
|
||||
|
||||
#include "core/rendering/canvas.hpp"
|
||||
#include "core/rendering/sdl3gpu/viewport.hpp"
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Screenshot {
|
||||
// Estat de mòdul: ruta base on s'escriu la subcarpeta
|
||||
// `screenshots/`. Buida per defecte ⇒ relativa al CWD (fallback
|
||||
// si el Director no ha pogut establir la ruta per plataforma).
|
||||
std::string g_base_dir; // NOLINT(*-avoid-non-const-global-variables)
|
||||
std::string g_base_dir; // NOLINT(*-avoid-non-const-global-variables) -- estat de mòdul (singleton-like): s'estableix una vegada al startup via setBaseDir() i es llegeix N vegades; encapsulat dins del namespace anònim, no és accessible des de fora.
|
||||
|
||||
// Construeix la ruta del directori on van les captures:
|
||||
// `<base>/screenshots/`. Si la base és buida, retorna
|
||||
|
||||
@@ -22,7 +22,7 @@ struct PostVOut {
|
||||
|
||||
struct PostFXUniforms {
|
||||
float vignette_strength;
|
||||
float chroma_strength;
|
||||
float chroma_min;
|
||||
float scanline_strength;
|
||||
float screen_height;
|
||||
float mask_strength;
|
||||
@@ -32,7 +32,7 @@ struct PostFXUniforms {
|
||||
float pixel_scale;
|
||||
float time;
|
||||
float flicker;
|
||||
float pad2;
|
||||
float chroma_max;
|
||||
// vec4 #3 — paràmetres de scanlines (exposats per preset YAML)
|
||||
float scan_dark_ratio;
|
||||
float scan_dark_floor;
|
||||
@@ -40,6 +40,18 @@ struct PostFXUniforms {
|
||||
float pad3;
|
||||
};
|
||||
|
||||
// Mostreig bilinear horitzontal d'un canal RGB. Evita el "tic-tac" del sampler
|
||||
// NEAREST quan l'offset de chroma és subpíxel.
|
||||
static float sampleBilinearX(float2 uv_target, int channel, texture2d<float> scene, sampler samp) {
|
||||
float2 tex_size = float2(scene.get_width(), scene.get_height());
|
||||
float px = uv_target.x * tex_size.x - 0.5f;
|
||||
float p_floor = floor(px);
|
||||
float f = px - p_floor;
|
||||
float4 c0 = scene.sample(samp, float2((p_floor + 0.5f) / tex_size.x, uv_target.y));
|
||||
float4 c1 = scene.sample(samp, float2((p_floor + 1.5f) / tex_size.x, uv_target.y));
|
||||
return mix(c0[channel], c1[channel], f);
|
||||
}
|
||||
|
||||
static float3 rgb_to_ycc(float3 rgb) {
|
||||
return float3(
|
||||
0.299f*rgb.r + 0.587f*rgb.g + 0.114f*rgb.b,
|
||||
@@ -94,9 +106,14 @@ fragment float4 postfx_fs(PostVOut in [[stage_in]],
|
||||
colour = base;
|
||||
}
|
||||
|
||||
float ca = u.chroma_strength * 0.005f * (1.0f + 0.15f * sin(u.time * 7.3f));
|
||||
colour.r = scene.sample(samp, uv + float2(ca, 0.0f)).r;
|
||||
colour.b = scene.sample(samp, uv - float2(ca, 0.0f)).b;
|
||||
// Chroma — varia entre chroma_min i chroma_max via sinusoidal; si min == max
|
||||
// queda estàtic. Mostreig bilinear horitzontal per evitar el "tic-tac" del
|
||||
// NEAREST sampler amb offsets subpíxel.
|
||||
if (u.chroma_min > 0.0f || u.chroma_max > 0.0f) {
|
||||
float ca = mix(u.chroma_min, u.chroma_max, 0.5f + 0.5f * sin(u.time * 7.3f)) * 0.005f;
|
||||
colour.r = sampleBilinearX(uv + float2(ca, 0.0f), 0, scene, samp);
|
||||
colour.b = sampleBilinearX(uv - float2(ca, 0.0f), 2, scene, samp);
|
||||
}
|
||||
|
||||
if (u.gamma_strength > 0.0f) {
|
||||
float3 lin = pow(colour, float3(2.4f));
|
||||
|
||||
@@ -234,7 +234,8 @@ namespace Rendering {
|
||||
void SDL3GPUShader::setPostFXParams(const PostFXParams& params) {
|
||||
uniforms_.vignette_strength = params.vignette;
|
||||
uniforms_.scanline_strength = params.scanlines;
|
||||
uniforms_.chroma_strength = params.chroma;
|
||||
uniforms_.chroma_min = params.chroma_min;
|
||||
uniforms_.chroma_max = params.chroma_max;
|
||||
uniforms_.mask_strength = params.mask;
|
||||
uniforms_.gamma_strength = params.gamma;
|
||||
uniforms_.curvature = params.curvature;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
struct PostFXUniforms {
|
||||
// vec4 #0
|
||||
float vignette_strength;
|
||||
float chroma_strength;
|
||||
float chroma_min;
|
||||
float scanline_strength;
|
||||
float screen_height;
|
||||
// vec4 #1
|
||||
@@ -27,7 +27,7 @@ struct PostFXUniforms {
|
||||
float pixel_scale;
|
||||
float time;
|
||||
float flicker;
|
||||
float pad2;
|
||||
float chroma_max;
|
||||
// vec4 #3 — paràmetres de scanlines exposats al preset YAML
|
||||
float scan_dark_ratio;
|
||||
float scan_dark_floor;
|
||||
@@ -92,7 +92,7 @@ namespace Rendering {
|
||||
|
||||
PostFXUniforms uniforms_{
|
||||
.vignette_strength = 0.0F,
|
||||
.chroma_strength = 0.0F,
|
||||
.chroma_min = 0.0F,
|
||||
.scanline_strength = 0.0F,
|
||||
.screen_height = 0.0F,
|
||||
.mask_strength = 0.0F,
|
||||
@@ -102,7 +102,7 @@ namespace Rendering {
|
||||
.pixel_scale = 1.0F,
|
||||
.time = 0.0F,
|
||||
.flicker = 0.0F,
|
||||
.pad2 = 0.0F,
|
||||
.chroma_max = 0.0F,
|
||||
// Defaults canònics: 2/3 brillant + 1/3 fosca, brillantor 42%, suavitzat complet (1 px físic).
|
||||
.scan_dark_ratio = 0.333F,
|
||||
.scan_dark_floor = 0.42F,
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -19,7 +19,10 @@ namespace Rendering {
|
||||
struct PostFXParams {
|
||||
float vignette{0.0F};
|
||||
float scanlines{0.0F};
|
||||
float chroma{0.0F};
|
||||
// Aberració cromàtica — varia entre min i max via sinusoidal; si coincideixen
|
||||
// queda estàtica. Permet un "floor" perquè la imatge mai sigui lliure de chroma.
|
||||
float chroma_min{0.0F};
|
||||
float chroma_max{0.0F};
|
||||
float mask{0.0F};
|
||||
float gamma{0.0F};
|
||||
float curvature{0.0F};
|
||||
|
||||
@@ -62,7 +62,7 @@ Surface::Surface(const std::string& file_path) {
|
||||
palette_[i] = Rendering::OPAQUE_ALPHA | rgb[i];
|
||||
}
|
||||
|
||||
printWithDots("Surface", getFileName(file_path), "[ LOADED ]");
|
||||
printStatus("Surface", getFileName(file_path), StatusKind::SUCCESS);
|
||||
}
|
||||
|
||||
void Surface::clear(Uint8 color) {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
|
||||
@@ -101,7 +101,7 @@ auto Text::loadTextFile(const std::string& file_path) -> std::shared_ptr<File> {
|
||||
}
|
||||
}
|
||||
|
||||
printWithDots("Text File", getFileName(file_path), "[ LOADED ]");
|
||||
printStatus("Text File", getFileName(file_path), StatusKind::SUCCESS);
|
||||
return tf;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,13 +37,13 @@ namespace Resource {
|
||||
throw std::runtime_error("Cache: cannot load animation " + file_path);
|
||||
}
|
||||
animations_.emplace_back(AnimationResource{.name = name, .yaml_data = std::move(bytes)});
|
||||
printWithDots("Animation", getFileName(file_path), "[ LOADED ]");
|
||||
printStatus("Animation", getFileName(file_path), StatusKind::SUCCESS);
|
||||
}
|
||||
|
||||
void Cache::loadPalette(const std::string& name, const std::string& file_path) {
|
||||
auto palette = PaletteLoader::loadFromFile(file_path);
|
||||
palettes_.emplace_back(PaletteResource{.name = name, .palette = palette});
|
||||
printWithDots("Palette", getFileName(file_path), "[ LOADED ]");
|
||||
printStatus("Palette", getFileName(file_path), StatusKind::SUCCESS);
|
||||
}
|
||||
|
||||
auto Cache::getSurface(const std::string& name) -> std::shared_ptr<Surface> {
|
||||
@@ -83,7 +83,7 @@ namespace Resource {
|
||||
throw std::runtime_error("Cache: Ja::loadMusic failed for " + file_path);
|
||||
}
|
||||
musics_.emplace_back(MusicResource{.name = name, .music = std::unique_ptr<Ja::Music, MusicDeleter>(raw)});
|
||||
printWithDots("Music", getFileName(file_path), "[ LOADED ]");
|
||||
printStatus("Music", getFileName(file_path), StatusKind::SUCCESS);
|
||||
}
|
||||
|
||||
void Cache::loadSound(const std::string& name, const std::string& file_path) {
|
||||
@@ -96,7 +96,7 @@ namespace Resource {
|
||||
throw std::runtime_error("Cache: Ja::loadSound failed for " + file_path);
|
||||
}
|
||||
sounds_.emplace_back(SoundResource{.name = name, .sound = std::unique_ptr<Ja::Sound, SoundDeleter>(raw)});
|
||||
printWithDots("Sound", getFileName(file_path), "[ LOADED ]");
|
||||
printStatus("Sound", getFileName(file_path), StatusKind::SUCCESS);
|
||||
}
|
||||
|
||||
auto Cache::getMusic(const std::string& name) -> Ja::Music* {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <numeric>
|
||||
|
||||
#include "utils/log.hpp"
|
||||
|
||||
@@ -19,12 +20,7 @@ namespace Resource {
|
||||
auto Pack::calculateChecksum(const std::vector<std::uint8_t>& data) -> std::uint32_t {
|
||||
// djb2: seed 0x12345678. Compatible amb aee/projecte_2026 per si mai
|
||||
// reutilitzem eines de diagnòstic entre projectes.
|
||||
std::uint32_t checksum = 0x12345678;
|
||||
for (unsigned char b : data) {
|
||||
// cppcheck-suppress useStlAlgorithm
|
||||
checksum = ((checksum << 5) + checksum) + b;
|
||||
}
|
||||
return checksum;
|
||||
return std::accumulate(data.begin(), data.end(), std::uint32_t{0x12345678}, [](std::uint32_t acc, unsigned char b) { return ((acc << 5) + acc) + b; });
|
||||
}
|
||||
|
||||
void Pack::xorInPlace(std::vector<std::uint8_t>& data, const std::string& key) {
|
||||
@@ -151,7 +147,7 @@ namespace Resource {
|
||||
return false;
|
||||
}
|
||||
|
||||
// cppcheck-suppress useStlAlgorithm
|
||||
// cppcheck-suppress useStlAlgorithm -- bucle amb early-return en error (addFile fallit) + filtre amb continue: no es pot expressar net amb std::ranges::all_of perquè la lògica combina filtratge i acumulació amb sortida prematura.
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(directory)) {
|
||||
if (!entry.is_regular_file()) { continue; }
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#include "core/audio/audio.hpp"
|
||||
#include "core/audio/sound_effects_config.hpp"
|
||||
@@ -24,7 +23,6 @@
|
||||
#include "core/rendering/render_info.hpp"
|
||||
#include "core/rendering/screen.hpp"
|
||||
#include "core/rendering/screenshot.hpp"
|
||||
#include "core/rendering/surface.hpp"
|
||||
#include "core/rendering/text.hpp"
|
||||
#include "core/resources/resource_cache.hpp"
|
||||
#include "core/resources/resource_helper.hpp"
|
||||
@@ -35,8 +33,8 @@
|
||||
#include "game/gameplay_config.hpp"
|
||||
#ifdef _DEBUG
|
||||
#include "game/editor/editor_config.hpp"
|
||||
#endif
|
||||
#include "game/levels/level_manager.hpp"
|
||||
#endif
|
||||
#include "game/options.hpp"
|
||||
#include "game/scenes/game_scene.hpp"
|
||||
#include "game/scenes/intro_scene.hpp"
|
||||
@@ -446,9 +444,12 @@ void Director::processGamepadEvents() {
|
||||
}
|
||||
|
||||
void Director::processMenuInput() {
|
||||
// Intercepta input abans que l'escena vegi els events. Si el menú està
|
||||
// obert, consumeix TOTS els KEY_DOWN perquè ni GlobalInputs (F1–F8,
|
||||
// F11, F12) ni el jugador els veuen. F9 obre el menú quan està tancat.
|
||||
// Intercepta input abans que l'escena vegi els events. Amb el menú
|
||||
// obert, `handleEvent` consumeix tot KEY_DOWN excepte les F-keys
|
||||
// (F1–F12) i ESC, que són hotkeys d'UI omnipresents i passen avall
|
||||
// a GlobalInputs (ESC manté la doble pulsació per a confirmar l'exit
|
||||
// independentment del context). Quan el menú està tancat, la branca
|
||||
// de baix detecta `menu_toggle` (F12 per defecte) i obre el menú.
|
||||
// Erase invalida iteradors, per això actualitzem `it` a cada branca.
|
||||
for (auto it = EventBuffer::events.begin(); it != EventBuffer::events.end();) {
|
||||
if (ServiceMenu::get().isOpen() && ServiceMenu::get().handleEvent(*it)) {
|
||||
@@ -465,16 +466,6 @@ void Director::processMenuInput() {
|
||||
|
||||
void Director::switchSceneIfRequested() {
|
||||
if (!current_scene_) { return; }
|
||||
// Prioritat al menú: si un item del ServiceMenu ha encuat una
|
||||
// transició (típicament "Reiniciar" → LOGO amb reset), s'aplica
|
||||
// primer. Substitueix l'`ExitRequest` que pugui haver publicat
|
||||
// l'escena en aquest mateix frame — l'usuari ha cancel·lat l'estat
|
||||
// de joc explícitament i no ens interessa mantenir-ne la sortida.
|
||||
if (auto menu_req = ServiceMenu::get().takePendingSceneRequest(); menu_req.has_value()) {
|
||||
Log::info("Director", "scene transition (menu) → {}", toString(menu_req->next));
|
||||
current_scene_ = buildScene(menu_req->next, menu_req->params);
|
||||
return;
|
||||
}
|
||||
// L'escena ha publicat un `ExitRequest` dins de `exit_request_`.
|
||||
// Director el desempaqueta i construeix la nova escena via
|
||||
// `buildScene`. Si la `std::any_cast` falla (tipus inesperat), el
|
||||
@@ -497,10 +488,8 @@ auto Director::buildScene(SceneId id, const SceneParams& params) -> std::unique_
|
||||
// ctors antics no l'utilitzen directament. Quan totes estiguen
|
||||
// migrades a `ExitRequest`, aquestes lambdes desapareixeran.
|
||||
switch (id) {
|
||||
case SceneId::LOGO: {
|
||||
const auto& p = std::get<LogoParams>(params);
|
||||
return std::make_unique<LogoScene>(logo_full_surface_, p.reset);
|
||||
}
|
||||
case SceneId::LOGO:
|
||||
return std::make_unique<LogoScene>(logo_full_surface_);
|
||||
case SceneId::TITLE:
|
||||
return std::make_unique<TitleScene>(placeholder_text_, copyright_text_, title_bg_surface_, game_logo_surface_, mini_logo_surface_);
|
||||
case SceneId::INTRO: {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// relaunch.cpp - Implementació del reinici en calent
|
||||
|
||||
#include "core/system/relaunch.hpp"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <process.h> // _execv
|
||||
#else
|
||||
#include <unistd.h> // execv
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
// Estat global (process-scope). Aquesta TU és l'única que gestiona el
|
||||
// reinici, així que els statics interns no s'escapen.
|
||||
char** g_argv = nullptr;
|
||||
bool g_requested = false;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace System::Relaunch {
|
||||
|
||||
void setArgv(int /*argc*/, char** argv) {
|
||||
g_argv = argv;
|
||||
}
|
||||
|
||||
void request() {
|
||||
g_requested = true;
|
||||
}
|
||||
|
||||
void execIfRequested() {
|
||||
if (!g_requested || g_argv == nullptr || g_argv[0] == nullptr) {
|
||||
return;
|
||||
}
|
||||
std::cout << "[Relaunch] Reiniciant " << g_argv[0] << "...\n";
|
||||
#ifdef _WIN32
|
||||
_execv(g_argv[0], g_argv);
|
||||
#else
|
||||
execv(g_argv[0], g_argv);
|
||||
#endif
|
||||
// Si arribem aquí, execv ha fallat. Tots els subsistemes ja estan
|
||||
// destruïts; sortim amb error i el shell rebrà el codi.
|
||||
std::cerr << "[Relaunch] Ha fallat: " << std::strerror(errno) << '\n';
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
} // namespace System::Relaunch
|
||||
@@ -0,0 +1,28 @@
|
||||
// relaunch.hpp - Reinici en calent del procés (execv)
|
||||
//
|
||||
// Helper desacoblat perquè el menú de servei demani un reinici sense conèixer
|
||||
// ni Director ni main.cpp. Patró d'ús:
|
||||
//
|
||||
// main() → Relaunch::setArgv(argc, argv) (a l'arrencada)
|
||||
// ServiceMenu → Relaunch::request() (en activar "Reiniciar")
|
||||
// main() → Relaunch::execIfRequested() (al final de SDL_AppQuit)
|
||||
//
|
||||
// L'execv() reemplaça el procés actual: si torna, ha fallat. Es crida després
|
||||
// de destruir el Director per garantir que GPU/àudio/SDL ja són morts.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace System::Relaunch {
|
||||
|
||||
// Emmagatzema l'argv original. Cal cridar-ho una vegada des de main.
|
||||
void setArgv(int argc, char** argv);
|
||||
|
||||
// Demana un reinici (no actua immediatament; només marca el flag).
|
||||
void request();
|
||||
|
||||
// Si hi ha reinici demanat i tenim argv vàlid, fa execv. En cas d'èxit
|
||||
// no torna. Si execv falla, registra l'error per stderr i surt amb
|
||||
// EXIT_FAILURE (no podem reprendre — els subsistemes ja estan destruïts).
|
||||
void execIfRequested();
|
||||
|
||||
} // namespace System::Relaunch
|
||||
@@ -81,6 +81,10 @@ UiConfig::UiConfig()
|
||||
menu_value_(Defaults::UI::MENU_VALUE),
|
||||
menu_cursor_(Defaults::UI::MENU_CURSOR),
|
||||
menu_disabled_(Defaults::UI::MENU_DISABLED),
|
||||
menu_title_outline_(Defaults::UI::MENU_TITLE_OUTLINE),
|
||||
menu_label_outline_(Defaults::UI::MENU_LABEL_OUTLINE),
|
||||
menu_value_outline_(Defaults::UI::MENU_VALUE_OUTLINE),
|
||||
menu_cursor_outline_(Defaults::UI::MENU_CURSOR_OUTLINE),
|
||||
hud_bg_(Defaults::UI::HUD_BG),
|
||||
hud_p1_label_(Defaults::UI::HUD_P1_LABEL),
|
||||
hud_p1_value_(Defaults::UI::HUD_P1_VALUE),
|
||||
@@ -215,19 +219,38 @@ void UiConfig::load(const std::string& file_path) {
|
||||
readColorIndex(c, "notification_bg", notification_bg_);
|
||||
readColorIndex(c, "exit_text", exit_text_);
|
||||
readColorIndex(c, "exit_bg", exit_bg_);
|
||||
readColorIndex(c, "menu_bg", menu_bg_);
|
||||
readColorIndex(c, "menu_border", menu_border_);
|
||||
readColorIndex(c, "menu_title", menu_title_);
|
||||
readColorIndex(c, "menu_label", menu_label_);
|
||||
readColorIndex(c, "menu_value", menu_value_);
|
||||
readColorIndex(c, "menu_cursor", menu_cursor_);
|
||||
readColorIndex(c, "menu_disabled", menu_disabled_);
|
||||
readColorIndex(c, "hud_bg", hud_bg_);
|
||||
readColorIndex(c, "hud_flash", hud_flash_);
|
||||
readColorIndex(c, "room_text", room_text_);
|
||||
readColorIndex(c, "room_text_outline", room_text_outline_);
|
||||
}
|
||||
|
||||
// Tema actiu del menú: `menu_theme` és el nom del bloc dins de
|
||||
// `menu_themes` que aporta els 7 slots de color. Si qualsevol
|
||||
// de les dues claus manca o el nom no existeix, els slots
|
||||
// queden amb el default compilat (= tema "classic" hardcoded
|
||||
// a Defaults::UI). Per-key dins del tema també és opcional.
|
||||
if (yaml.contains("menu_theme") && yaml.contains("menu_themes")) {
|
||||
const auto THEME_NAME = yaml["menu_theme"].get_value<std::string>();
|
||||
const auto& themes = yaml["menu_themes"];
|
||||
if (themes.contains(THEME_NAME)) {
|
||||
const auto& t = themes[THEME_NAME];
|
||||
readColorIndex(t, "bg", menu_bg_);
|
||||
readColorIndex(t, "border", menu_border_);
|
||||
readColorIndex(t, "title", menu_title_);
|
||||
readColorIndex(t, "label", menu_label_);
|
||||
readColorIndex(t, "value", menu_value_);
|
||||
readColorIndex(t, "cursor", menu_cursor_);
|
||||
readColorIndex(t, "disabled", menu_disabled_);
|
||||
readColorIndex(t, "title_outline", menu_title_outline_);
|
||||
readColorIndex(t, "label_outline", menu_label_outline_);
|
||||
readColorIndex(t, "value_outline", menu_value_outline_);
|
||||
readColorIndex(t, "cursor_outline", menu_cursor_outline_);
|
||||
} else {
|
||||
Log::warn("UiConfig", "menu_theme '{}' no existeix a menu_themes — s'usen defaults", THEME_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
// Famílies de color del HUD: blocs `hud_p1`, `hud_center`,
|
||||
// `hud_p2` amb claus `label`, `value`, `shadow`. Cada bloc i
|
||||
// cada clau són opcionals — el que manqui cau al default
|
||||
|
||||
@@ -65,6 +65,10 @@ class UiConfig {
|
||||
[[nodiscard]] auto menuValue() const -> Uint8 { return menu_value_; }
|
||||
[[nodiscard]] auto menuCursor() const -> Uint8 { return menu_cursor_; }
|
||||
[[nodiscard]] auto menuDisabled() const -> Uint8 { return menu_disabled_; }
|
||||
[[nodiscard]] auto menuTitleOutline() const -> Uint8 { return menu_title_outline_; }
|
||||
[[nodiscard]] auto menuLabelOutline() const -> Uint8 { return menu_label_outline_; }
|
||||
[[nodiscard]] auto menuValueOutline() const -> Uint8 { return menu_value_outline_; }
|
||||
[[nodiscard]] auto menuCursorOutline() const -> Uint8 { return menu_cursor_outline_; }
|
||||
[[nodiscard]] auto hudBg() const -> Uint8 { return hud_bg_; }
|
||||
// Famílies de color del HUD per bloc (LEFT/CENTER/RIGHT). Cada
|
||||
// família té label (etiqueta apagada), value (valor destacat) i
|
||||
@@ -214,6 +218,10 @@ class UiConfig {
|
||||
Uint8 menu_value_;
|
||||
Uint8 menu_cursor_;
|
||||
Uint8 menu_disabled_;
|
||||
Uint8 menu_title_outline_;
|
||||
Uint8 menu_label_outline_;
|
||||
Uint8 menu_value_outline_;
|
||||
Uint8 menu_cursor_outline_;
|
||||
Uint8 hud_bg_;
|
||||
Uint8 hud_p1_label_;
|
||||
Uint8 hud_p1_value_;
|
||||
|
||||
@@ -345,9 +345,7 @@ namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
// cppcheck-suppress constParameterReference
|
||||
// Tilemap& mutable per simetria amb `toggle()` i per futures
|
||||
// invalidacions de caches d'edició, encara que `save()` sigui const.
|
||||
// cppcheck-suppress constParameterReference -- Tilemap& mutable per simetria amb `toggle()` i per futures invalidacions de caches d'edició; cppcheck no pot anticipar que les futures mutacions estan previstes en el contracte.
|
||||
void Editor::exit(Tilemap& tilemap, const EntityManager& entities) {
|
||||
if (!active_) { return; }
|
||||
// Constraint del nivell: cal exactament una porta i com a mínim un
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "game/entities/animation_component.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
@@ -33,8 +35,7 @@ namespace {
|
||||
if (speeds.empty()) {
|
||||
return std::vector<float>(frame_count, K_DEFAULT_FRAME_SECONDS);
|
||||
}
|
||||
// cppcheck-suppress useStlAlgorithm
|
||||
for (auto& s : speeds) { s = sanitizeSpeed(s); }
|
||||
std::ranges::transform(speeds, speeds.begin(), sanitizeSpeed);
|
||||
if (speeds.size() == frame_count) { return speeds; }
|
||||
if (speeds.size() > frame_count) {
|
||||
speeds.resize(frame_count);
|
||||
@@ -50,10 +51,7 @@ namespace {
|
||||
// component vàlid.
|
||||
auto parseFrames(const fkyaml::node& anim) -> std::vector<int> {
|
||||
std::vector<int> frames;
|
||||
for (const auto& f : anim["frames"]) {
|
||||
// cppcheck-suppress useStlAlgorithm
|
||||
frames.push_back(f.get_value<int>());
|
||||
}
|
||||
std::ranges::transform(anim["frames"], std::back_inserter(frames), [](const fkyaml::node& f) { return f.template get_value<int>(); });
|
||||
if (frames.empty()) { frames.push_back(0); }
|
||||
return frames;
|
||||
}
|
||||
@@ -66,10 +64,7 @@ namespace {
|
||||
std::vector<float> speeds_secs;
|
||||
const auto& fm = anim["frame_ms"];
|
||||
if (fm.is_sequence()) {
|
||||
for (const auto& v : fm) {
|
||||
// cppcheck-suppress useStlAlgorithm
|
||||
speeds_secs.push_back(static_cast<float>(v.get_value<int>()) / 1000.0F);
|
||||
}
|
||||
std::ranges::transform(fm, std::back_inserter(speeds_secs), [](const fkyaml::node& v) { return static_cast<float>(v.template get_value<int>()) / 1000.0F; });
|
||||
} else {
|
||||
const float SECS = static_cast<float>(fm.get_value<int>()) / 1000.0F;
|
||||
speeds_secs.assign(frame_count, SECS);
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "game/entities/concrete/mummy.hpp"
|
||||
#include "game/entities/entity_factory.hpp"
|
||||
#include "game/entities/entity_manager.hpp"
|
||||
#include "game/entities/player.hpp"
|
||||
@@ -41,10 +41,7 @@ namespace {
|
||||
if (!yaml.contains("animations") || !yaml["animations"].contains("swing")) { return; }
|
||||
const auto& sw = yaml["animations"]["swing"];
|
||||
if (!sw.contains("active_frames")) { return; }
|
||||
for (const auto& f : sw["active_frames"]) {
|
||||
// cppcheck-suppress useStlAlgorithm
|
||||
active_out.push_back(f.get_value<int>());
|
||||
}
|
||||
std::ranges::transform(sw["active_frames"], std::back_inserter(active_out), [](const fkyaml::node& f) { return f.template get_value<int>(); });
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -64,7 +64,8 @@ class EntityFactory {
|
||||
}); \
|
||||
} \
|
||||
}; \
|
||||
const TAG##_Registrar g_##TAG##_registrar; /* NOLINT(cert-err58-cpp) */ \
|
||||
/* NOLINTNEXTLINE(cert-err58-cpp) -- inicialització estàtica intencionada: el constructor registra el creator a l'EntityFactory abans de main(); el patró és l'únic mecanisme C++ que permet auto-registre per tipus sense modificar la factory. */ \
|
||||
const TAG##_Registrar g_##TAG##_registrar; \
|
||||
}
|
||||
|
||||
#define REGISTER_ENTITY(NAME, CLASS) REGISTER_ENTITY_AS(NAME, CLASS, CLASS)
|
||||
@@ -1,11 +1,14 @@
|
||||
#include "game/entities/entity_loader.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "core/resources/resource_helper.hpp"
|
||||
#include "game/entities/entity.hpp"
|
||||
#include "game/entities/entity_factory.hpp"
|
||||
#include "game/map/room_yaml.hpp"
|
||||
#include "utils/defaults.hpp"
|
||||
|
||||
namespace EntityLoader {
|
||||
@@ -44,20 +47,13 @@ namespace EntityLoader {
|
||||
}
|
||||
|
||||
auto readSpawns(const std::string& level_yaml_path) -> std::vector<fkyaml::node> {
|
||||
auto bytes = Resource::Helper::loadFile(level_yaml_path);
|
||||
if (bytes.empty()) {
|
||||
throw std::runtime_error("EntityLoader: no s'ha pogut obrir " + level_yaml_path);
|
||||
}
|
||||
const auto* begin = reinterpret_cast<const char*>(bytes.data());
|
||||
const auto* end = begin + bytes.size();
|
||||
fkyaml::node root = fkyaml::node::deserialize(begin, end);
|
||||
fkyaml::node root = RoomYaml::loadMerged(level_yaml_path);
|
||||
std::vector<fkyaml::node> spawns;
|
||||
if (!root.contains("entities")) { return spawns; }
|
||||
const auto& list = root["entities"];
|
||||
if (!list.is_sequence()) { return spawns; }
|
||||
spawns.reserve(list.size());
|
||||
// cppcheck-suppress useStlAlgorithm
|
||||
for (const auto& item : list) { spawns.push_back(item); }
|
||||
std::ranges::copy(list, std::back_inserter(spawns));
|
||||
return spawns;
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ auto EntityManager::add(std::unique_ptr<Entity> entity) -> Entity* {
|
||||
entity->setEntityManager(this);
|
||||
Entity* raw = entity.get();
|
||||
entities_.push_back(std::move(entity));
|
||||
// cppcheck-suppress returnDanglingLifetime
|
||||
// cppcheck-suppress returnDanglingLifetime -- fals positiu: std::move transfereix ownership del unique_ptr al vector, però l'objecte apuntat per `raw` no es mou ni es destrueix. El punter continua vàlid mentre el vector el posseesca.
|
||||
return raw;
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ auto EntityManager::addSarcophagus(std::unique_ptr<Sarcophagus> sarc) -> Sarcoph
|
||||
Sarcophagus* raw = sarc.get();
|
||||
entities_.push_back(std::move(sarc));
|
||||
++total_sarcs_;
|
||||
// cppcheck-suppress returnDanglingLifetime
|
||||
// cppcheck-suppress returnDanglingLifetime -- fals positiu: std::move transfereix ownership al vector però l'objecte no es mou. `raw` continua vàlid (vegeu `add` més amunt).
|
||||
return raw;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "core/rendering/surface.hpp"
|
||||
#include "core/resources/resource_cache.hpp"
|
||||
#include "external/fkyaml_node.hpp"
|
||||
#include "game/entities/concrete/door.hpp"
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
|
||||
@@ -11,12 +11,9 @@
|
||||
#include "core/audio/audio.hpp"
|
||||
#include "core/input/gamepad_bindings.hpp"
|
||||
#include "core/input/gamepad_manager.hpp"
|
||||
#include "core/input/input_state.hpp"
|
||||
#include "core/rendering/canvas.hpp"
|
||||
#include "core/rendering/effects/shadow.hpp"
|
||||
#include "core/rendering/screen.hpp"
|
||||
#include "core/rendering/surface.hpp"
|
||||
#include "core/resources/palette_loader.hpp"
|
||||
#include "core/resources/resource_cache.hpp"
|
||||
#include "core/resources/resource_helper.hpp"
|
||||
#include "external/fkyaml_node.hpp"
|
||||
@@ -46,12 +43,12 @@ namespace {
|
||||
void accumulateGamepadMovement(int player_id, float& vx, float& vy) {
|
||||
SDL_Gamepad* pad = GamepadManager::get().getForPlayer(player_id);
|
||||
if (pad == nullptr) { return; }
|
||||
if (InputState::getGamepadButton(pad, SDL_GAMEPAD_BUTTON_DPAD_LEFT)) { vx -= SPEED_PX_PER_S; }
|
||||
if (InputState::getGamepadButton(pad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT)) { vx += SPEED_PX_PER_S; }
|
||||
if (InputState::getGamepadButton(pad, SDL_GAMEPAD_BUTTON_DPAD_UP)) { vy -= SPEED_PX_PER_S; }
|
||||
if (InputState::getGamepadButton(pad, SDL_GAMEPAD_BUTTON_DPAD_DOWN)) { vy += SPEED_PX_PER_S; }
|
||||
const Sint16 LX = InputState::getGamepadAxis(pad, SDL_GAMEPAD_AXIS_LEFTX);
|
||||
const Sint16 LY = InputState::getGamepadAxis(pad, SDL_GAMEPAD_AXIS_LEFTY);
|
||||
if (SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_DPAD_LEFT)) { vx -= SPEED_PX_PER_S; }
|
||||
if (SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT)) { vx += SPEED_PX_PER_S; }
|
||||
if (SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_DPAD_UP)) { vy -= SPEED_PX_PER_S; }
|
||||
if (SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_DPAD_DOWN)) { vy += SPEED_PX_PER_S; }
|
||||
const Sint16 LX = SDL_GetGamepadAxis(pad, SDL_GAMEPAD_AXIS_LEFTX);
|
||||
const Sint16 LY = SDL_GetGamepadAxis(pad, SDL_GAMEPAD_AXIS_LEFTY);
|
||||
if (LX < -STICK_DEADZONE) { vx -= SPEED_PX_PER_S; }
|
||||
if (LX > STICK_DEADZONE) { vx += SPEED_PX_PER_S; }
|
||||
if (LY < -STICK_DEADZONE) { vy -= SPEED_PX_PER_S; }
|
||||
@@ -514,33 +511,13 @@ void Player::integrateAxis(float& phys, float input, float delta_time, float mot
|
||||
}
|
||||
}
|
||||
|
||||
// Pipeline d'estats i input separats; extracció a helpers empitjoraria la
|
||||
// llegibilitat. La complexitat puja 1 per la guarda EXITED.
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void Player::tick(float delta_time) {
|
||||
// EXITED/DISABLED: jugador fora del joc, no fa res. Cap input, cap
|
||||
// física, cap bus event. Continua sent al vector de jugadors per
|
||||
// simplicitat (no s'esborra) però queda completament inert fins
|
||||
// que GameScene el reactiva (respawn / spawnFromAir).
|
||||
if (isOutOfPlay()) { return; }
|
||||
|
||||
// Decrement del timer d'immunitat. Independent de l'estat: si el
|
||||
// jugador mor durant la immunitat (només via forat, que no es gateja),
|
||||
// el `respawn` rearma una nova finestra. Si la finestra acaba mentre
|
||||
// és AIRBORNE/HURT, no passa res — el gate dels hazards passa a
|
||||
// quedar obert al primer frame post-zero.
|
||||
if (immunity_remaining_ms_ > 0.0F) {
|
||||
immunity_remaining_ms_ -= delta_time * 1000.0F;
|
||||
immunity_remaining_ms_ = std::max(immunity_remaining_ms_, 0.0F);
|
||||
}
|
||||
|
||||
// DYING: animació `death_spinning` + pausa de respawn. Ignorem tot
|
||||
// l'input (ni moviment, ni salt, ni atac). El moviment queda
|
||||
// congelat al lloc de la mort; `applyMovement` avança l'animació i
|
||||
// detecta la fi per disparar el respawn. Mantenim els flancs de
|
||||
// botó actualitzats perquè una tecla sostinguda no es comporte com
|
||||
// a flanc nou en sortir del cicle.
|
||||
if (state_ == State::DYING) {
|
||||
void Player::tickDying() {
|
||||
(void)consumeJumpEdge();
|
||||
(void)consumeAttackEdge();
|
||||
input_vx_ = 0.0F;
|
||||
@@ -550,20 +527,13 @@ void Player::tick(float delta_time) {
|
||||
motion_time_ms_ = 0.0F;
|
||||
setVelX(0.0F);
|
||||
setVelY(0.0F);
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset per frame: el resolver del GameScene el tornarà a posar a
|
||||
// true si aquest frame hi ha push actiu. Així quan el push acaba
|
||||
// (un deixa d'empentar, l'altre comença a caminar), l'animació torna
|
||||
// a governar-se per la velocitat real sense estat romanent.
|
||||
being_pushed_ = false;
|
||||
|
||||
// En LANDING_STUN, descomptem el timer i ignorem qualsevol input
|
||||
// (moviment i salt). Quan s'esgota, tornem a GROUNDED al pròxim
|
||||
// frame. La velocitat queda a zero — després del stun hi tornarà
|
||||
// a haver intenció si el jugador prem alguna tecla.
|
||||
if (state_ == State::LANDING_STUN) {
|
||||
void Player::tickLandingStun(float delta_time) {
|
||||
landing_stun_remaining_ms_ -= delta_time * 1000.0F;
|
||||
if (landing_stun_remaining_ms_ <= 0.0F) {
|
||||
landing_stun_remaining_ms_ = 0.0F;
|
||||
@@ -581,19 +551,17 @@ void Player::tick(float delta_time) {
|
||||
// se'l consumeix com a salt nou en sortir del stun.
|
||||
(void)consumeJumpEdge();
|
||||
(void)consumeAttackEdge();
|
||||
return;
|
||||
}
|
||||
|
||||
// En AIRBORNE el jugador no llig input direccional: la velocitat
|
||||
// horitzontal queda fixada al moment del despegue. Tampoc canvia
|
||||
// facing. Sí actualitzem els flancs del botó perquè la propera
|
||||
// pulsació quan torne a terra es detecte com a flanc nou.
|
||||
if (state_ == State::AIRBORNE) {
|
||||
void Player::tickAirborne() {
|
||||
(void)consumeJumpEdge();
|
||||
(void)consumeAttackEdge();
|
||||
setVelX(intended_vx_);
|
||||
setVelY(intended_vy_);
|
||||
return;
|
||||
}
|
||||
|
||||
// En ATTACKING el jugador queda bloquejat tot el `swing_ms +
|
||||
@@ -602,7 +570,7 @@ void Player::tick(float delta_time) {
|
||||
// mantenir la convenció de delta_ms de la resta d'estats temporals.
|
||||
// Mantenim els flancs dels botons actualitzats perquè una
|
||||
// tecla mantinguda no es comporte com a flanc nou en sortir.
|
||||
if (state_ == State::ATTACKING) {
|
||||
void Player::tickAttacking(float delta_time) {
|
||||
(void)consumeJumpEdge();
|
||||
(void)consumeAttackEdge();
|
||||
attacking_remaining_ms_ -= delta_time * 1000.0F;
|
||||
@@ -617,7 +585,6 @@ void Player::tick(float delta_time) {
|
||||
motion_time_ms_ = 0.0F;
|
||||
setVelX(0.0F);
|
||||
setVelY(0.0F);
|
||||
return;
|
||||
}
|
||||
|
||||
// En HURT el jugador està projectat sense control: descomptem
|
||||
@@ -625,7 +592,7 @@ void Player::tick(float delta_time) {
|
||||
// zero, tornem a GROUNDED i el frame següent ja torna a llegir input.
|
||||
// El moviment real l'aplica `applyMovement` (igual que els altres
|
||||
// estats), que també gestiona el xoc contra murs.
|
||||
if (state_ == State::HURT) {
|
||||
void Player::tickHurt(float delta_time) {
|
||||
(void)consumeJumpEdge();
|
||||
(void)consumeAttackEdge();
|
||||
const float DECEL = (cfg_.hurt.brake_time_ms > 0)
|
||||
@@ -648,20 +615,18 @@ void Player::tick(float delta_time) {
|
||||
input_vy_ = 0.0F;
|
||||
setVelX(intended_vx_);
|
||||
setVelY(intended_vy_);
|
||||
return;
|
||||
}
|
||||
|
||||
// Estat normal de joc (GROUNDED): llegeix input, integra física, actualitza
|
||||
// facing i processa els flancs d'atac i salt.
|
||||
void Player::tickGroundedInput(float delta_time) {
|
||||
// Refresca les vinculacions des d'Options en calent: així un remap fet
|
||||
// al menú de servei té efecte immediatament, sense esperar al pròxim
|
||||
// GameScene::onEnter. Replica el comportament d'AEE
|
||||
// (`KeyRemap::update` que llig `Options::keys_game` cada frame).
|
||||
refreshControlsFromOptions();
|
||||
|
||||
// Via `InputState` en lloc de `SDL_GetKeyboardState` directe: quan el
|
||||
// service menu (o qualsevol UI modal) està obert, el wrapper retorna
|
||||
// un buffer tot-a-zero perquè el jugador no es mogui mentre l'usuari
|
||||
// navega el menú. Polling-blocked; veure `core/input/input_state.hpp`.
|
||||
const bool* keys = InputState::getKeyboardState();
|
||||
const bool* keys = SDL_GetKeyboardState(nullptr);
|
||||
float vx = 0.0F;
|
||||
float vy = 0.0F;
|
||||
if (keys[controls_.left]) { vx -= SPEED_PX_PER_S; }
|
||||
@@ -669,9 +634,7 @@ void Player::tick(float delta_time) {
|
||||
if (keys[controls_.up]) { vy -= SPEED_PX_PER_S; }
|
||||
if (keys[controls_.down]) { vy += SPEED_PX_PER_S; }
|
||||
|
||||
// Cruceta i stick esquerre del mando assignat (si n'hi ha). També
|
||||
// respecten el bloqueig d'`InputState`, així que amb el menú obert
|
||||
// els botons tornen false/0 i no contribueixen a la velocitat.
|
||||
// Cruceta i stick esquerre del mando assignat (si n'hi ha).
|
||||
accumulateGamepadMovement(id_, vx, vy);
|
||||
|
||||
input_vx_ = vx;
|
||||
@@ -732,6 +695,55 @@ void Player::tick(float delta_time) {
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatcher per estat. La complexitat real viu als handlers individuals.
|
||||
void Player::tick(float delta_time) {
|
||||
// EXITED/DISABLED: jugador fora del joc, no fa res. Cap input, cap
|
||||
// física, cap bus event. Continua sent al vector de jugadors per
|
||||
// simplicitat (no s'esborra) però queda completament inert fins
|
||||
// que GameScene el reactiva (respawn / spawnFromAir).
|
||||
if (isOutOfPlay()) { return; }
|
||||
|
||||
// Decrement del timer d'immunitat. Independent de l'estat: si el
|
||||
// jugador mor durant la immunitat (només via forat, que no es gateja),
|
||||
// el `respawn` rearma una nova finestra. Si la finestra acaba mentre
|
||||
// és AIRBORNE/HURT, no passa res — el gate dels hazards passa a
|
||||
// quedar obert al primer frame post-zero.
|
||||
if (immunity_remaining_ms_ > 0.0F) {
|
||||
immunity_remaining_ms_ -= delta_time * 1000.0F;
|
||||
immunity_remaining_ms_ = std::max(immunity_remaining_ms_, 0.0F);
|
||||
}
|
||||
|
||||
if (state_ == State::DYING) {
|
||||
tickDying();
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset per frame: el resolver del GameScene el tornarà a posar a
|
||||
// true si aquest frame hi ha push actiu. Així quan el push acaba
|
||||
// (un deixa d'empentar, l'altre comença a caminar), l'animació torna
|
||||
// a governar-se per la velocitat real sense estat romanent.
|
||||
being_pushed_ = false;
|
||||
|
||||
if (state_ == State::LANDING_STUN) {
|
||||
tickLandingStun(delta_time);
|
||||
return;
|
||||
}
|
||||
if (state_ == State::AIRBORNE) {
|
||||
tickAirborne();
|
||||
return;
|
||||
}
|
||||
if (state_ == State::ATTACKING) {
|
||||
tickAttacking(delta_time);
|
||||
return;
|
||||
}
|
||||
if (state_ == State::HURT) {
|
||||
tickHurt(delta_time);
|
||||
return;
|
||||
}
|
||||
|
||||
tickGroundedInput(delta_time);
|
||||
}
|
||||
|
||||
void Player::refreshControlsFromOptions() {
|
||||
if (id_ != 0 && id_ != 1) { return; }
|
||||
const auto& kb = (id_ == 0) ? Options::controls().keyboard_p1
|
||||
@@ -749,7 +761,7 @@ auto Player::consumeJumpEdge() -> bool {
|
||||
// dos passa de no-premut a premut aquest frame, comptem com a salt.
|
||||
// Sense detecció de flanc, mantenir la tecla provocaria saltar
|
||||
// immediatament en aterrar.
|
||||
const bool* keys = InputState::getKeyboardState();
|
||||
const bool* keys = SDL_GetKeyboardState(nullptr);
|
||||
const bool KEY_NOW = keys[controls_.jump];
|
||||
bool edge = (KEY_NOW && !jump_held_keyboard_);
|
||||
jump_held_keyboard_ = KEY_NOW;
|
||||
@@ -758,8 +770,7 @@ auto Player::consumeJumpEdge() -> bool {
|
||||
const auto& slot = (id_ == 0) ? Options::controls().gamepad_p1
|
||||
: Options::controls().gamepad_p2;
|
||||
const int JUMP_BTN = GamepadBindings::get().getButton(slot.name, slot.path, GamepadBindings::Action::JUMP);
|
||||
const bool PAD_NOW = (pad != nullptr) && !InputState::isBlocked() &&
|
||||
GamepadBindings::isPressed(pad, JUMP_BTN);
|
||||
const bool PAD_NOW = (pad != nullptr) && GamepadBindings::isPressed(pad, JUMP_BTN);
|
||||
if (PAD_NOW && !jump_held_gamepad_) { edge = true; }
|
||||
jump_held_gamepad_ = PAD_NOW;
|
||||
|
||||
@@ -770,7 +781,7 @@ auto Player::consumeAttackEdge() -> bool {
|
||||
// Mateix patró que `consumeJumpEdge` però per al botó d'atac. El botó
|
||||
// físic ve d'`GamepadBindings`, persistit per dispositiu — l'usuari
|
||||
// l'ha pogut remapejar des del menú de servei.
|
||||
const bool* keys = InputState::getKeyboardState();
|
||||
const bool* keys = SDL_GetKeyboardState(nullptr);
|
||||
const bool KEY_NOW = keys[controls_.attack];
|
||||
bool edge = (KEY_NOW && !attack_held_keyboard_);
|
||||
attack_held_keyboard_ = KEY_NOW;
|
||||
@@ -779,8 +790,7 @@ auto Player::consumeAttackEdge() -> bool {
|
||||
const auto& slot = (id_ == 0) ? Options::controls().gamepad_p1
|
||||
: Options::controls().gamepad_p2;
|
||||
const int ATK_BTN = GamepadBindings::get().getButton(slot.name, slot.path, GamepadBindings::Action::ATTACK);
|
||||
const bool PAD_NOW = (pad != nullptr) && !InputState::isBlocked() &&
|
||||
GamepadBindings::isPressed(pad, ATK_BTN);
|
||||
const bool PAD_NOW = (pad != nullptr) && GamepadBindings::isPressed(pad, ATK_BTN);
|
||||
if (PAD_NOW && !attack_held_gamepad_) { edge = true; }
|
||||
attack_held_gamepad_ = PAD_NOW;
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "core/rendering/lighting.hpp"
|
||||
#include "core/rendering/sprite/animated_sprite.hpp"
|
||||
#include "core/rendering/surface.hpp"
|
||||
#include "external/fkyaml_node.hpp"
|
||||
#include "game/map/map.hpp"
|
||||
|
||||
class EventBus;
|
||||
@@ -604,6 +603,15 @@ class Player : public AnimatedSprite {
|
||||
void renderShadow() const;
|
||||
|
||||
private:
|
||||
// Sub-handlers de tick(), un per estat amb sortida prematura. Mantenir-los
|
||||
// a part redueix la complexitat cognitiva del dispatcher principal.
|
||||
void tickDying();
|
||||
void tickLandingStun(float delta_time);
|
||||
void tickAirborne();
|
||||
void tickAttacking(float delta_time);
|
||||
void tickHurt(float delta_time);
|
||||
void tickGroundedInput(float delta_time);
|
||||
|
||||
// Alias privat històric. Mantingut perquè viu dins de l'implementació
|
||||
// (snapToLane, clamp). Els consumidors externs usen `getBaseBox()`.
|
||||
[[nodiscard]] auto baseBox() const -> SDL_FRect { return getBaseBox(); }
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "core/resources/resource_helper.hpp"
|
||||
#include "external/fkyaml_node.hpp"
|
||||
#include "game/map/room_yaml.hpp"
|
||||
#include "utils/defaults.hpp"
|
||||
#include "utils/log.hpp"
|
||||
|
||||
@@ -157,13 +157,7 @@ namespace Map {
|
||||
}
|
||||
|
||||
void loadFrom(const std::string& yaml_path) {
|
||||
auto bytes = Resource::Helper::loadFile(yaml_path);
|
||||
if (bytes.empty()) {
|
||||
throw std::runtime_error("Map: no s'ha pogut obrir " + yaml_path);
|
||||
}
|
||||
const auto* begin = reinterpret_cast<const char*>(bytes.data());
|
||||
const auto* end = begin + bytes.size();
|
||||
auto yaml = fkyaml::node::deserialize(begin, end);
|
||||
auto yaml = RoomYaml::loadMerged(yaml_path);
|
||||
if (!yaml.contains("geometry")) {
|
||||
throw std::runtime_error("Map: manca bloc `geometry:` a " + yaml_path);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "game/map/room_yaml.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "core/resources/resource_helper.hpp"
|
||||
|
||||
namespace RoomYaml {
|
||||
|
||||
namespace {
|
||||
|
||||
// Fusiona `overlay` damunt `base` in-place. Recursió només per a
|
||||
// mapes; per a la resta (escalars, sequences) la `overlay` substitueix
|
||||
// l'entrada de `base`. Manté l'ordre de claus de `base` per a les
|
||||
// que ja hi eren; les noves s'afegeixen al final.
|
||||
void deepMerge(fkyaml::node& base, const fkyaml::node& overlay) {
|
||||
if (!overlay.is_mapping()) { return; }
|
||||
for (const auto& kv : overlay.as_map()) {
|
||||
const auto KEY = kv.first.get_value<std::string>();
|
||||
const auto& val = kv.second;
|
||||
if (base.contains(KEY) && base[KEY].is_mapping() && val.is_mapping()) {
|
||||
deepMerge(base[KEY], val);
|
||||
} else {
|
||||
base[KEY] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto deserializeFile(const std::string& path) -> fkyaml::node {
|
||||
auto bytes = Resource::Helper::loadFile(path);
|
||||
if (bytes.empty()) {
|
||||
throw std::runtime_error("RoomYaml: no s'ha pogut obrir " + path);
|
||||
}
|
||||
const auto* begin = reinterpret_cast<const char*>(bytes.data());
|
||||
const auto* end = begin + bytes.size();
|
||||
return fkyaml::node::deserialize(begin, end);
|
||||
}
|
||||
|
||||
// Retorna el directori del path (sense barra final). Si no hi ha
|
||||
// separador, retorna cadena buida.
|
||||
auto dirOf(const std::string& path) -> std::string {
|
||||
const auto POS = path.find_last_of('/');
|
||||
if (POS == std::string::npos) { return ""; }
|
||||
return path.substr(0, POS);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
auto loadMerged(const std::string& room_yaml_path) -> fkyaml::node {
|
||||
const auto DIR = dirOf(room_yaml_path);
|
||||
const auto PYRAMID_PATH = DIR.empty() ? std::string{"pyramid.yaml"} : (DIR + "/pyramid.yaml");
|
||||
|
||||
// El pyramid.yaml és opcional: si Resource::Helper::loadFile el
|
||||
// troba (disc o pack), el fem servir com a base; si no, només
|
||||
// retornem el YAML de sala tal qual.
|
||||
auto pyramid_bytes = Resource::Helper::loadFile(PYRAMID_PATH);
|
||||
auto room = deserializeFile(room_yaml_path);
|
||||
if (pyramid_bytes.empty()) { return room; }
|
||||
|
||||
const auto* begin = reinterpret_cast<const char*>(pyramid_bytes.data());
|
||||
const auto* end = begin + pyramid_bytes.size();
|
||||
auto merged = fkyaml::node::deserialize(begin, end);
|
||||
deepMerge(merged, room);
|
||||
return merged;
|
||||
}
|
||||
|
||||
} // namespace RoomYaml
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "external/fkyaml_node.hpp"
|
||||
|
||||
// Carregador de YAMLs de sala amb fusió per piràmide. Cada piràmide pot
|
||||
// tenir un `pyramid.yaml` germà al directori de sala que aporta camps
|
||||
// compartits (tileset, lighting, push_factor, paint_tiles...); el YAML
|
||||
// de sala només declara el que canvia (data, entities, geometry, room).
|
||||
//
|
||||
// Regles de fusió:
|
||||
// - mapes: fusió recursiva, claus de la sala guanyen
|
||||
// - sequences i escalars: la sala substitueix sencer
|
||||
//
|
||||
// Si no hi ha `pyramid.yaml` al directori, retorna només el YAML de sala
|
||||
// sense modificacions — així rutes externes (tests, mapes solts) segueixen
|
||||
// funcionant igual.
|
||||
namespace RoomYaml {
|
||||
|
||||
auto loadMerged(const std::string& room_yaml_path) -> fkyaml::node;
|
||||
|
||||
} // namespace RoomYaml
|
||||
@@ -9,8 +9,8 @@
|
||||
#include "core/rendering/screen.hpp"
|
||||
#include "core/rendering/surface.hpp"
|
||||
#include "core/resources/resource_cache.hpp"
|
||||
#include "core/resources/resource_helper.hpp"
|
||||
#include "external/fkyaml_node.hpp"
|
||||
#include "game/map/room_yaml.hpp"
|
||||
#include "utils/log.hpp"
|
||||
|
||||
#ifdef _DEBUG
|
||||
@@ -41,13 +41,7 @@ namespace {
|
||||
} // namespace
|
||||
|
||||
Tilemap::Tilemap(const std::string& yaml_path) {
|
||||
auto bytes = Resource::Helper::loadFile(yaml_path);
|
||||
if (bytes.empty()) {
|
||||
throw std::runtime_error("Tilemap: no s'ha pogut obrir " + yaml_path);
|
||||
}
|
||||
const auto* begin = reinterpret_cast<const char*>(bytes.data());
|
||||
const auto* end = begin + bytes.size();
|
||||
auto yaml = fkyaml::node::deserialize(begin, end);
|
||||
auto yaml = RoomYaml::loadMerged(yaml_path);
|
||||
|
||||
tileset_path_ = yaml["tileset"].get_value<std::string>();
|
||||
tile_size_ = yaml["tile_size"].get_value<int>();
|
||||
|
||||
+20
-9
@@ -248,12 +248,15 @@ namespace {
|
||||
}
|
||||
|
||||
// Presets per defecte de PostFX, portats directament de projecte_2026.
|
||||
// chroma_min == chroma_max → chroma estàtic (per defecte). Ajusta'ls al gust
|
||||
// (per exemple, augmentar el rang en CRT Live perquè "pulsi" visiblement).
|
||||
auto defaultPostFXPresets() -> std::vector<Options::PostFXPreset> {
|
||||
return {
|
||||
{.name = "CRT",
|
||||
.vignette = 0.6F,
|
||||
.scanlines = 0.7F,
|
||||
.chroma = 0.15F,
|
||||
.chroma_min = 0.15F,
|
||||
.chroma_max = 0.15F,
|
||||
.mask = 0.6F,
|
||||
.gamma = 0.8F,
|
||||
.curvature = 0.0F,
|
||||
@@ -262,7 +265,8 @@ namespace {
|
||||
{.name = "NTSC",
|
||||
.vignette = 0.4F,
|
||||
.scanlines = 0.5F,
|
||||
.chroma = 0.2F,
|
||||
.chroma_min = 0.2F,
|
||||
.chroma_max = 0.2F,
|
||||
.mask = 0.4F,
|
||||
.gamma = 0.5F,
|
||||
.curvature = 0.0F,
|
||||
@@ -271,7 +275,8 @@ namespace {
|
||||
{.name = "Curved",
|
||||
.vignette = 0.5F,
|
||||
.scanlines = 0.6F,
|
||||
.chroma = 0.1F,
|
||||
.chroma_min = 0.1F,
|
||||
.chroma_max = 0.1F,
|
||||
.mask = 0.5F,
|
||||
.gamma = 0.7F,
|
||||
.curvature = 0.8F,
|
||||
@@ -280,7 +285,8 @@ namespace {
|
||||
{.name = "Scanlines",
|
||||
.vignette = 0.0F,
|
||||
.scanlines = 0.8F,
|
||||
.chroma = 0.0F,
|
||||
.chroma_min = 0.0F,
|
||||
.chroma_max = 0.0F,
|
||||
.mask = 0.0F,
|
||||
.gamma = 0.0F,
|
||||
.curvature = 0.0F,
|
||||
@@ -289,7 +295,8 @@ namespace {
|
||||
{.name = "Subtle",
|
||||
.vignette = 0.3F,
|
||||
.scanlines = 0.4F,
|
||||
.chroma = 0.05F,
|
||||
.chroma_min = 0.05F,
|
||||
.chroma_max = 0.05F,
|
||||
.mask = 0.0F,
|
||||
.gamma = 0.3F,
|
||||
.curvature = 0.0F,
|
||||
@@ -298,7 +305,8 @@ namespace {
|
||||
{.name = "CRT Live",
|
||||
.vignette = 0.5F,
|
||||
.scanlines = 0.6F,
|
||||
.chroma = 0.3F,
|
||||
.chroma_min = 0.3F,
|
||||
.chroma_max = 0.3F,
|
||||
.mask = 0.3F,
|
||||
.gamma = 0.4F,
|
||||
.curvature = 0.3F,
|
||||
@@ -311,7 +319,8 @@ namespace {
|
||||
readField(node, "name", preset.name);
|
||||
readField(node, "vignette", preset.vignette);
|
||||
readField(node, "scanlines", preset.scanlines);
|
||||
readField(node, "chroma", preset.chroma);
|
||||
readField(node, "chroma_min", preset.chroma_min);
|
||||
readField(node, "chroma_max", preset.chroma_max);
|
||||
readField(node, "mask", preset.mask);
|
||||
readField(node, "gamma", preset.gamma);
|
||||
readField(node, "curvature", preset.curvature);
|
||||
@@ -326,7 +335,8 @@ namespace {
|
||||
out << " - name: \"" << preset.name << "\"\n";
|
||||
out << " vignette: " << preset.vignette << "\n";
|
||||
out << " scanlines: " << preset.scanlines << "\n";
|
||||
out << " chroma: " << preset.chroma << "\n";
|
||||
out << " chroma_min: " << preset.chroma_min << "\n";
|
||||
out << " chroma_max: " << preset.chroma_max << "\n";
|
||||
out << " mask: " << preset.mask << "\n";
|
||||
out << " gamma: " << preset.gamma << "\n";
|
||||
out << " curvature: " << preset.curvature << "\n";
|
||||
@@ -876,7 +886,8 @@ namespace Options {
|
||||
return {
|
||||
.vignette = IT->vignette,
|
||||
.scanlines = IT->scanlines,
|
||||
.chroma = IT->chroma,
|
||||
.chroma_min = IT->chroma_min,
|
||||
.chroma_max = IT->chroma_max,
|
||||
.mask = IT->mask,
|
||||
.gamma = IT->gamma,
|
||||
.curvature = IT->curvature,
|
||||
|
||||
@@ -59,7 +59,9 @@ namespace Options {
|
||||
std::string name;
|
||||
float vignette{0.0F};
|
||||
float scanlines{0.0F};
|
||||
float chroma{0.0F};
|
||||
// Aberració cromàtica entre min i max (si coincideixen, estàtica).
|
||||
float chroma_min{0.0F};
|
||||
float chroma_max{0.0F};
|
||||
float mask{0.0F};
|
||||
float gamma{0.0F};
|
||||
float curvature{0.0F};
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
#include "core/audio/audio.hpp"
|
||||
#include "core/input/global_inputs.hpp"
|
||||
#include "core/input/key_config.hpp"
|
||||
#include "core/locale/locale.hpp"
|
||||
#include "core/rendering/lighting.hpp"
|
||||
#include "core/rendering/notifier.hpp"
|
||||
@@ -22,8 +21,6 @@
|
||||
#include "core/rendering/screen.hpp"
|
||||
#include "core/rendering/text.hpp"
|
||||
#include "core/resources/resource_cache.hpp"
|
||||
#include "core/resources/resource_helper.hpp"
|
||||
#include "core/system/event_buffer.hpp"
|
||||
#include "core/ui/ui_config.hpp"
|
||||
#include "external/fkyaml_node.hpp"
|
||||
#include "game/entities/concrete/dust.hpp"
|
||||
@@ -33,11 +30,16 @@
|
||||
#include "game/entities/entity_factory.hpp"
|
||||
#include "game/entities/entity_loader.hpp"
|
||||
#include "game/entities/event_bus.hpp"
|
||||
#include "game/entities/hazard.hpp"
|
||||
#include "game/entities/player.hpp"
|
||||
|
||||
#ifdef _DEBUG
|
||||
#include "core/input/key_config.hpp"
|
||||
#include "core/system/event_buffer.hpp"
|
||||
#endif
|
||||
#include "game/gameplay_config.hpp"
|
||||
#include "game/levels/level_manager.hpp"
|
||||
#include "game/map/map.hpp"
|
||||
#include "game/map/room_yaml.hpp"
|
||||
#include "game/options.hpp"
|
||||
#include "game/scenes/scene_flow.hpp"
|
||||
#include "game/scenes/start_input.hpp"
|
||||
@@ -65,21 +67,13 @@ namespace {
|
||||
// pinta amb l'índex declarat al mapa, i un mapa sense aquests camps
|
||||
// simplement no deixa rastre visual.
|
||||
auto readOptionalInt(const std::string& yaml_path, const std::string& key, int fallback = -1) -> int {
|
||||
auto bytes = Resource::Helper::loadFile(yaml_path);
|
||||
if (bytes.empty()) { return fallback; }
|
||||
const auto* begin = reinterpret_cast<const char*>(bytes.data());
|
||||
const auto* end = begin + bytes.size();
|
||||
auto yaml = fkyaml::node::deserialize(begin, end);
|
||||
auto yaml = RoomYaml::loadMerged(yaml_path);
|
||||
if (!yaml.contains(key)) { return fallback; }
|
||||
return yaml[key].get_value<int>();
|
||||
}
|
||||
|
||||
auto readOptionalFloat(const std::string& yaml_path, const std::string& key, float fallback) -> float {
|
||||
auto bytes = Resource::Helper::loadFile(yaml_path);
|
||||
if (bytes.empty()) { return fallback; }
|
||||
const auto* begin = reinterpret_cast<const char*>(bytes.data());
|
||||
const auto* end = begin + bytes.size();
|
||||
auto yaml = fkyaml::node::deserialize(begin, end);
|
||||
auto yaml = RoomYaml::loadMerged(yaml_path);
|
||||
if (!yaml.contains(key)) { return fallback; }
|
||||
return yaml[key].get_value<float>();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#include "core/rendering/transitions/fade_to_black.hpp"
|
||||
#include "core/scene/scene.hpp"
|
||||
#include "core/ui/ui_config.hpp"
|
||||
#include "game/debug_config.hpp"
|
||||
#include "game/entities/entity_manager.hpp"
|
||||
#include "game/entities/player.hpp"
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "game/options.hpp"
|
||||
#include "game/scenes/placeholder_input.hpp"
|
||||
#include "game/scenes/scene_flow.hpp"
|
||||
#include "utils/defaults.hpp"
|
||||
#include "utils/defines.hpp"
|
||||
#include "utils/log.hpp"
|
||||
|
||||
|
||||
@@ -26,15 +26,9 @@ namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
LogoScene::LogoScene(std::shared_ptr<Surface> logo, bool reset)
|
||||
LogoScene::LogoScene(std::shared_ptr<Surface> logo)
|
||||
: logo_(std::move(logo)) {
|
||||
Log::info("scene", "{} start{}", SCENE_NAME, reset ? " (reset)" : "");
|
||||
if (reset) {
|
||||
if (auto* audio = Audio::get(); audio != nullptr) {
|
||||
audio->stopMusic();
|
||||
audio->stopAllSounds();
|
||||
}
|
||||
}
|
||||
Log::info("scene", "{} start", SCENE_NAME);
|
||||
// Arrenquem en BLACK_INTRO: ni so ni efecte. El glitch sound i el
|
||||
// begin() de scan_ es disparen quan entrem a SCAN.
|
||||
}
|
||||
|
||||
@@ -29,13 +29,9 @@ class Surface;
|
||||
// Skip: qualsevol tecla no reservada salta directament a la transició
|
||||
// (sense fade-out) — el TitleScene té el seu propi `intro.fade_in_ms`,
|
||||
// així que la transició queda visualment continua igualment.
|
||||
//
|
||||
// Si arriba amb `reset=true` (acció "Reiniciar" del menú de servei),
|
||||
// atura música i tots els sons al constructor — el següent LOGO arrenca
|
||||
// silenciós encara que veníssem del joc en marxa.
|
||||
class LogoScene : public Scene {
|
||||
public:
|
||||
LogoScene(std::shared_ptr<Surface> logo, bool reset);
|
||||
explicit LogoScene(std::shared_ptr<Surface> logo);
|
||||
~LogoScene() override;
|
||||
|
||||
void update(int delta_ms) override;
|
||||
|
||||
@@ -30,15 +30,7 @@ enum class SceneId : std::uint8_t {
|
||||
// rep cap dada de l'anterior; en cas contrari, només els camps que
|
||||
// fluixen pel canal de transició (els recursos, defaults i config
|
||||
// interna els resol Director quan construeix).
|
||||
struct LogoParams {
|
||||
// Quan val `true`, indica que aquesta entrada al LOGO és una reinicialització
|
||||
// completa (acció "Reiniciar" del menú de servei): l'escena haurà d'aturar
|
||||
// música i sons abans de mostrar-se. En el flux normal LOGO→TITLE→… es
|
||||
// queda a `false` perquè qualsevol música prèvia (p. ex. la pista de
|
||||
// TITLE durant l'attract-out) continui sonant fins que la nova escena la
|
||||
// substituïsca.
|
||||
bool reset{false};
|
||||
};
|
||||
struct LogoParams {};
|
||||
struct TitleParams {};
|
||||
struct IntroParams {
|
||||
DebugConfig::StartingPlayers starting_players{DebugConfig::StartingPlayers::BOTH};
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
|
||||
Loaded 100 of 111 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user