Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8eba52353b | ||
|
|
15a4016407 | ||
|
|
2272d3a37a | ||
|
|
3a42c7958a | ||
|
|
dacc4a9909 | ||
|
|
771ff1f6cd | ||
|
|
24609d6c5c | ||
|
|
9d0dde38f9 | ||
|
|
b76a3eb1f3 | ||
|
|
a779533d61 | ||
|
|
bc6ad6f718 | ||
|
|
9bd5e7db33 | ||
|
|
5cc8e349a5 | ||
|
|
0046d526b0 | ||
|
|
fc9947b87f | ||
|
|
35aaa6c519 | ||
|
|
3195746189 | ||
|
|
d1d5679d14 | ||
|
|
ebb161f1f5 | ||
|
|
68be8e55ec | ||
|
|
5ed963b53d | ||
|
|
5e5ff835a9 | ||
|
|
f5d251c33b | ||
|
|
d186f66c2c | ||
|
|
da361a1452 | ||
|
|
a43ae21014 | ||
|
|
6749780356 | ||
|
|
1c49248b72 | ||
|
|
63a03ae632 | ||
|
|
a8bff31a7c | ||
|
|
793e87a203 | ||
|
|
642e0653b2 | ||
|
|
d26abcb9d5 | ||
|
|
a358d8ddbd | ||
|
|
f7c4d62234 | ||
|
|
06a49e09ba | ||
|
|
629baaaec3 | ||
|
|
932d195eb0 | ||
|
|
3e31a88f78 | ||
|
|
cba46642c6 | ||
|
|
8c84892250 | ||
|
|
d14da0e61f | ||
|
|
8bdc994b75 | ||
|
|
6a70d6de9f | ||
|
|
2523661c8f | ||
|
|
fa27308897 | ||
|
|
e58eb10d6d | ||
|
|
a29ea641ab | ||
|
|
28ff425c89 | ||
|
|
e8418f89e0 | ||
|
|
9ca09484a6 | ||
|
|
9291ef6572 | ||
|
|
6f8ab74d6b | ||
|
|
881e0ea1f6 | ||
|
|
6a6a1a52c7 | ||
|
|
62f4b857ef | ||
|
|
3b378e2b29 | ||
|
|
041447eaa4 | ||
|
|
a6b2b02930 | ||
|
|
b7811180ba | ||
|
|
2cbf1bb6e0 | ||
|
|
8890fa9e1f | ||
|
|
23f7cb03a6 | ||
|
|
486a440668 | ||
|
|
a717411edd | ||
|
|
5e862ceadc | ||
|
|
78e164fce4 | ||
|
|
c13b11c501 | ||
|
|
8921b9fd0c | ||
|
|
1d9fea28fe | ||
|
|
d0d5cb0e66 | ||
|
|
e25ccd02fd | ||
|
|
3635cebf25 | ||
|
|
cfbcdc236b | ||
|
|
7871d3930e | ||
|
|
932b457725 | ||
|
|
e9f99a0542 | ||
|
|
0d58da536c | ||
|
|
583acb9053 | ||
|
|
babb111b6f | ||
|
|
866e133aa9 | ||
|
|
170521aba6 | ||
|
|
c8882a3f6e | ||
|
|
5fce52e0ad | ||
|
|
0b708f1515 | ||
|
|
6e99fd0a8b | ||
|
|
1243b457be | ||
|
|
c910a16834 | ||
|
|
0ec1a2548a | ||
|
|
4ea8ab7b6d | ||
|
|
05465b791e | ||
|
|
9949096fba | ||
|
|
ea5762265e | ||
|
|
78732042cb | ||
|
|
f10671d9c0 | ||
|
|
7d5a4cee8e | ||
|
|
d560623e00 | ||
|
|
3f88dfaaee | ||
|
|
e1bd75e00d | ||
|
|
e32cff9dac | ||
|
|
243ced7498 | ||
|
|
fceeb11cdd | ||
|
|
233fcb8226 | ||
|
|
8bc45ccedc | ||
|
|
f7ab6a641a |
No files matched your search
@@ -8,8 +8,8 @@ dist/
|
|||||||
*.dylib
|
*.dylib
|
||||||
*.dll
|
*.dll
|
||||||
*.exe
|
*.exe
|
||||||
aee_2026
|
aee_arcade
|
||||||
_aee_2026
|
_aee_arcade
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
.cache/
|
.cache/
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
project(aee_2026 VERSION 0.1.0 LANGUAGES CXX)
|
project(aee_arcade VERSION 0.1.0 LANGUAGES CXX)
|
||||||
|
|
||||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||||
set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE)
|
set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE)
|
||||||
@@ -52,6 +52,7 @@ set(ENTITY_SOURCES_BASE
|
|||||||
source/game/entities/concrete/floor_trap.cpp
|
source/game/entities/concrete/floor_trap.cpp
|
||||||
source/game/entities/concrete/hole.cpp
|
source/game/entities/concrete/hole.cpp
|
||||||
source/game/entities/concrete/dust.cpp
|
source/game/entities/concrete/dust.cpp
|
||||||
|
source/game/entities/concrete/solid_props.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Entitats que criden directament símbols de Player (com WhipAttack via
|
# Entitats que criden directament símbols de Player (com WhipAttack via
|
||||||
@@ -80,6 +81,7 @@ set(APP_SOURCES
|
|||||||
source/core/input/mouse.cpp
|
source/core/input/mouse.cpp
|
||||||
source/core/rendering/screen.cpp
|
source/core/rendering/screen.cpp
|
||||||
source/core/rendering/canvas.cpp
|
source/core/rendering/canvas.cpp
|
||||||
|
source/core/rendering/lighting.cpp
|
||||||
source/core/rendering/notifier.cpp
|
source/core/rendering/notifier.cpp
|
||||||
source/core/rendering/render_info.cpp
|
source/core/rendering/render_info.cpp
|
||||||
source/core/rendering/gif.cpp
|
source/core/rendering/gif.cpp
|
||||||
@@ -93,6 +95,8 @@ set(APP_SOURCES
|
|||||||
source/core/rendering/sprite/animated_sprite.cpp
|
source/core/rendering/sprite/animated_sprite.cpp
|
||||||
source/core/rendering/sprite/animated_sprite_parser.cpp
|
source/core/rendering/sprite/animated_sprite_parser.cpp
|
||||||
source/core/rendering/effects/heat_haze.cpp
|
source/core/rendering/effects/heat_haze.cpp
|
||||||
|
source/core/rendering/effects/scan_reveal.cpp
|
||||||
|
source/core/rendering/effects/shadow.cpp
|
||||||
source/core/rendering/transitions/mosaic.cpp
|
source/core/rendering/transitions/mosaic.cpp
|
||||||
source/core/rendering/transitions/fade_to_black.cpp
|
source/core/rendering/transitions/fade_to_black.cpp
|
||||||
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp
|
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp
|
||||||
@@ -103,18 +107,22 @@ set(APP_SOURCES
|
|||||||
source/core/resources/resource_pack.cpp
|
source/core/resources/resource_pack.cpp
|
||||||
source/core/resources/palette_loader.cpp
|
source/core/resources/palette_loader.cpp
|
||||||
source/utils/easing.cpp
|
source/utils/easing.cpp
|
||||||
|
source/utils/log.cpp
|
||||||
source/utils/utils.cpp
|
source/utils/utils.cpp
|
||||||
source/game/debug_config.cpp
|
source/game/debug_config.cpp
|
||||||
|
source/game/gameplay_config.cpp
|
||||||
source/game/options.cpp
|
source/game/options.cpp
|
||||||
source/game/options_apply.cpp
|
source/game/options_apply.cpp
|
||||||
source/game/ui/service_menu.cpp
|
source/game/ui/service_menu.cpp
|
||||||
source/game/ui/hud.cpp
|
source/game/ui/hud.cpp
|
||||||
source/game/scenes/placeholder_input.cpp
|
source/game/scenes/placeholder_input.cpp
|
||||||
|
source/game/scenes/start_input.cpp
|
||||||
source/game/scenes/logo_scene.cpp
|
source/game/scenes/logo_scene.cpp
|
||||||
source/game/scenes/title_scene.cpp
|
source/game/scenes/title_scene.cpp
|
||||||
source/game/scenes/intro_scene.cpp
|
source/game/scenes/intro_scene.cpp
|
||||||
source/game/scenes/game_scene.cpp
|
source/game/scenes/game_scene.cpp
|
||||||
source/game/scenes/room_fsm.cpp
|
source/game/scenes/room_fsm.cpp
|
||||||
|
source/game/scenes/game_over_fsm.cpp
|
||||||
source/game/entities/player.cpp
|
source/game/entities/player.cpp
|
||||||
source/game/map/map.cpp
|
source/game/map/map.cpp
|
||||||
source/game/map/tilemap.cpp
|
source/game/map/tilemap.cpp
|
||||||
@@ -217,8 +225,10 @@ target_link_libraries(${PROJECT_NAME} PRIVATE SDL3::SDL3)
|
|||||||
add_executable(pack_resources EXCLUDE_FROM_ALL
|
add_executable(pack_resources EXCLUDE_FROM_ALL
|
||||||
tools/pack_resources/pack_resources.cpp
|
tools/pack_resources/pack_resources.cpp
|
||||||
source/core/resources/resource_pack.cpp
|
source/core/resources/resource_pack.cpp
|
||||||
|
source/utils/log.cpp
|
||||||
)
|
)
|
||||||
target_include_directories(pack_resources PRIVATE "${CMAKE_SOURCE_DIR}/source")
|
target_include_directories(pack_resources PRIVATE "${CMAKE_SOURCE_DIR}/source")
|
||||||
|
target_link_libraries(pack_resources PRIVATE SDL3::SDL3)
|
||||||
target_compile_options(pack_resources PRIVATE -Wall -Wextra -Wpedantic)
|
target_compile_options(pack_resources PRIVATE -Wall -Wextra -Wpedantic)
|
||||||
|
|
||||||
# --- REGENERACIÓ AUTOMÀTICA DE build/resources.pack ---
|
# --- REGENERACIÓ AUTOMÀTICA DE build/resources.pack ---
|
||||||
@@ -360,9 +370,11 @@ if(BUILD_TESTS)
|
|||||||
add_executable(test_entities_smoke
|
add_executable(test_entities_smoke
|
||||||
tests/test_entities_smoke.cpp
|
tests/test_entities_smoke.cpp
|
||||||
tests/hole_player_stubs.cpp
|
tests/hole_player_stubs.cpp
|
||||||
|
tests/audio_cache_stubs.cpp
|
||||||
source/core/resources/resource_helper.cpp
|
source/core/resources/resource_helper.cpp
|
||||||
source/core/resources/resource_pack.cpp
|
source/core/resources/resource_pack.cpp
|
||||||
source/game/map/map.cpp
|
source/game/map/map.cpp
|
||||||
|
source/utils/log.cpp
|
||||||
${ENTITY_SOURCES_BASE}
|
${ENTITY_SOURCES_BASE}
|
||||||
)
|
)
|
||||||
target_include_directories(test_entities_smoke PRIVATE
|
target_include_directories(test_entities_smoke PRIVATE
|
||||||
@@ -388,9 +400,12 @@ if(BUILD_TESTS)
|
|||||||
tests/test_whip_attack.cpp
|
tests/test_whip_attack.cpp
|
||||||
tests/whip_attack_player_stubs.cpp
|
tests/whip_attack_player_stubs.cpp
|
||||||
tests/hole_player_stubs.cpp
|
tests/hole_player_stubs.cpp
|
||||||
|
tests/audio_cache_stubs.cpp
|
||||||
source/core/resources/resource_helper.cpp
|
source/core/resources/resource_helper.cpp
|
||||||
source/core/resources/resource_pack.cpp
|
source/core/resources/resource_pack.cpp
|
||||||
|
source/game/gameplay_config.cpp
|
||||||
source/game/map/map.cpp
|
source/game/map/map.cpp
|
||||||
|
source/utils/log.cpp
|
||||||
${ENTITY_SOURCES_BASE}
|
${ENTITY_SOURCES_BASE}
|
||||||
source/game/entities/concrete/whip_attack.cpp
|
source/game/entities/concrete/whip_attack.cpp
|
||||||
)
|
)
|
||||||
@@ -415,6 +430,7 @@ if(BUILD_TESTS)
|
|||||||
source/core/input/key_config.cpp
|
source/core/input/key_config.cpp
|
||||||
source/core/resources/resource_helper.cpp
|
source/core/resources/resource_helper.cpp
|
||||||
source/core/resources/resource_pack.cpp
|
source/core/resources/resource_pack.cpp
|
||||||
|
source/utils/log.cpp
|
||||||
)
|
)
|
||||||
target_include_directories(test_scene_flow PRIVATE
|
target_include_directories(test_scene_flow PRIVATE
|
||||||
"${CMAKE_SOURCE_DIR}/source"
|
"${CMAKE_SOURCE_DIR}/source"
|
||||||
@@ -454,6 +470,7 @@ if(BUILD_TESTS)
|
|||||||
source/core/resources/resource_helper.cpp
|
source/core/resources/resource_helper.cpp
|
||||||
source/core/resources/resource_pack.cpp
|
source/core/resources/resource_pack.cpp
|
||||||
source/utils/easing.cpp
|
source/utils/easing.cpp
|
||||||
|
source/utils/log.cpp
|
||||||
)
|
)
|
||||||
target_include_directories(test_game_fsm PRIVATE
|
target_include_directories(test_game_fsm PRIVATE
|
||||||
"${CMAKE_SOURCE_DIR}/source"
|
"${CMAKE_SOURCE_DIR}/source"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
PROJECT := aee_2026
|
PROJECT := aee_arcade
|
||||||
BUILDDIR := build
|
BUILDDIR := build
|
||||||
|
|
||||||
# Detecció de plataforma. En Windows CMake defaulteja a "NMake Makefiles"
|
# Detecció de plataforma. En Windows CMake defaulteja a "NMake Makefiles"
|
||||||
@@ -293,8 +293,14 @@ rebuild: clean all
|
|||||||
# Empaqueta data/ a $(BUILDDIR)/resources.pack (format AE26). Força un rebuild
|
# Empaqueta data/ a $(BUILDDIR)/resources.pack (format AE26). Força un rebuild
|
||||||
# del pack encara que res no hagi canviat dins data/. L'empaquetat també es fa
|
# del pack encara que res no hagi canviat dins data/. L'empaquetat també es fa
|
||||||
# automàticament a cada `make`/`make debug` via el target CMake `resource_pack`.
|
# automàticament a cada `make`/`make debug` via el target CMake `resource_pack`.
|
||||||
|
#
|
||||||
|
# NO passem `-DCMAKE_BUILD_TYPE`: així respectem el tipus ja configurat al
|
||||||
|
# `CMakeCache.txt` (Debug si vens de `make debug`, Release si vens de
|
||||||
|
# `make release`). Si forcéssim Release ací, el següent build de debug
|
||||||
|
# invalidaria la cache i recompilaria-ho tot. El binari `pack_resources`
|
||||||
|
# és una utilitat petita on el build type és irrellevant.
|
||||||
pack:
|
pack:
|
||||||
@cmake -S . -B $(BUILDDIR) $(CMAKE_GEN) -DCMAKE_BUILD_TYPE=Release $(CMAKE_DEFS)
|
@cmake -S . -B $(BUILDDIR) $(CMAKE_GEN) $(CMAKE_DEFS)
|
||||||
@cmake --build $(BUILDDIR) --target pack_resources
|
@cmake --build $(BUILDDIR) --target pack_resources
|
||||||
@./$(BUILDDIR)/pack_resources data $(BUILDDIR)/resources.pack
|
@./$(BUILDDIR)/pack_resources data $(BUILDDIR)/resources.pack
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ picker:
|
|||||||
|
|
||||||
# Colors ARGB en hex. Accepta enter (0xFF2244FF) o cadena ("#FF2244FF").
|
# Colors ARGB en hex. Accepta enter (0xFF2244FF) o cadena ("#FF2244FF").
|
||||||
colors:
|
colors:
|
||||||
border: "0xFF2244FF" # vora 1 px que envolta canvas i picker
|
border: "0xFF6A86C8" # vora 1 px que envolta canvas i picker (blau suau)
|
||||||
picker_bg: "0xFF101824" # fons del picker
|
picker_bg: "0xFFC8C0D8" # fons del picker (lavanda clara)
|
||||||
picker_hover: "0xFFFFFFFF" # vora 1 px del tile sota el cursor
|
picker_hover: "0xFFFFFFFF" # vora 1 px del tile sota el cursor
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# AEE 2026 — Tecles d'UI (Global Inputs)
|
# AEE ARCADE — Tecles d'UI (Global Inputs)
|
||||||
#
|
#
|
||||||
# Mapeig id → codi SDL (el que reconeix SDL_GetScancodeFromName). Les tecles
|
# Mapeig id → codi SDL (el que reconeix SDL_GetScancodeFromName). Les tecles
|
||||||
# de moviment del jugador viuen a part (Defaults::KeysGame); ací només hi ha
|
# de moviment del jugador viuen a part (Defaults::KeysGame); ací només hi ha
|
||||||
@@ -35,9 +35,6 @@ keys:
|
|||||||
- id: toggle_render_info
|
- id: toggle_render_info
|
||||||
code: "F10"
|
code: "F10"
|
||||||
desc: "Cicle de l'overlay de debug: apagat / dalt / baix"
|
desc: "Cicle de l'overlay de debug: apagat / dalt / baix"
|
||||||
- id: pause
|
|
||||||
code: "F11"
|
|
||||||
desc: "Pausa el joc (o botó START del mando)"
|
|
||||||
- id: menu_toggle
|
- id: menu_toggle
|
||||||
code: "F12"
|
code: "F12"
|
||||||
desc: "Obri / tanca el menú de servei"
|
desc: "Obri / tanca el menú de servei"
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# Configuració global del sistema d'il·luminació.
|
||||||
|
#
|
||||||
|
# Cada llum emissora (player, torxa, mòmia, ...) té un únic paràmetre
|
||||||
|
# propi: el radi en píxels. La forma de la llum dins d'aquest radi
|
||||||
|
# (els tres anells del gradient) viu en aquest fitxer perquè és una
|
||||||
|
# decisió de "feel" comuna a totes les entitats — així pots tocar la
|
||||||
|
# corba aquí sense haver de modificar 10 YAMLs.
|
||||||
|
#
|
||||||
|
# Forma del gradient (centre → vora):
|
||||||
|
# 0..inner_ratio del radi → inner_intensity (zona totalment il·luminada)
|
||||||
|
# inner_ratio..mid_ratio → mid_intensity (anell mig)
|
||||||
|
# mid_ratio..1.0 → outer_intensity (anell exterior)
|
||||||
|
# fora del radi → light_level de la sala
|
||||||
|
#
|
||||||
|
# Hi ha dues corbes paral·leles, una per mode, perquè la mateixa escala
|
||||||
|
# 0..1 vol dir coses molt diferents:
|
||||||
|
# `gradient` → subtractive: 1.0 = "no fosqueix" el píxel
|
||||||
|
# (l'emissor "trenca" una sala fosca).
|
||||||
|
# `gradient_additive` → additive: 1.0 = "afegeix el color sencer"
|
||||||
|
# (satura). Valors molt més baixos donen un
|
||||||
|
# halo cromàtic subtil sobre una sala normal.
|
||||||
|
gradient:
|
||||||
|
inner_ratio: 0.5
|
||||||
|
mid_ratio: 0.85
|
||||||
|
inner_intensity: 1.0
|
||||||
|
mid_intensity: 0.8
|
||||||
|
outer_intensity: 0.6
|
||||||
|
|
||||||
|
# Corba del mode additive. Valors petits a propòsit: només volem un
|
||||||
|
# toc de tint cromàtic prop de l'emissor — fora del nucli, la sala
|
||||||
|
# original ha de quedar pràcticament intacta. Si pugeu massa l'inner,
|
||||||
|
# els colors de l'emissor "inunden" la sala (additiu satura ràpid).
|
||||||
|
gradient_additive:
|
||||||
|
inner_ratio: 0.5
|
||||||
|
mid_ratio: 0.85
|
||||||
|
inner_intensity: 0.15
|
||||||
|
mid_intensity: 0.10
|
||||||
|
outer_intensity: 0.05
|
||||||
|
|
||||||
|
# Modulació del radi quan una entitat declara `flicker: true` al seu
|
||||||
|
# bloc `light:`. El radi efectiu per frame és:
|
||||||
|
# r_eff = r_base + (r_amp1·r_base)·sin(2π·f1·t + φ1) + (r_amp2·r_base)·sin(2π·f2·t + φ2)
|
||||||
|
# on (φ1, φ2) es deriven de la seed de l'emissor per a que cada
|
||||||
|
# antorxa balancegi en una fase diferent (sense estat per emissor).
|
||||||
|
# Les amplituds són **ràtios** sobre el radi base: així el feel del
|
||||||
|
# flicker és independent del radi (una torxa de 24 px oscil·la el
|
||||||
|
# mateix % que una de 40 px). Combinant dues sinusoides amb freqüències
|
||||||
|
# incommensurables s'obté un patró "fluid" que el ull llegeix com a
|
||||||
|
# foc; pujar `noise_*` aporta més inquietud, baixar-ho fa la flama
|
||||||
|
# més serena. Aquests són els defaults globals; cada tipus d'entitat
|
||||||
|
# (o cada spawn / sala) pot sobreescriure'ls al seu `light.flicker_params:`.
|
||||||
|
flicker:
|
||||||
|
wave_amplitude_ratio: 0.05 # 5% del radi (per a radi 40 → ±2 px, feel actual)
|
||||||
|
wave_frequency_hz: 3.0
|
||||||
|
noise_amplitude_ratio: 0.025 # 2.5% (per a radi 40 → ±1 px, feel actual)
|
||||||
|
noise_frequency_hz: 7.0
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
# Tema de la UI: índexs de paleta, timings, fonts i flags visuals. Tota
|
||||||
|
# clau absent cau al valor compilat a Defaults::UI (veure defaults.hpp).
|
||||||
|
|
||||||
|
colors:
|
||||||
|
render_info_text: 58
|
||||||
|
render_info_shadow: 16
|
||||||
|
canvas_bg: 72
|
||||||
|
notification_text: 232
|
||||||
|
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
|
||||||
|
# de notification_* perquè la fase i les notificacions són dos canals
|
||||||
|
# visuals distints: ací pots canviar el color del rètol sense tocar
|
||||||
|
# els missatges del HUD. Comparteix la família del CENTER del HUD
|
||||||
|
# (or sobre marró) perquè "Piràmide N / Sala N" sigui sempre daurat
|
||||||
|
# arreu del joc.
|
||||||
|
room_text: 55 # or brillant (#FFDA00, = hud_center.value)
|
||||||
|
room_text_outline: 10 # marró cremat (#482400, = hud_center.shadow)
|
||||||
|
|
||||||
|
timings:
|
||||||
|
notification_ms: 4000
|
||||||
|
exit_ms: 4000
|
||||||
|
|
||||||
|
fonts:
|
||||||
|
hud:
|
||||||
|
name: "8bithud"
|
||||||
|
gif: "data/fonts/8bithud.gif"
|
||||||
|
fnt: "data/fonts/8bithud.fnt"
|
||||||
|
kerning: 1
|
||||||
|
placeholder:
|
||||||
|
name: "ari-w9500-condensed-display"
|
||||||
|
gif: "data/fonts/ari-w9500-condensed-display.gif"
|
||||||
|
fnt: "data/fonts/ari-w9500-condensed-display.fnt"
|
||||||
|
kerning: 1
|
||||||
|
# Font del rètol de transició de sala (ROOM_INTRO i ROOM_OUTRO).
|
||||||
|
room_text:
|
||||||
|
name: "CaesarDressing-Regular"
|
||||||
|
gif: "data/fonts/CaesarDressing-Regular.gif"
|
||||||
|
fnt: "data/fonts/CaesarDressing-Regular.fnt"
|
||||||
|
kerning: 1
|
||||||
|
menu_title:
|
||||||
|
name: "ari-w9500-display"
|
||||||
|
gif: "data/fonts/ari-w9500-display.gif"
|
||||||
|
fnt: "data/fonts/ari-w9500-display.fnt"
|
||||||
|
kerning: 0
|
||||||
|
menu_items:
|
||||||
|
name: "ari-w9500-condensed-display"
|
||||||
|
gif: "data/fonts/ari-w9500-condensed-display.gif"
|
||||||
|
fnt: "data/fonts/ari-w9500-condensed-display.fnt"
|
||||||
|
kerning: 0
|
||||||
|
# Copyright de la TITLE: pixel-art estricte (Tiny5-Regular size 5,
|
||||||
|
# caixa 3×6 px) generat amb font-gen. Diferenciada de la `placeholder`
|
||||||
|
# que pinta el "press start" — així la jerarquia tipogràfica del peu
|
||||||
|
# queda neta.
|
||||||
|
copyright:
|
||||||
|
name: "Tiny5-Regular"
|
||||||
|
gif: "data/fonts/Tiny5-Regular.gif"
|
||||||
|
fnt: "data/fonts/Tiny5-Regular.fnt"
|
||||||
|
kerning: 1
|
||||||
|
|
||||||
|
hud:
|
||||||
|
bg_enabled: false
|
||||||
|
entry_ms: 800 # durada de l'animació d'entrada dels tres blocs
|
||||||
|
flash_ms: 250 # durada del flash quan un jugador rep un hit
|
||||||
|
shake_ms: 300 # durada del shake que acompanya el flash
|
||||||
|
# Parpadeig dels prompts inactius ("Polsa start..." / "Espera..."). Un
|
||||||
|
# únic temporitzador global al Hud sincronitza P1 i P2 i no es reinicia
|
||||||
|
# quan el text canvia entre CAN_JOIN i WAIT. cycle<=0 o ratio>=1 →
|
||||||
|
# sempre visible (disable trivial).
|
||||||
|
blink_cycle_ms: 1000 # durada del cicle complet
|
||||||
|
blink_visible_ratio: 0.8 # proporció del cicle visible (0..1)
|
||||||
|
|
||||||
|
# Famílies de color del HUD per bloc. Cada bloc té `label` (to apagat
|
||||||
|
# per etiquetes), `value` (to viu per a la xifra) i `shadow` (to fosc
|
||||||
|
# del mateix matís — efecte embossing 1 px a baix-dreta). Si manca un
|
||||||
|
# camp, cau al default compilat a `Defaults::UI`. El `colors.hud_flash`
|
||||||
|
# és comú a totes tres famílies.
|
||||||
|
hud_p1:
|
||||||
|
label: 7 # vermell pur
|
||||||
|
value: 183 # crema-préssec
|
||||||
|
shadow: 2 # vermell fosc
|
||||||
|
hud_center:
|
||||||
|
label: 29 # ambre cremat
|
||||||
|
value: 55 # or brillant
|
||||||
|
shadow: 10 # marró cremat
|
||||||
|
hud_p2:
|
||||||
|
label: 248 # cian pur
|
||||||
|
value: 253 # blau pàl·lid
|
||||||
|
shadow: 64 # blau marí
|
||||||
|
|
||||||
|
# Geometria del popup superior (Notifier). En píxels del canvas, llevat de
|
||||||
|
# slide_ms (durada en ms de l'animació d'entrada i d'eixida; constant
|
||||||
|
# independentment de la mida de la caixa). El padding vertical és asimètric a
|
||||||
|
# propòsit: padding_top > padding_bottom puja una mica el text dins la caixa.
|
||||||
|
# Corbes disponibles: linear, outQuad, inQuad, inOutQuad, outCubic, inCubic.
|
||||||
|
notifier:
|
||||||
|
padding_h: 6
|
||||||
|
padding_top: 4
|
||||||
|
padding_bottom: 3
|
||||||
|
line_gap: 1
|
||||||
|
margin_top: 3
|
||||||
|
border_px: 1
|
||||||
|
slide_ms: 300
|
||||||
|
slide_in_curve: outCubic
|
||||||
|
slide_out_curve: inCubic
|
||||||
|
|
||||||
|
# Sons del menú de servei. Els fitxers viuen a data/audio/sounds/. SELECT
|
||||||
|
# acompanya navegació (amunt/avall) i back; ACCEPT acompanya modificació
|
||||||
|
# d'opció (esquerra/dreta/retorn) o entrada a un submenú.
|
||||||
|
sounds:
|
||||||
|
menu_select: "menu_select.wav"
|
||||||
|
menu_accept: "menu_accept.wav"
|
||||||
|
# Disparat quan un jugador prem START a la TITLE (primera pulsació o
|
||||||
|
# la del segon jugador dins de la finestra de STARTING).
|
||||||
|
player_start: "player_start.wav"
|
||||||
|
# Soroll/scratch sincronitzat amb la fase SCAN de LogoScene (scanline
|
||||||
|
# reveal). Es dispara al constructor de l'escena.
|
||||||
|
logo_glitch: "glitch.wav"
|
||||||
|
After Width: | Height: | Size: 805 B |
@@ -0,0 +1,22 @@
|
|||||||
|
name: column
|
||||||
|
type: decoration
|
||||||
|
editable: true
|
||||||
|
# Sub-picker de frames a l'editor: 6 variants (atlas 2×3 de sub-sprites 16×32).
|
||||||
|
editable_variants: true
|
||||||
|
sprite:
|
||||||
|
path: column.gif
|
||||||
|
size: [16, 32]
|
||||||
|
# Ancoratge "feet": el tile [col, row] del spawn és la base de la columna.
|
||||||
|
# El sprite de 16×32 sobresurt 16 px cap amunt sense afectar col·lisió ni
|
||||||
|
# z-order (la caixa lògica és el tile inferior).
|
||||||
|
anchor: feet
|
||||||
|
tile_size: [1, 1]
|
||||||
|
collision:
|
||||||
|
size: [16, 16]
|
||||||
|
# Mur infranquejable al salt: el jugador no pot passar pel damunt.
|
||||||
|
height: 999
|
||||||
|
flags:
|
||||||
|
collides: true
|
||||||
|
interactable: false
|
||||||
|
animations:
|
||||||
|
default: { frames: [0], frame_ms: 1000, loop: false }
|
||||||
@@ -21,9 +21,9 @@ flags:
|
|||||||
collides: true
|
collides: true
|
||||||
interactable: true
|
interactable: true
|
||||||
animations:
|
animations:
|
||||||
closed: { frames: [1], fps: 1, loop: true }
|
closed: { frames: [1], frame_ms: 1000, loop: true }
|
||||||
opening: { frames: [0], fps: 6, loop: false }
|
opening: { frames: [0], frame_ms: 167, loop: false }
|
||||||
open: { frames: [0], fps: 1, loop: true }
|
open: { frames: [0], frame_ms: 1000, loop: true }
|
||||||
behaviour:
|
behaviour:
|
||||||
states: [CLOSED, OPENING, OPEN]
|
states: [CLOSED, OPENING, OPEN]
|
||||||
initial_state: CLOSED
|
initial_state: CLOSED
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 306 B After Width: | Height: | Size: 303 B |
@@ -16,7 +16,7 @@ flags:
|
|||||||
# apropiada via `setVariant` abans del primer tick.
|
# apropiada via `setVariant` abans del primer tick.
|
||||||
# jump: frames 0..6 (7 frames) → reproduït al despegue.
|
# jump: frames 0..6 (7 frames) → reproduït al despegue.
|
||||||
# land: frames 7..12 (6 frames) → reproduït a l'aterratge.
|
# land: frames 7..12 (6 frames) → reproduït a l'aterratge.
|
||||||
# fps: 10 = 100 ms per frame.
|
# frame_ms: 67 ≈ 15 fps (1000 / 67 ≈ 14.9 fps).
|
||||||
animations:
|
animations:
|
||||||
jump: { frames: [0, 1, 2, 3, 4, 5, 6], fps: 15, loop: false }
|
jump: { frames: [0, 1, 2, 3, 4, 5, 6], frame_ms: 67, loop: false }
|
||||||
land: { frames: [7, 8, 9, 10, 11, 12], fps: 15, loop: false }
|
land: { frames: [7, 8, 9, 10, 11, 12], frame_ms: 67, loop: false }
|
||||||
@@ -13,8 +13,8 @@ flags:
|
|||||||
collides: false
|
collides: false
|
||||||
interactable: false
|
interactable: false
|
||||||
animations:
|
animations:
|
||||||
inactive: { frames: [0], fps: 1, loop: true }
|
inactive: { frames: [0], frame_ms: 1000, loop: true }
|
||||||
active: { frames: [0], fps: 6, loop: true }
|
active: { frames: [0], frame_ms: 167, loop: true }
|
||||||
cycle:
|
cycle:
|
||||||
active_duration: 0.3
|
active_duration: 0.3
|
||||||
inactive_duration: 2.0
|
inactive_duration: 2.0
|
||||||
|
|||||||
@@ -22,6 +22,6 @@ flags:
|
|||||||
# lectura genèrica de flags.
|
# lectura genèrica de flags.
|
||||||
triggers_on_overlap: true
|
triggers_on_overlap: true
|
||||||
animations:
|
animations:
|
||||||
idle: { frames: [0], fps: 1, loop: true }
|
idle: { frames: [0], frame_ms: 1000, loop: true }
|
||||||
stats:
|
stats:
|
||||||
damage: 0
|
damage: 0
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -32,14 +32,28 @@ flags:
|
|||||||
# no decideix flip per direcció (només selecciona quina animació mostrar).
|
# no decideix flip per direcció (només selecciona quina animació mostrar).
|
||||||
# La momia no té variants `stand_*` perquè mai no para.
|
# La momia no té variants `stand_*` perquè mai no para.
|
||||||
animations:
|
animations:
|
||||||
walk_south: { frames: [0, 1, 2, 3], fps: 8, loop: true }
|
walk_south: { frames: [0, 1, 2, 3], frame_ms: 125, loop: true }
|
||||||
walk_north: { frames: [4, 5, 6, 7], fps: 8, loop: true }
|
walk_north: { frames: [4, 5, 6, 7], frame_ms: 125, loop: true }
|
||||||
walk_east: { frames: [8, 9, 10, 11], fps: 8, loop: true }
|
walk_east: { frames: [8, 9, 10, 11], frame_ms: 125, loop: true }
|
||||||
walk_west: { frames: [8, 9, 10, 11], fps: 8, loop: true, flip_h: true }
|
walk_west: { frames: [8, 9, 10, 11], frame_ms: 125, loop: true, flip_h: true }
|
||||||
|
# Alçament: els 15 primers frames passen ràpid (125 ms cadascun) i el
|
||||||
|
# darrer aguanta 1 s perquè el jugador pugui veure la mòmia ja alçada
|
||||||
|
# abans que comenci a deambular. `frame_ms` accepta una llista per-frame
|
||||||
|
# que mapeja 1:1 contra `frames`.
|
||||||
|
raise:
|
||||||
|
frames: [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]
|
||||||
|
frame_ms: [125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 1000]
|
||||||
|
loop: false
|
||||||
ai:
|
ai:
|
||||||
type: corridor_wanderer
|
type: corridor_wanderer
|
||||||
direction_choice: random
|
direction_choice: random
|
||||||
initial_direction: east
|
initial_direction: east
|
||||||
|
|
||||||
|
# Estat SPAWN: la mòmia s'instancia al pixel_rect_ inicial quan
|
||||||
|
# GameScene crida `spawnPendingActors()` (subfase text-exit de
|
||||||
|
# ROOM_INTRO, fase 4) amb l'animació `raise` activa. La duració del
|
||||||
|
# SPAWN la marca la pròpia animació (no-loop): al `completed()`,
|
||||||
|
# transita a ROAMING i comença a deambular.
|
||||||
stats:
|
stats:
|
||||||
speed: 30
|
speed: 30
|
||||||
damage: 1
|
damage: 1
|
||||||
@@ -58,3 +72,13 @@ hurt:
|
|||||||
duration_ms: 600
|
duration_ms: 600
|
||||||
speed: 60
|
speed: 60
|
||||||
whippable: true
|
whippable: true
|
||||||
|
# So opcional: basename del fitxer dins `data/audio/sounds/`. Es dispara
|
||||||
|
# un sol cop per fuetada quan la mòmia entra a HURT. Si manca, no sona.
|
||||||
|
sound: mummy_hurt.wav
|
||||||
|
|
||||||
|
# Halo tènue al voltant de la mòmia. Radi més curt que el del jugador
|
||||||
|
# perquè el rastre lumínic delati la seva posició sense "il·luminar
|
||||||
|
# la sala" — vol ser una pista visual a la foscor.
|
||||||
|
light:
|
||||||
|
radius: 24
|
||||||
|
flicker: true
|
||||||
@@ -30,20 +30,20 @@ animations:
|
|||||||
stun_east: { frames: [18] }
|
stun_east: { frames: [18] }
|
||||||
stun_west: { frames: [18], flip_h: true }
|
stun_west: { frames: [18], flip_h: true }
|
||||||
|
|
||||||
walk_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
walk_south: { frames: [1, 2, 3, 0], frame_ms: 100 }
|
||||||
walk_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
walk_north: { frames: [5, 6, 7, 4], frame_ms: 100 }
|
||||||
walk_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
|
walk_east: { frames: [9, 10, 11, 8], frame_ms: 100 }
|
||||||
walk_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
|
walk_west: { frames: [9, 10, 11, 8], frame_ms: 100, flip_h: true }
|
||||||
|
|
||||||
hurt_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
hurt_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
||||||
hurt_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
hurt_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
||||||
hurt_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
|
hurt_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
|
||||||
hurt_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
|
hurt_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
|
||||||
|
|
||||||
attack_south: { frames: [20, 21, 22, 23], frame_ms: 50 }
|
attack_south: { frames: [20, 21, 22, 23], frame_ms: 80 }
|
||||||
attack_north: { frames: [24, 25, 26, 27], frame_ms: 50 }
|
attack_north: { frames: [24, 25, 26, 27], frame_ms: 80 }
|
||||||
attack_east: { frames: [28, 29, 30, 31], frame_ms: 50 }
|
attack_east: { frames: [28, 29, 30, 31], frame_ms: 80 }
|
||||||
attack_west: { frames: [28, 29, 30, 31], frame_ms: 50, flip_h: true }
|
attack_west: { frames: [28, 29, 30, 31], frame_ms: 80, flip_h: true }
|
||||||
|
|
||||||
recover_south: { frames: [23] }
|
recover_south: { frames: [23] }
|
||||||
recover_north: { frames: [27] }
|
recover_north: { frames: [27] }
|
||||||
@@ -97,13 +97,10 @@ turn:
|
|||||||
south_to_north: west
|
south_to_north: west
|
||||||
|
|
||||||
# Salt vertical (px de z, no toca la posició al món).
|
# Salt vertical (px de z, no toca la posició al món).
|
||||||
# `shadow_y_offset`: puja la sombra per compensar la franja
|
|
||||||
# transparent sota els peus del sprite.
|
|
||||||
jump:
|
jump:
|
||||||
impulse: 200
|
impulse: 200
|
||||||
gravity: 600
|
gravity: 600
|
||||||
landing_stun_ms: 400
|
landing_stun_ms: 400
|
||||||
shadow_y_offset: 3
|
|
||||||
sound: jump.wav
|
sound: jump.wav
|
||||||
land_sound: land.wav
|
land_sound: land.wav
|
||||||
|
|
||||||
@@ -119,7 +116,7 @@ hurt:
|
|||||||
# recuperació en què la hitbox ja no fa dany.
|
# recuperació en què la hitbox ja no fa dany.
|
||||||
whip:
|
whip:
|
||||||
range_tiles: 3
|
range_tiles: 3
|
||||||
swing_ms: 200
|
swing_ms: 320
|
||||||
cooldown_ms: 200
|
cooldown_ms: 200
|
||||||
damage: 1
|
damage: 1
|
||||||
sound: whip.wav
|
sound: whip.wav
|
||||||
@@ -136,10 +133,18 @@ death:
|
|||||||
# poden ferir ni matar el jugador (els forats sí que el maten — la
|
# poden ferir ni matar el jugador (els forats sí que el maten — la
|
||||||
# caiguda no és dany convencional). Visualment es manifesta amb un
|
# caiguda no és dany convencional). Visualment es manifesta amb un
|
||||||
# parpadeig que alterna la paleta normal del sprite amb la paleta
|
# parpadeig que alterna la paleta normal del sprite amb la paleta
|
||||||
# `palette` (basename de `data/player/<name>.pal`) cada
|
# `palette` (basename de `data/entities/player/<name>.pal`) cada
|
||||||
# `blink_period_ms` ms. Si `duration_ms` és 0 (o el bloc manca), la
|
# `blink_period_ms` ms. Si `duration_ms` és 0 (o el bloc manca), la
|
||||||
# mecànica queda desactivada i el comportament és l'antic.
|
# mecànica queda desactivada i el comportament és l'antic.
|
||||||
immunity:
|
immunity:
|
||||||
duration_ms: 3000
|
duration_ms: 3000
|
||||||
blink_period_ms: 100
|
blink_period_ms: 100
|
||||||
palette: immune
|
palette: morcus_wash
|
||||||
|
|
||||||
|
# Llum emesa pel jugador. `radius` és el radi del cercle de llum
|
||||||
|
# en píxels del canvas; el gradient (3 anells: 1.0 / 0.9 / 0.8) viu
|
||||||
|
# a data/config/lighting.yaml i és compartit per tots els emissors.
|
||||||
|
# Omet el bloc per desactivar la llum d'aquest personatge.
|
||||||
|
light:
|
||||||
|
radius: 64
|
||||||
|
flicker: true
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
JASC-PAL
|
||||||
|
0100
|
||||||
|
16
|
||||||
|
0 0 0 255
|
||||||
|
150 133 88 255
|
||||||
|
253 233 185 255
|
||||||
|
237 215 165 255
|
||||||
|
214 193 145 255
|
||||||
|
255 252 208 255
|
||||||
|
252 234 189 255
|
||||||
|
239 222 176 255
|
||||||
|
175 190 128 255
|
||||||
|
173 175 115 255
|
||||||
|
159 159 104 255
|
||||||
|
246 211 157 255
|
||||||
|
229 192 139 255
|
||||||
|
213 177 127 255
|
||||||
|
243 226 184 255
|
||||||
|
0 0 0 255
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
JASC-PAL
|
||||||
|
0100
|
||||||
|
16
|
||||||
|
0 0 0 255
|
||||||
|
102 118 132 255
|
||||||
|
226 238 247 255
|
||||||
|
206 216 222 255
|
||||||
|
179 190 198 255
|
||||||
|
245 255 255 255
|
||||||
|
223 239 253 255
|
||||||
|
208 224 238 255
|
||||||
|
124 190 178 255
|
||||||
|
125 170 162 255
|
||||||
|
109 151 150 255
|
||||||
|
221 210 210 255
|
||||||
|
200 187 189 255
|
||||||
|
181 169 175 255
|
||||||
|
213 229 247 255
|
||||||
|
0 0 0 255
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
JASC-PAL
|
||||||
|
0100
|
||||||
|
16
|
||||||
|
0 0 0 255
|
||||||
|
190 190 190 255
|
||||||
|
255 255 255 255
|
||||||
|
255 248 239 255
|
||||||
|
239 233 226 255
|
||||||
|
255 255 255 255
|
||||||
|
255 255 255 255
|
||||||
|
254 254 254 255
|
||||||
|
184 244 213 255
|
||||||
|
193 229 202 255
|
||||||
|
184 216 198 255
|
||||||
|
255 242 225 255
|
||||||
|
255 228 213 255
|
||||||
|
251 216 207 255
|
||||||
|
255 255 255 255
|
||||||
|
0 0 0 255
|
||||||
@@ -30,20 +30,20 @@ animations:
|
|||||||
stun_east: { frames: [18] }
|
stun_east: { frames: [18] }
|
||||||
stun_west: { frames: [18], flip_h: true }
|
stun_west: { frames: [18], flip_h: true }
|
||||||
|
|
||||||
walk_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
walk_south: { frames: [1, 2, 3, 0], frame_ms: 100 }
|
||||||
walk_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
walk_north: { frames: [5, 6, 7, 4], frame_ms: 100 }
|
||||||
walk_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
|
walk_east: { frames: [9, 10, 11, 8], frame_ms: 100 }
|
||||||
walk_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
|
walk_west: { frames: [9, 10, 11, 8], frame_ms: 100, flip_h: true }
|
||||||
|
|
||||||
hurt_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
hurt_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
||||||
hurt_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
hurt_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
||||||
hurt_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
|
hurt_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
|
||||||
hurt_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
|
hurt_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
|
||||||
|
|
||||||
attack_south: { frames: [20, 21, 22, 23], frame_ms: 50 }
|
attack_south: { frames: [20, 21, 22, 23], frame_ms: 80 }
|
||||||
attack_north: { frames: [24, 25, 26, 27], frame_ms: 50 }
|
attack_north: { frames: [24, 25, 26, 27], frame_ms: 80 }
|
||||||
attack_east: { frames: [28, 29, 30, 31], frame_ms: 50 }
|
attack_east: { frames: [28, 29, 30, 31], frame_ms: 80 }
|
||||||
attack_west: { frames: [28, 29, 30, 31], frame_ms: 50, flip_h: true }
|
attack_west: { frames: [28, 29, 30, 31], frame_ms: 80, flip_h: true }
|
||||||
|
|
||||||
recover_south: { frames: [23] }
|
recover_south: { frames: [23] }
|
||||||
recover_north: { frames: [27] }
|
recover_north: { frames: [27] }
|
||||||
@@ -97,13 +97,10 @@ turn:
|
|||||||
south_to_north: west
|
south_to_north: west
|
||||||
|
|
||||||
# Salt vertical (px de z, no toca la posició al món).
|
# Salt vertical (px de z, no toca la posició al món).
|
||||||
# `shadow_y_offset`: puja la sombra per compensar la franja
|
|
||||||
# transparent sota els peus del sprite.
|
|
||||||
jump:
|
jump:
|
||||||
impulse: 200
|
impulse: 200
|
||||||
gravity: 600
|
gravity: 600
|
||||||
landing_stun_ms: 400
|
landing_stun_ms: 400
|
||||||
shadow_y_offset: 3
|
|
||||||
sound: jump.wav
|
sound: jump.wav
|
||||||
land_sound: land.wav
|
land_sound: land.wav
|
||||||
|
|
||||||
@@ -119,7 +116,7 @@ hurt:
|
|||||||
# recuperació en què la hitbox ja no fa dany.
|
# recuperació en què la hitbox ja no fa dany.
|
||||||
whip:
|
whip:
|
||||||
range_tiles: 3
|
range_tiles: 3
|
||||||
swing_ms: 200
|
swing_ms: 320
|
||||||
cooldown_ms: 200
|
cooldown_ms: 200
|
||||||
damage: 1
|
damage: 1
|
||||||
sound: whip.wav
|
sound: whip.wav
|
||||||
@@ -136,10 +133,18 @@ death:
|
|||||||
# poden ferir ni matar el jugador (els forats sí que el maten — la
|
# poden ferir ni matar el jugador (els forats sí que el maten — la
|
||||||
# caiguda no és dany convencional). Visualment es manifesta amb un
|
# caiguda no és dany convencional). Visualment es manifesta amb un
|
||||||
# parpadeig que alterna la paleta normal del sprite amb la paleta
|
# parpadeig que alterna la paleta normal del sprite amb la paleta
|
||||||
# `palette` (basename de `data/player/<name>.pal`) cada
|
# `palette` (basename de `data/entities/player/<name>.pal`) cada
|
||||||
# `blink_period_ms` ms. Si `duration_ms` és 0 (o el bloc manca), la
|
# `blink_period_ms` ms. Si `duration_ms` és 0 (o el bloc manca), la
|
||||||
# mecànica queda desactivada i el comportament és l'antic.
|
# mecànica queda desactivada i el comportament és l'antic.
|
||||||
immunity:
|
immunity:
|
||||||
duration_ms: 3000
|
duration_ms: 3000
|
||||||
blink_period_ms: 100
|
blink_period_ms: 100
|
||||||
palette: immune
|
palette: sam_wash
|
||||||
|
|
||||||
|
# Llum emesa pel jugador. `radius` és el radi del cercle de llum
|
||||||
|
# en píxels del canvas; el gradient (3 anells: 1.0 / 0.9 / 0.8) viu
|
||||||
|
# a data/config/lighting.yaml i és compartit per tots els emissors.
|
||||||
|
# Omet el bloc per desactivar la llum d'aquest personatge.
|
||||||
|
light:
|
||||||
|
radius: 64
|
||||||
|
flicker: true
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
JASC-PAL
|
||||||
|
0100
|
||||||
|
16
|
||||||
|
0 0 0 255
|
||||||
|
150 133 88 255
|
||||||
|
202 176 123 255
|
||||||
|
191 165 112 255
|
||||||
|
181 156 103 255
|
||||||
|
217 158 115 255
|
||||||
|
194 142 101 255
|
||||||
|
188 143 98 255
|
||||||
|
214 193 145 255
|
||||||
|
196 176 128 255
|
||||||
|
181 161 115 255
|
||||||
|
246 211 157 255
|
||||||
|
229 192 139 255
|
||||||
|
213 177 127 255
|
||||||
|
243 226 184 255
|
||||||
|
0 0 0 255
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
JASC-PAL
|
||||||
|
0100
|
||||||
|
16
|
||||||
|
0 0 0 255
|
||||||
|
102 118 132 255
|
||||||
|
166 169 171 255
|
||||||
|
152 156 158 255
|
||||||
|
140 144 147 255
|
||||||
|
191 144 160 255
|
||||||
|
161 125 146 255
|
||||||
|
152 127 142 255
|
||||||
|
179 190 198 255
|
||||||
|
157 168 179 255
|
||||||
|
139 151 164 255
|
||||||
|
221 210 210 255
|
||||||
|
200 187 189 255
|
||||||
|
181 169 175 255
|
||||||
|
213 229 247 255
|
||||||
|
0 0 0 255
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
JASC-PAL
|
||||||
|
0100
|
||||||
|
16
|
||||||
|
0 0 0 255
|
||||||
|
190 190 190 255
|
||||||
|
235 220 205 255
|
||||||
|
226 212 197 255
|
||||||
|
219 205 191 255
|
||||||
|
255 194 197 255
|
||||||
|
248 184 193 255
|
||||||
|
238 188 189 255
|
||||||
|
239 233 226 255
|
||||||
|
226 220 216 255
|
||||||
|
214 210 208 255
|
||||||
|
255 242 225 255
|
||||||
|
255 228 213 255
|
||||||
|
251 216 207 255
|
||||||
|
255 255 255 255
|
||||||
|
0 0 0 255
|
||||||
@@ -13,7 +13,7 @@ flags:
|
|||||||
collides: true
|
collides: true
|
||||||
interactable: false
|
interactable: false
|
||||||
animations:
|
animations:
|
||||||
roll: { frames: [0], fps: 10, loop: true }
|
roll: { frames: [0], frame_ms: 100, loop: true }
|
||||||
ai:
|
ai:
|
||||||
type: straight_line
|
type: straight_line
|
||||||
initial_direction: right
|
initial_direction: right
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 848 B After Width: | Height: | Size: 1.6 KiB |
@@ -19,12 +19,30 @@ flags:
|
|||||||
collides: true
|
collides: true
|
||||||
interactable: true
|
interactable: true
|
||||||
animations:
|
animations:
|
||||||
closed: { frames: [0], fps: 1, loop: true }
|
closed: { frames: [0], frame_ms: 1000, loop: true }
|
||||||
opening: { frames: [0], fps: 6, loop: false }
|
opening: { frames: [1, 2], frame_ms: 167, loop: false }
|
||||||
open: { frames: [0], fps: 1, loop: true }
|
open: { frames: [3], frame_ms: 1000, loop: true }
|
||||||
behaviour:
|
behaviour:
|
||||||
states: [CLOSED, OPENING, OPEN]
|
states: [CLOSED, OPENING, OPEN]
|
||||||
initial_state: CLOSED
|
initial_state: CLOSED
|
||||||
on_perimeter_complete: OPENING
|
on_perimeter_complete: OPENING
|
||||||
on_opening_end: OPEN
|
on_opening_end: OPEN
|
||||||
emits_event_on_open: sarcophagus_opened
|
emits_event_on_open: sarcophagus_opened
|
||||||
|
# Sons opcionals. `opening` es dispara un sol cop en la transició
|
||||||
|
# CLOSED → OPENING (quan el jugador completa el perímetre). Si la clau
|
||||||
|
# manca, no sona res. El basename val tant de clau al Resource::Cache
|
||||||
|
# com de path (`Defaults::Paths::AUDIO_SOUNDS_DIR + key`).
|
||||||
|
#
|
||||||
|
# `opening_pitch_spread`: variació uniforme del ratio de reproducció
|
||||||
|
# centrada a 1.0. Amb 0.05 cada obertura sona entre 0.95× i 1.05× la
|
||||||
|
# velocitat normal (puja/baixa també el to, efecte arcade clàssic).
|
||||||
|
# 0 ⇒ sempre 1.0 (sense variació).
|
||||||
|
sound:
|
||||||
|
opening: open_sarc.wav
|
||||||
|
opening_pitch_spread: 0.5
|
||||||
|
# Recompensa que rep el jugador que pinta el tile que tanca el perímetre
|
||||||
|
# (el GameScene la reparteix consumint `sarcophagus_opened`). Si es lleva
|
||||||
|
# aquest bloc o un dels camps, el camp absent val 0 i no s'aplica.
|
||||||
|
reward:
|
||||||
|
points: 100
|
||||||
|
treasures: 1
|
||||||
|
After Width: | Height: | Size: 98 B |
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,22 @@
|
|||||||
|
name: stone
|
||||||
|
type: decoration
|
||||||
|
editable: true
|
||||||
|
# Sub-picker de frames a l'editor: ofereix una graella amb les 24 variants
|
||||||
|
# del atlas (6×4 sub-sprites 16×16) per escollir quina representa cada
|
||||||
|
# instància col·locada.
|
||||||
|
editable_variants: true
|
||||||
|
sprite:
|
||||||
|
path: stone.gif
|
||||||
|
size: [16, 16]
|
||||||
|
tile_size: [1, 1]
|
||||||
|
collision:
|
||||||
|
size: [16, 16]
|
||||||
|
# Mur infranquejable al salt: el jugador no la pot saltar per damunt.
|
||||||
|
height: 999
|
||||||
|
flags:
|
||||||
|
collides: true
|
||||||
|
interactable: false
|
||||||
|
# Animació "default" amb un sol frame: el spawn YAML pot sobreescriure-la
|
||||||
|
# via `frame: N` per fixar la variant escollida al editor.
|
||||||
|
animations:
|
||||||
|
default: { frames: [0], frame_ms: 1000, loop: false }
|
||||||
@@ -15,4 +15,19 @@ flags:
|
|||||||
# El torch.gif és un strip vertical 16x64 amb 4 frames apilats.
|
# El torch.gif és un strip vertical 16x64 amb 4 frames apilats.
|
||||||
# El render clipa cada frame per `anim.currentFrame()` × 16 píxels en Y.
|
# El render clipa cada frame per `anim.currentFrame()` × 16 píxels en Y.
|
||||||
animations:
|
animations:
|
||||||
idle: { frames: [0, 1, 2, 3], fps: 8, loop: true }
|
idle: { frames: [0, 1, 2, 3], frame_ms: 125, loop: true }
|
||||||
|
|
||||||
|
# Cercle de llum centrat a l'antorxa. El radi és en píxels del canvas;
|
||||||
|
# el gradient global (data/config/lighting.yaml) hi posa la forma (3
|
||||||
|
# anells). Amb `flicker: true` el radi efectiu oscil·la per frame
|
||||||
|
# (suma de dues sinusoides amb fase derivada de la posició) per a
|
||||||
|
# que el cercle "respiri" com una flama; els paràmetres del flicker
|
||||||
|
# també viuen al config global. `offset: [x, y]` desplaça el centre
|
||||||
|
# del cercle: la flama del sprite no està al centre del bounding box
|
||||||
|
# sinó cap a dalt, així que pujem la llum 4 px (Y creix cap avall)
|
||||||
|
# per a que el halo "neixi" de la flama, no del peu. Omet el bloc
|
||||||
|
# per fer una antorxa "apagada".
|
||||||
|
light:
|
||||||
|
radius: 40
|
||||||
|
flicker: true
|
||||||
|
offset: [0, -4]
|
||||||
@@ -13,8 +13,8 @@ flags:
|
|||||||
collides: false
|
collides: false
|
||||||
interactable: false
|
interactable: false
|
||||||
animations:
|
animations:
|
||||||
inactive: { frames: [0], fps: 1, loop: true }
|
inactive: { frames: [0], frame_ms: 1000, loop: true }
|
||||||
active: { frames: [0], fps: 6, loop: true }
|
active: { frames: [0], frame_ms: 167, loop: true }
|
||||||
cycle:
|
cycle:
|
||||||
active_duration: 0.5
|
active_duration: 0.5
|
||||||
inactive_duration: 1.5
|
inactive_duration: 1.5
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ sprites:
|
|||||||
# Animació compartida entre les 4 direccions: la seqüència de frames i les
|
# Animació compartida entre les 4 direccions: la seqüència de frames i les
|
||||||
# durades són idèntiques; només canvia quina surface/mida es clipa.
|
# durades són idèntiques; només canvia quina surface/mida es clipa.
|
||||||
# - `frames` : índex de tile dins l'spritesheet (graella deduïda).
|
# - `frames` : índex de tile dins l'spritesheet (graella deduïda).
|
||||||
# - `frame_ms` : durada uniforme per frame; 50 ms × 4 ≈ 200 ms swing.
|
# - `frame_ms` : durada uniforme per frame; 80 ms × 4 = 320 ms swing.
|
||||||
# - `loop: false` : el látigo viu una animació i mor (`markDead`).
|
# - `loop: false` : el látigo viu una animació i mor (`markDead`).
|
||||||
# - `active_frames` : valors dins de `frames` durant els quals la
|
# - `active_frames` : valors dins de `frames` durant els quals la
|
||||||
# hitbox aplica dany. Wind-up (0) i recovery (3) no
|
# hitbox aplica dany. Wind-up (0) i recovery (3) no
|
||||||
@@ -33,6 +33,6 @@ sprites:
|
|||||||
animations:
|
animations:
|
||||||
swing:
|
swing:
|
||||||
frames: [0, 1, 2, 3]
|
frames: [0, 1, 2, 3]
|
||||||
frame_ms: 50
|
frame_ms: 80
|
||||||
loop: false
|
loop: false
|
||||||
active_frames: [1, 2]
|
active_frames: [1, 2]
|
||||||
|
Before Width: | Height: | Size: 313 B After Width: | Height: | Size: 311 B |
|
Before Width: | Height: | Size: 378 B After Width: | Height: | Size: 363 B |
|
Before Width: | Height: | Size: 369 B After Width: | Height: | Size: 348 B |
|
Before Width: | Height: | Size: 313 B After Width: | Height: | Size: 327 B |
@@ -0,0 +1,133 @@
|
|||||||
|
# Font: Tiny5-Regular — generado desde Tiny5-Regular.ttf size 8
|
||||||
|
# Generado con font_gen.py
|
||||||
|
|
||||||
|
box_width 6
|
||||||
|
box_height 9
|
||||||
|
columns 15
|
||||||
|
|
||||||
|
# codepoint_decimal ancho_visual
|
||||||
|
32 2 # U+0020
|
||||||
|
33 1 # !
|
||||||
|
34 3 # "
|
||||||
|
35 5 # #
|
||||||
|
36 3 # $
|
||||||
|
37 4 # %
|
||||||
|
38 4 # &
|
||||||
|
39 1 # '
|
||||||
|
40 2 # (
|
||||||
|
41 2 # )
|
||||||
|
42 3 # *
|
||||||
|
43 3 # +
|
||||||
|
44 2 # ,
|
||||||
|
45 3 # -
|
||||||
|
46 1 # .
|
||||||
|
47 2 # /
|
||||||
|
48 3 # 0
|
||||||
|
49 2 # 1
|
||||||
|
50 3 # 2
|
||||||
|
51 3 # 3
|
||||||
|
52 3 # 4
|
||||||
|
53 3 # 5
|
||||||
|
54 3 # 6
|
||||||
|
55 3 # 7
|
||||||
|
56 3 # 8
|
||||||
|
57 3 # 9
|
||||||
|
58 1 # :
|
||||||
|
59 2 # ;
|
||||||
|
60 3 # <
|
||||||
|
61 3 # =
|
||||||
|
62 3 # >
|
||||||
|
63 3 # ?
|
||||||
|
64 4 # @
|
||||||
|
65 4 # A
|
||||||
|
66 4 # B
|
||||||
|
67 4 # C
|
||||||
|
68 4 # D
|
||||||
|
69 4 # E
|
||||||
|
70 4 # F
|
||||||
|
71 4 # G
|
||||||
|
72 4 # H
|
||||||
|
73 1 # I
|
||||||
|
74 3 # J
|
||||||
|
75 4 # K
|
||||||
|
76 3 # L
|
||||||
|
77 5 # M
|
||||||
|
78 4 # N
|
||||||
|
79 4 # O
|
||||||
|
80 4 # P
|
||||||
|
81 4 # Q
|
||||||
|
82 4 # R
|
||||||
|
83 4 # S
|
||||||
|
84 3 # T
|
||||||
|
85 4 # U
|
||||||
|
86 3 # V
|
||||||
|
87 5 # W
|
||||||
|
88 3 # X
|
||||||
|
89 3 # Y
|
||||||
|
90 3 # Z
|
||||||
|
91 2 # [
|
||||||
|
92 2 # \
|
||||||
|
93 2 # ]
|
||||||
|
94 3 # ^
|
||||||
|
95 3 # _
|
||||||
|
96 2 # `
|
||||||
|
97 3 # a
|
||||||
|
98 3 # b
|
||||||
|
99 3 # c
|
||||||
|
100 3 # d
|
||||||
|
101 3 # e
|
||||||
|
102 2 # f
|
||||||
|
103 3 # g
|
||||||
|
104 3 # h
|
||||||
|
105 1 # i
|
||||||
|
106 1 # j
|
||||||
|
107 3 # k
|
||||||
|
108 1 # l
|
||||||
|
109 5 # m
|
||||||
|
110 3 # n
|
||||||
|
111 3 # o
|
||||||
|
112 3 # p
|
||||||
|
113 3 # q
|
||||||
|
114 2 # r
|
||||||
|
115 3 # s
|
||||||
|
116 2 # t
|
||||||
|
117 3 # u
|
||||||
|
118 3 # v
|
||||||
|
119 5 # w
|
||||||
|
120 3 # x
|
||||||
|
121 3 # y
|
||||||
|
122 3 # z
|
||||||
|
123 3 # {
|
||||||
|
124 1 # |
|
||||||
|
125 3 # }
|
||||||
|
126 2 # ~
|
||||||
|
192 4 # À
|
||||||
|
193 4 # Á
|
||||||
|
200 4 # È
|
||||||
|
201 4 # É
|
||||||
|
204 2 # Ì
|
||||||
|
205 2 # Í
|
||||||
|
210 4 # Ò
|
||||||
|
211 4 # Ó
|
||||||
|
217 4 # Ù
|
||||||
|
218 4 # Ú
|
||||||
|
209 4 # Ñ
|
||||||
|
199 4 # Ç
|
||||||
|
224 3 # à
|
||||||
|
225 3 # á
|
||||||
|
232 3 # è
|
||||||
|
233 3 # é
|
||||||
|
236 2 # ì
|
||||||
|
237 2 # í
|
||||||
|
242 3 # ò
|
||||||
|
243 3 # ó
|
||||||
|
249 3 # ù
|
||||||
|
250 3 # ú
|
||||||
|
241 3 # ñ
|
||||||
|
231 3 # ç
|
||||||
|
161 1 # ¡
|
||||||
|
191 3 # ¿
|
||||||
|
171 5 # «
|
||||||
|
187 5 # »
|
||||||
|
183 1 # ·
|
||||||
|
169 3 # ©
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -2,7 +2,7 @@
|
|||||||
# Generado con font_gen.py
|
# Generado con font_gen.py
|
||||||
|
|
||||||
box_width 12
|
box_width 12
|
||||||
box_height 14
|
box_height 13
|
||||||
columns 15
|
columns 15
|
||||||
|
|
||||||
# codepoint_decimal ancho_visual
|
# codepoint_decimal ancho_visual
|
||||||
@@ -105,8 +105,8 @@ columns 15
|
|||||||
193 8 # Á
|
193 8 # Á
|
||||||
200 7 # È
|
200 7 # È
|
||||||
201 7 # É
|
201 7 # É
|
||||||
204 4 # Ì
|
204 3 # Ì
|
||||||
205 4 # Í
|
205 3 # Í
|
||||||
210 9 # Ò
|
210 9 # Ò
|
||||||
211 9 # Ó
|
211 9 # Ó
|
||||||
217 7 # Ù
|
217 7 # Ù
|
||||||
@@ -117,8 +117,8 @@ columns 15
|
|||||||
225 7 # á
|
225 7 # á
|
||||||
232 7 # è
|
232 7 # è
|
||||||
233 7 # é
|
233 7 # é
|
||||||
236 4 # ì
|
236 3 # ì
|
||||||
237 4 # í
|
237 3 # í
|
||||||
242 7 # ò
|
242 7 # ò
|
||||||
243 7 # ó
|
243 7 # ó
|
||||||
249 7 # ù
|
249 7 # ù
|
||||||
@@ -127,7 +127,7 @@ columns 15
|
|||||||
231 7 # ç
|
231 7 # ç
|
||||||
161 3 # ¡
|
161 3 # ¡
|
||||||
191 7 # ¿
|
191 7 # ¿
|
||||||
171 10 # «
|
171 7 # «
|
||||||
187 10 # »
|
187 7 # »
|
||||||
183 3 # ·
|
183 3 # ·
|
||||||
169 10 # ©
|
169 10 # ©
|
||||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.3 KiB |
@@ -2,7 +2,7 @@
|
|||||||
# Generado con font_gen.py
|
# Generado con font_gen.py
|
||||||
|
|
||||||
box_width 12
|
box_width 12
|
||||||
box_height 14
|
box_height 13
|
||||||
columns 15
|
columns 15
|
||||||
|
|
||||||
# codepoint_decimal ancho_visual
|
# codepoint_decimal ancho_visual
|
||||||
@@ -105,8 +105,8 @@ columns 15
|
|||||||
193 9 # Á
|
193 9 # Á
|
||||||
200 8 # È
|
200 8 # È
|
||||||
201 8 # É
|
201 8 # É
|
||||||
204 4 # Ì
|
204 3 # Ì
|
||||||
205 4 # Í
|
205 3 # Í
|
||||||
210 10 # Ò
|
210 10 # Ò
|
||||||
211 10 # Ó
|
211 10 # Ó
|
||||||
217 8 # Ù
|
217 8 # Ù
|
||||||
@@ -117,8 +117,8 @@ columns 15
|
|||||||
225 8 # á
|
225 8 # á
|
||||||
232 8 # è
|
232 8 # è
|
||||||
233 8 # é
|
233 8 # é
|
||||||
236 4 # ì
|
236 3 # ì
|
||||||
237 4 # í
|
237 3 # í
|
||||||
242 8 # ò
|
242 8 # ò
|
||||||
243 8 # ó
|
243 8 # ó
|
||||||
249 8 # ù
|
249 8 # ù
|
||||||
@@ -127,7 +127,7 @@ columns 15
|
|||||||
231 8 # ç
|
231 8 # ç
|
||||||
161 3 # ¡
|
161 3 # ¡
|
||||||
191 8 # ¿
|
191 8 # ¿
|
||||||
171 10 # «
|
171 7 # «
|
||||||
187 10 # »
|
187 7 # »
|
||||||
183 3 # ·
|
183 3 # ·
|
||||||
169 10 # ©
|
169 10 # ©
|
||||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -1,4 +1,4 @@
|
|||||||
# AEE 2026 — Català
|
# AEE ARCADE — Català
|
||||||
# lang: ca
|
# lang: ca
|
||||||
|
|
||||||
notification:
|
notification:
|
||||||
@@ -14,7 +14,7 @@ input:
|
|||||||
shader_off: "Shaders desactivats"
|
shader_off: "Shaders desactivats"
|
||||||
vsync_on: "VSync activat"
|
vsync_on: "VSync activat"
|
||||||
vsync_off: "VSync desactivat"
|
vsync_off: "VSync desactivat"
|
||||||
screenshot: "Captura"
|
screenshot: "Imatge {} guardada a {}"
|
||||||
shader: "Shader"
|
shader: "Shader"
|
||||||
preset: "Preset"
|
preset: "Preset"
|
||||||
scaling_disabled: "Escalat sense"
|
scaling_disabled: "Escalat sense"
|
||||||
@@ -23,7 +23,6 @@ input:
|
|||||||
scaling_overscan: "Escalat overscan"
|
scaling_overscan: "Escalat overscan"
|
||||||
scaling_integer: "Escalat enter"
|
scaling_integer: "Escalat enter"
|
||||||
press_again_exit: "Prem una altra volta per eixir"
|
press_again_exit: "Prem una altra volta per eixir"
|
||||||
paused: "Pausa"
|
|
||||||
editor_on: "Editor ON"
|
editor_on: "Editor ON"
|
||||||
editor_off: "Editor OFF"
|
editor_off: "Editor OFF"
|
||||||
editor_save_error: "Editor: error guardant el mapa"
|
editor_save_error: "Editor: error guardant el mapa"
|
||||||
@@ -38,6 +37,13 @@ hud:
|
|||||||
treasures: "Tres"
|
treasures: "Tres"
|
||||||
pyramid: "Piramide"
|
pyramid: "Piramide"
|
||||||
room: "Sala"
|
room: "Sala"
|
||||||
|
# Prompts mostrats al bloc d'un slot inactiu (en lloc dels zeros)
|
||||||
|
# quan un jugador encara no ha pulsat START o ha esgotat vides.
|
||||||
|
# Dues línies per fer-los caber al bloc lateral (~106 px).
|
||||||
|
join_prompt_can_join_l1: "Polsa start"
|
||||||
|
join_prompt_can_join_l2: "per jugar"
|
||||||
|
join_prompt_wait_l1: "Espera"
|
||||||
|
join_prompt_wait_l2: "per jugar"
|
||||||
|
|
||||||
# Textos de l'escena TITLE.
|
# Textos de l'escena TITLE.
|
||||||
title:
|
title:
|
||||||
@@ -53,6 +59,12 @@ game:
|
|||||||
room_intro_room: "Sala {r}"
|
room_intro_room: "Sala {r}"
|
||||||
room_complete_line1: "Fase"
|
room_complete_line1: "Fase"
|
||||||
room_complete_line2: "Completada"
|
room_complete_line2: "Completada"
|
||||||
|
# Rètol del game over (sub-FSM de la GameScene). El countdown es
|
||||||
|
# pinta com a segona línia (un enter) durant la fase 1 de
|
||||||
|
# CONTINUE_PROMPT — només cal traduir el rètol superior.
|
||||||
|
continue_prompt: "Continuar?"
|
||||||
|
game_over_line1: "Fi del"
|
||||||
|
game_over_line2: "Joc"
|
||||||
|
|
||||||
menu:
|
menu:
|
||||||
titles:
|
titles:
|
||||||
@@ -62,6 +74,7 @@ menu:
|
|||||||
controls: "Controls"
|
controls: "Controls"
|
||||||
keyboard: "Teclat"
|
keyboard: "Teclat"
|
||||||
gamepad: "Comandament"
|
gamepad: "Comandament"
|
||||||
|
tweaks: "Jugabilitat"
|
||||||
system: "Sistema"
|
system: "Sistema"
|
||||||
labels:
|
labels:
|
||||||
player_1: "Jugador 1"
|
player_1: "Jugador 1"
|
||||||
@@ -73,6 +86,7 @@ menu:
|
|||||||
controls_submenu: "Controls"
|
controls_submenu: "Controls"
|
||||||
keyboard_submenu: "Teclat"
|
keyboard_submenu: "Teclat"
|
||||||
gamepad_submenu: "Comandament"
|
gamepad_submenu: "Comandament"
|
||||||
|
tweaks_submenu: "Jugabilitat"
|
||||||
system_submenu: "Sistema"
|
system_submenu: "Sistema"
|
||||||
move_up: "Amunt"
|
move_up: "Amunt"
|
||||||
move_down: "Avall"
|
move_down: "Avall"
|
||||||
@@ -101,6 +115,9 @@ menu:
|
|||||||
music_volume: "Volum música"
|
music_volume: "Volum música"
|
||||||
sounds: "Sons"
|
sounds: "Sons"
|
||||||
sounds_volume: "Volum sons"
|
sounds_volume: "Volum sons"
|
||||||
|
whip_pierces_walls: "Látec travessa sarcs"
|
||||||
|
friendly_fire: "Foc amic"
|
||||||
|
players_collide: "Col·lisió entre jugadors"
|
||||||
restart: "Reiniciar"
|
restart: "Reiniciar"
|
||||||
exit_game: "Eixir del joc"
|
exit_game: "Eixir del joc"
|
||||||
values:
|
values:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# AEE 2026 — English
|
# AEE ARCADE — English
|
||||||
# lang: en
|
# lang: en
|
||||||
|
|
||||||
notification:
|
notification:
|
||||||
@@ -14,7 +14,7 @@ input:
|
|||||||
shader_off: "Shaders off"
|
shader_off: "Shaders off"
|
||||||
vsync_on: "VSync on"
|
vsync_on: "VSync on"
|
||||||
vsync_off: "VSync off"
|
vsync_off: "VSync off"
|
||||||
screenshot: "Screenshot"
|
screenshot: "Image {} saved at {}"
|
||||||
shader: "Shader"
|
shader: "Shader"
|
||||||
preset: "Preset"
|
preset: "Preset"
|
||||||
scaling_disabled: "Scaling off"
|
scaling_disabled: "Scaling off"
|
||||||
@@ -23,7 +23,6 @@ input:
|
|||||||
scaling_overscan: "Scaling overscan"
|
scaling_overscan: "Scaling overscan"
|
||||||
scaling_integer: "Scaling integer"
|
scaling_integer: "Scaling integer"
|
||||||
press_again_exit: "Press again to exit"
|
press_again_exit: "Press again to exit"
|
||||||
paused: "Paused"
|
|
||||||
editor_on: "Editor ON"
|
editor_on: "Editor ON"
|
||||||
editor_off: "Editor OFF"
|
editor_off: "Editor OFF"
|
||||||
editor_save_error: "Editor: map save failed"
|
editor_save_error: "Editor: map save failed"
|
||||||
@@ -38,6 +37,13 @@ hud:
|
|||||||
treasures: "Tre"
|
treasures: "Tre"
|
||||||
pyramid: "Pyramid"
|
pyramid: "Pyramid"
|
||||||
room: "Room"
|
room: "Room"
|
||||||
|
# Prompts shown in an inactive slot's block (instead of zeros) while
|
||||||
|
# a player hasn't pressed START yet or has run out of lives. Two
|
||||||
|
# short lines so they fit in the side block (~106 px wide).
|
||||||
|
join_prompt_can_join_l1: "Press start"
|
||||||
|
join_prompt_can_join_l2: "to play"
|
||||||
|
join_prompt_wait_l1: "Please"
|
||||||
|
join_prompt_wait_l2: "wait"
|
||||||
|
|
||||||
# Texts of the TITLE scene.
|
# Texts of the TITLE scene.
|
||||||
title:
|
title:
|
||||||
@@ -52,6 +58,12 @@ game:
|
|||||||
room_intro_room: "Room {r}"
|
room_intro_room: "Room {r}"
|
||||||
room_complete_line1: "Stage"
|
room_complete_line1: "Stage"
|
||||||
room_complete_line2: "Complete"
|
room_complete_line2: "Complete"
|
||||||
|
# Game over labels (sub-FSM in GameScene). The countdown is drawn as
|
||||||
|
# a numeric second line during CONTINUE_PROMPT phase 1, so only the
|
||||||
|
# top label needs translation.
|
||||||
|
continue_prompt: "Continue?"
|
||||||
|
game_over_line1: "Game"
|
||||||
|
game_over_line2: "Over"
|
||||||
|
|
||||||
menu:
|
menu:
|
||||||
titles:
|
titles:
|
||||||
@@ -61,6 +73,7 @@ menu:
|
|||||||
controls: "Controls"
|
controls: "Controls"
|
||||||
keyboard: "Keyboard"
|
keyboard: "Keyboard"
|
||||||
gamepad: "Gamepad"
|
gamepad: "Gamepad"
|
||||||
|
tweaks: "Gameplay"
|
||||||
system: "System"
|
system: "System"
|
||||||
labels:
|
labels:
|
||||||
player_1: "Player 1"
|
player_1: "Player 1"
|
||||||
@@ -72,6 +85,7 @@ menu:
|
|||||||
controls_submenu: "Controls"
|
controls_submenu: "Controls"
|
||||||
keyboard_submenu: "Keyboard"
|
keyboard_submenu: "Keyboard"
|
||||||
gamepad_submenu: "Gamepad"
|
gamepad_submenu: "Gamepad"
|
||||||
|
tweaks_submenu: "Gameplay"
|
||||||
system_submenu: "System"
|
system_submenu: "System"
|
||||||
move_up: "Up"
|
move_up: "Up"
|
||||||
move_down: "Down"
|
move_down: "Down"
|
||||||
@@ -100,6 +114,9 @@ menu:
|
|||||||
music_volume: "Music volume"
|
music_volume: "Music volume"
|
||||||
sounds: "Sounds"
|
sounds: "Sounds"
|
||||||
sounds_volume: "Sound volume"
|
sounds_volume: "Sound volume"
|
||||||
|
whip_pierces_walls: "Whip pierces sarcs"
|
||||||
|
friendly_fire: "Friendly fire"
|
||||||
|
players_collide: "Player collision"
|
||||||
restart: "Restart"
|
restart: "Restart"
|
||||||
exit_game: "Exit game"
|
exit_game: "Exit game"
|
||||||
values:
|
values:
|
||||||
|
|||||||
@@ -10,11 +10,38 @@
|
|||||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
pyramid: 1
|
pyramid: 1
|
||||||
room: 1
|
room: 2
|
||||||
tileset: data/tilesets/pyramid_1.gif
|
tileset: data/tilesets/pyramid_1.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
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
|
# Í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
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
@@ -82,6 +109,8 @@ data:
|
|||||||
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
# superior esquerre per verificar render d'una entitat decorativa.
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
entities:
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
- type: torch
|
- type: torch
|
||||||
position: [8, 2]
|
position: [8, 2]
|
||||||
- type: torch
|
- type: torch
|
||||||
@@ -128,8 +157,8 @@ entities:
|
|||||||
sarc_position: [2, 4]
|
sarc_position: [2, 4]
|
||||||
- type: sarcophagus
|
- type: sarcophagus
|
||||||
sarc_position: [3, 4]
|
sarc_position: [3, 4]
|
||||||
- type: torch
|
|
||||||
position: [4, 2]
|
|
||||||
- type: door
|
- type: door
|
||||||
- type: mummy
|
- type: mummy
|
||||||
position: [10, 11]
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -10,11 +10,38 @@
|
|||||||
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
pyramid: 1
|
pyramid: 1
|
||||||
room: 2
|
room: 1
|
||||||
tileset: data/tilesets/pyramid_1.gif
|
tileset: data/tilesets/pyramid_1.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
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
|
||||||
|
# valors declarats al seu YAML de tipus (comportament històric).
|
||||||
|
#
|
||||||
|
# level → atenuació base de la sala (0..1). 1.0 = res
|
||||||
|
# d'efecte, 0.0 = negre fora del radi dels emissors.
|
||||||
|
# El gradient dels emissors viu a
|
||||||
|
# data/config/lighting.yaml.
|
||||||
|
# emitters.<tipus> → override per tipus d'entitat. Tres formes:
|
||||||
|
# false → apaga el tipus
|
||||||
|
# true → defaults del tipus
|
||||||
|
# { radius: N, flicker: B } → camps presents
|
||||||
|
# sobreescriuen el
|
||||||
|
# default; els
|
||||||
|
# absents cauen al
|
||||||
|
# default del tipus.
|
||||||
|
# Plural per llegir-se natural ("totes les momies de la sala"). Per a
|
||||||
|
# casos particulars, un spawn pot dur el seu propi `light:` (mateixes
|
||||||
|
# formes) que té prioritat sobre l'override de sala. La forma antiga
|
||||||
|
# (`light_level:` plà a l'arrel) també segueix sent vàlida per
|
||||||
|
# retrocompatibilitat.
|
||||||
|
lighting:
|
||||||
|
level: 0.15
|
||||||
|
emitters:
|
||||||
|
players: true
|
||||||
|
mummies: false
|
||||||
|
|
||||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
# Í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
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
@@ -82,16 +109,12 @@ data:
|
|||||||
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
# superior esquerre per verificar render d'una entitat decorativa.
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
entities:
|
entities:
|
||||||
- type: torch
|
|
||||||
position: [4, 2]
|
|
||||||
- type: torch
|
- type: torch
|
||||||
position: [8, 2]
|
position: [8, 2]
|
||||||
- type: torch
|
- type: torch
|
||||||
position: [12, 2]
|
position: [12, 2]
|
||||||
- type: torch
|
- type: torch
|
||||||
position: [16, 2]
|
position: [16, 2]
|
||||||
- type: sarcophagus
|
|
||||||
sarc_position: [0, 0]
|
|
||||||
- type: sarcophagus
|
- type: sarcophagus
|
||||||
sarc_position: [1, 0]
|
sarc_position: [1, 0]
|
||||||
- type: sarcophagus
|
- type: sarcophagus
|
||||||
@@ -118,8 +141,6 @@ entities:
|
|||||||
sarc_position: [0, 3]
|
sarc_position: [0, 3]
|
||||||
- type: sarcophagus
|
- type: sarcophagus
|
||||||
sarc_position: [1, 3]
|
sarc_position: [1, 3]
|
||||||
- type: sarcophagus
|
|
||||||
sarc_position: [2, 3]
|
|
||||||
- type: sarcophagus
|
- type: sarcophagus
|
||||||
sarc_position: [3, 3]
|
sarc_position: [3, 3]
|
||||||
- type: sarcophagus
|
- type: sarcophagus
|
||||||
@@ -130,11 +151,33 @@ entities:
|
|||||||
sarc_position: [2, 4]
|
sarc_position: [2, 4]
|
||||||
- type: sarcophagus
|
- type: sarcophagus
|
||||||
sarc_position: [3, 4]
|
sarc_position: [3, 4]
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
- type: door
|
- type: door
|
||||||
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
- type: column
|
||||||
# transitable la dicta `geometry.door_col`; el render i la caixa
|
position: [3, 4]
|
||||||
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
frame: 2
|
||||||
|
- type: stone
|
||||||
|
position: [5, 4]
|
||||||
|
frame: 16
|
||||||
|
- type: stone
|
||||||
|
position: [4, 4]
|
||||||
|
frame: 19
|
||||||
|
- type: column
|
||||||
|
position: [13, 10]
|
||||||
|
frame: 4
|
||||||
|
- type: stone
|
||||||
|
position: [12, 10]
|
||||||
|
frame: 13
|
||||||
|
- type: stone
|
||||||
|
position: [11, 10]
|
||||||
|
frame: 23
|
||||||
|
- type: hole
|
||||||
|
position: [5, 9]
|
||||||
|
- type: hole
|
||||||
|
position: [9, 11]
|
||||||
|
- type: stone
|
||||||
|
position: [10, 10]
|
||||||
|
frame: 18
|
||||||
- type: mummy
|
- type: mummy
|
||||||
position: [10, 7]
|
position: [10, 13]
|
||||||
- type: mummy
|
|
||||||
position: [7, 9]
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
JASC-PAL
|
|
||||||
0100
|
|
||||||
32
|
|
||||||
0 0 0
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
217 217 217
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
# Configuració de la GameScene: timings i corbes de la FSM de sala
|
|
||||||
# (ROOM_INTRO i ROOM_OUTRO). Tots els valors són ms i tota la geometria
|
|
||||||
# i font del text es reutilitzen del tema UI (data/ui.yaml > pause +
|
|
||||||
# notification). Si manca el fitxer o alguna clau, GameScene cau als
|
|
||||||
# fallbacks compilats; veure `readSceneConfig` a game_scene.cpp.
|
|
||||||
|
|
||||||
room_intro:
|
|
||||||
fade_in_ms: 500 # negre opac → món visible
|
|
||||||
text_delay_ms: 500 # espera amb el món net abans de sortir el text
|
|
||||||
text_enter_ms: 400 # text esquerra → centre
|
|
||||||
text_hold_ms: 2000 # text quiet al centre
|
|
||||||
text_exit_ms: 400 # text centre → fora per l'esquerra
|
|
||||||
line_gap_px: -4 # px extra entre les dues línies (piràmide / sala)
|
|
||||||
# Delays d'spawn aeri **mesurats des de la fi de la música d'INTRO**
|
|
||||||
# (callback `Audio::setOnMusicEnded`). 0 = cau al moment exacte que
|
|
||||||
# `room_intro.ogg` acaba; valors > 0 escalonen els jugadors. En mode
|
|
||||||
# single-player s'usa només `player_spawn_delay_ms`.
|
|
||||||
player_spawn_delay_ms: 0
|
|
||||||
player2_spawn_delay_ms: 300
|
|
||||||
# Preset de reverb (catàleg a `data/sounds.yaml`) aplicat al primer
|
|
||||||
# LAND de cada jugador durant la cinemàtica d'entrada. La sala dona
|
|
||||||
# "més detall" sonor a l'INTRO; els salts del joc i els respawns
|
|
||||||
# post-mort cauen al so sec. Buit o absent = sense reverb.
|
|
||||||
land_reverb: stone_hall
|
|
||||||
|
|
||||||
room_outro:
|
|
||||||
text_enter_ms: 400
|
|
||||||
text_hold_ms: 2000
|
|
||||||
text_exit_ms: 400
|
|
||||||
fade_out_ms: 500 # món visible → negre opac
|
|
||||||
line_gap_px: -4 # px extra entre les dues línies (fase / completada)
|
|
||||||
black_hold_ms: 500 # pausa en negre abans d'entrar a la pròxima sala
|
|
||||||
|
|
||||||
text:
|
|
||||||
# Noms d'easing tal com els resol UiConfig::curveByName (vegeu
|
|
||||||
# `data/ui.yaml`). El text entra suau i surt accelerant.
|
|
||||||
enter_curve: outCubic
|
|
||||||
exit_curve: inCubic
|
|
||||||
|
|
||||||
# Panel translúcid pintat sota el rètol de transició (INTRO i OUTRO).
|
|
||||||
# Va centrat verticalment al centre del playfield i ocupa tota
|
|
||||||
# l'amplada del canvas. Creix d'altura amb la mateixa enter_curve del
|
|
||||||
# text i decreix amb l'exit_curve. `color` són tres canals RGB (0..255);
|
|
||||||
# `alpha` és la transparència del blend (0 = invisible, 255 = opac).
|
|
||||||
text_panel:
|
|
||||||
color: [36, 182, 170]
|
|
||||||
alpha: 128
|
|
||||||
height_px: 32
|
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
# Configuració de la GameScene: timings i corbes de la FSM de sala
|
||||||
|
# (ROOM_INTRO i ROOM_OUTRO). Tots els valors són ms i tota la geometria
|
||||||
|
# i font del text es reutilitzen del tema UI (data/config/ui.yaml >
|
||||||
|
# notification). Si manca el fitxer o alguna clau, GameScene cau als
|
||||||
|
# fallbacks compilats; veure `readSceneConfig` a game_scene.cpp.
|
||||||
|
|
||||||
|
room_intro:
|
||||||
|
fade_in_ms: 500 # negre opac → món visible
|
||||||
|
text_delay_ms: 500 # espera amb el món net abans de sortir el text
|
||||||
|
text_enter_ms: 400 # text esquerra → centre
|
||||||
|
text_hold_ms: 2000 # text quiet al centre
|
||||||
|
text_exit_ms: 400 # text centre → fora per l'esquerra
|
||||||
|
line_gap_px: -4 # px extra entre les dues línies (piràmide / sala)
|
||||||
|
# Delays d'spawn aeri **mesurats des de la fi de la música d'INTRO**
|
||||||
|
# (callback `Audio::setOnMusicEnded`). 0 = cau al moment exacte que
|
||||||
|
# `room_intro.ogg` acaba; valors > 0 escalonen els jugadors. En mode
|
||||||
|
# single-player s'usa només `player_spawn_delay_ms`.
|
||||||
|
player_spawn_delay_ms: 0
|
||||||
|
player2_spawn_delay_ms: 300
|
||||||
|
# Preset de reverb (catàleg a `data/config/sounds.yaml`) aplicat al primer
|
||||||
|
# LAND de cada jugador durant la cinemàtica d'entrada. La sala dona
|
||||||
|
# "més detall" sonor a l'INTRO; els salts del joc i els respawns
|
||||||
|
# post-mort cauen al so sec. Buit o absent = sense reverb.
|
||||||
|
land_reverb: stone_hall
|
||||||
|
|
||||||
|
room_outro:
|
||||||
|
text_enter_ms: 400
|
||||||
|
text_hold_ms: 2000
|
||||||
|
text_exit_ms: 400
|
||||||
|
fade_out_ms: 500 # món visible → negre opac
|
||||||
|
line_gap_px: -4 # px extra entre les dues línies (fase / completada)
|
||||||
|
black_hold_ms: 500 # pausa en negre abans d'entrar a la pròxima sala
|
||||||
|
|
||||||
|
text:
|
||||||
|
# Noms d'easing tal com els resol UiConfig::curveByName (vegeu
|
||||||
|
# `data/config/ui.yaml`). El text entra suau i surt accelerant.
|
||||||
|
enter_curve: outCubic
|
||||||
|
exit_curve: inCubic
|
||||||
|
|
||||||
|
# Panel translúcid pintat sota el rètol de transició (INTRO i OUTRO).
|
||||||
|
# Va centrat verticalment al centre del playfield i ocupa tota
|
||||||
|
# l'amplada del canvas. Creix d'altura amb la mateixa enter_curve del
|
||||||
|
# text i decreix amb l'exit_curve. `color` són tres canals RGB (0..255);
|
||||||
|
# `alpha` és la transparència del blend (0 = invisible, 255 = opac).
|
||||||
|
text_panel:
|
||||||
|
# Violeta cerimonial: contrasta amb l'or del text (room_text), no
|
||||||
|
# compite amb el blau del bloc P2 del HUD, i deixa la sorra de les
|
||||||
|
# sales respirar al darrere.
|
||||||
|
color: [72, 36, 85]
|
||||||
|
alpha: 160
|
||||||
|
height_px: 32
|
||||||
|
|
||||||
|
# Sub-FSM de game over: dispara CONTINUE? quan tots els jugadors
|
||||||
|
# queden fora de partida durant PLAYING, fa countdown, i si ningú
|
||||||
|
# pulsa START acaba en GAME OVER + fade-to-black + transició a LOGO.
|
||||||
|
# Tots els valors són ms (excepte `continue_count_steps`, que és el
|
||||||
|
# nombre de tics visibles del countdown). Si manca el fitxer o alguna
|
||||||
|
# clau, GameScene cau als fallbacks compilats (vegeu `GameOverFsm::Config`).
|
||||||
|
game_over:
|
||||||
|
# CONTINUE? — animació d'entrada i sortida idèntica al room text.
|
||||||
|
continue_text_enter_ms: 400
|
||||||
|
# Hold del countdown: 10 s = (continue_count_start + 1) graons d'1 s.
|
||||||
|
# La sèrie visible és [continue_count_start, ..., 1, 0]; el "0" es
|
||||||
|
# manté l'últim segon abans del salt a GAME OVER.
|
||||||
|
continue_text_hold_ms: 10000
|
||||||
|
continue_text_exit_ms: 400
|
||||||
|
continue_count_start: 9
|
||||||
|
# GAME OVER — apareix sense enter anim (panel ja amunt del CONTINUE?),
|
||||||
|
# es manté un temps i marxa amb la mateixa exit anim del room text.
|
||||||
|
game_over_text_hold_ms: 1500
|
||||||
|
game_over_text_exit_ms: 400
|
||||||
|
game_over_wait_ms: 400
|
||||||
|
# Fade final cap a LOGO. `black_hold_ms` és la pausa en negre opac
|
||||||
|
# abans que el GameScene demani la transició d'escena.
|
||||||
|
fade_out_ms: 500
|
||||||
|
black_hold_ms: 500
|
||||||
|
# Velocitat (frequency ratio) de la música mentre dura el countdown
|
||||||
|
# de CONTINUE_PROMPT. 1.0 = sense canvi, 1.5 = un 50% més ràpid (i
|
||||||
|
# amb el to més agut: efecte arcade clàssic). Es restableix a 1.0 al
|
||||||
|
# sortir del prompt — tant si el jugador reviu com si entra GAME OVER.
|
||||||
|
continue_music_speed: 1.05
|
||||||
|
# Pista de música que sona durant l'estat GAME_OVER (sense loop).
|
||||||
|
# La fase de hold del text es sincronitza amb la durada real de la
|
||||||
|
# pista, així que el rètol "Fi del Joc" es manté visible mentre dura
|
||||||
|
# la música; després ve l'exit anim, el wait i el fade a LOGO.
|
||||||
|
game_over_music_track: "game_over.ogg"
|
||||||
|
# So que sona cada vegada que apareix un nou número del countdown
|
||||||
|
# (al primer cop que es mostra `continue_count_start`, a cada canvi,
|
||||||
|
# i quan apareix el 0 — però NO al saltar a GAME OVER). Buit = silenci.
|
||||||
|
countdown_sound_key: "countdown.wav"
|
||||||
|
# Llindar a partir del qual qualsevol botó de salt o atac de
|
||||||
|
# qualsevol jugador avança al següent número del countdown.
|
||||||
|
# 7 → skip habilitat mentre el número visible és 7..1 (no a 0).
|
||||||
|
# -1 = mai. Útil per accelerar la decisió un cop la urgència ja
|
||||||
|
# és evident al jugador.
|
||||||
|
skip_below_or_equal: 7
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Configuració de la LogoScene (logotip de l'editora). El revelat del
|
||||||
|
# logo a la fase SCAN imita un escaneig CRT: una banda lluminosa baixa
|
||||||
|
# de dalt a baix; per damunt es veu la imatge ja "impressa", per sota
|
||||||
|
# encara és negre. Al voltant de la banda hi ha glitch (offset
|
||||||
|
# horitzontal de files amb falloff per distància) i ocasionalment
|
||||||
|
# apareixen bursts VHS (franges desplaçades) damunt la part ja
|
||||||
|
# revelada. Quan la banda toca el peu del logo, un flash blanc tanca
|
||||||
|
# l'efecte i s'entra a MIDDLE (logo estable).
|
||||||
|
logo:
|
||||||
|
scan:
|
||||||
|
duration_ms: 1500 # durada total del scan top→bottom
|
||||||
|
band_height_px: 4 # alçada (px) de la banda lluminosa
|
||||||
|
band_alpha: 200 # opacitat (0..255) de la capa blanca de la banda
|
||||||
|
band_pulse_hz: 20.0 # freqüència del pulse de brillantor (Hz)
|
||||||
|
band_pulse_depth: 0.45 # profunditat del pulse (0..1; 0 = sense pulse)
|
||||||
|
# Glitch d'impacte (files al voltant de la banda)
|
||||||
|
glitch_amplitude_px: 6 # offset horitzontal màxim a la banda
|
||||||
|
glitch_band_radius_px: 12 # files damunt/sota la banda amb glitch (falloff lineal)
|
||||||
|
# Bursts VHS sobre la part ja revelada
|
||||||
|
vhs_burst_per_sec: 3 # bursts/segon mitjà
|
||||||
|
vhs_burst_duration_ms: 80 # durada de cada burst
|
||||||
|
vhs_burst_height_px: 8 # alçada de la franja afectada
|
||||||
|
vhs_burst_amplitude_px: 8 # offset horitzontal del burst
|
||||||
|
flash_ms: 100 # flash blanc final quan acaba el scan
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,77 @@
|
|||||||
|
# Configuració de la TitleScene.
|
||||||
|
# `bg_color` i `text_color` són índexs sobre la paleta "ui" (uzebox.pal):
|
||||||
|
# 1 = (36,0,0) gairebé negre amb un mínim de càlid; 31 = (255,109,0)
|
||||||
|
# taronja pur. `duration_ms` és el timeout abans de saltar a la
|
||||||
|
# següent escena (skipable amb tecla no reservada). Els dos
|
||||||
|
# `blink_*_ms` defineixen el cicle del prompt "press start": el text
|
||||||
|
# es veu durant `visible_ms` i s'amaga durant `hidden_ms` repetint-se.
|
||||||
|
title:
|
||||||
|
duration_ms: 30000
|
||||||
|
bg_color: 1
|
||||||
|
text_color: 31
|
||||||
|
# Color del text del © de copyright (peu de la TITLE). Off-white càlid:
|
||||||
|
# índex 183 = (255,218,170), tipus crema/peach, ~90% blanc amb tint càlid.
|
||||||
|
copyright_color: 183
|
||||||
|
# Color de l'outline del prompt "PRESS START". Marró fosc càlid (índex
|
||||||
|
# 19 = 109,72,0), coherent amb la paleta del logo. Es manté separat de
|
||||||
|
# `copyright_color` perquè l'outline i el text del © tenen rols visuals
|
||||||
|
# diferents (contrast vs. peu informatiu).
|
||||||
|
press_start_outline_color: 19
|
||||||
|
# Posicions Y dels tres elements verticals de la TITLE, en percentatge
|
||||||
|
# sobre l'alçada del canvas (240 px). El logo s'ancora pel seu top
|
||||||
|
# (la imatge cau cap avall des d'aquest Y); el prompt i el copyright
|
||||||
|
# es centren verticalment sobre la línia.
|
||||||
|
logo_y_percent: 15
|
||||||
|
prompt_y_percent: 68
|
||||||
|
copyright_y_percent: 92
|
||||||
|
# Separació vertical en píxels entre el minilogo i el text del © al peu.
|
||||||
|
mini_logo_gap_px: 2
|
||||||
|
blink_visible_ms: 800
|
||||||
|
blink_hidden_ms: 200
|
||||||
|
# Fase STARTING (entre la primera pulsació de START i el salt a la
|
||||||
|
# següent escena). `starting_ms` és la durada fixa; durant aquesta
|
||||||
|
# fase el parpadeig accelera per `starting_blink_speedup` (2.0 = doble
|
||||||
|
# de freqüència). Si l'altre jugador prem START dins d'aquesta
|
||||||
|
# finestra, el comptador es reseteja i s'arrenca amb BOTH.
|
||||||
|
starting_ms: 3000
|
||||||
|
# Durant STARTING el prompt commuta a un cicle simètric 50/50
|
||||||
|
# (visible/ocult) amb aquest període total. 200 ms = 5 Hz, prou
|
||||||
|
# ràpid per llegir-se com a "pulsació confirmada".
|
||||||
|
starting_blink_period_ms: 200
|
||||||
|
# Fade-out estil "mosaic dissolve" durant l'última part de STARTING.
|
||||||
|
# `fade_out_ms` ha de ser <= starting_ms (els últims X ms del STARTING
|
||||||
|
# es fan amb el dissolve actiu fins a quedar negre). `fade_out_max_block`
|
||||||
|
# és la mida màxima de bloc en píxels al final del fade.
|
||||||
|
fade_out_ms: 800
|
||||||
|
fade_out_max_block: 32
|
||||||
|
# Fade-out de `title.ogg` en entrar a STARTING. Independent del visual:
|
||||||
|
# la música pot acabar abans o després del mosaic dissolve.
|
||||||
|
music_fade_out_ms: 3000
|
||||||
|
# Fade-to-black per a la sortida d'attract (timeout cap a LOGO). NO és
|
||||||
|
# mosaic — capa negra lineal damunt del canvas. La música no es toca:
|
||||||
|
# `title.ogg` continua sonant cap a LOGO, que ja veurà què en fa.
|
||||||
|
attract_fade_to_black_ms: 800
|
||||||
|
# Seqüència d'aparició dels elements de la TITLE en fase IDLE. Tots
|
||||||
|
# els temps són absoluts des de l'entrada a l'escena. `fade_in_ms` és
|
||||||
|
# la capa negra inicial que s'esvaeix cap al fons; els altres marquen
|
||||||
|
# quan apareix cada element. Si l'usuari prem START en qualsevol
|
||||||
|
# moment, la seqüència es completa instantàniament.
|
||||||
|
intro:
|
||||||
|
fade_in_ms: 500 # fade-in negre inicial sobre el fons
|
||||||
|
game_logo_at_ms: 1500 # visibilitat binària del game_logo
|
||||||
|
mini_logo_at_ms: 2000 # inici del slide-in del mini-logo
|
||||||
|
mini_logo_slide_ms: 500 # durada del slide
|
||||||
|
copyright_at_ms: 2200 # inici del slide-in del copyright
|
||||||
|
copyright_slide_ms: 500 # durada del slide
|
||||||
|
slide_curve: outCubic # corba compartida per mini_logo i copyright
|
||||||
|
press_start_at_ms: 3000 # quan apareix PRESS START amb parpadeig
|
||||||
|
# Distorsió sinusoidal del fons (efecte de calor del desert).
|
||||||
|
# `amplitude_px`: desplaçament màxim de cada fila en píxels.
|
||||||
|
# `wavelength_px`: distància vertical entre dos pics de l'ona.
|
||||||
|
# `speed_hz`: velocitat amb què avança la fase (cicles per segon).
|
||||||
|
# Si `enabled` és false, el fons es pinta sense distorsió.
|
||||||
|
heat_haze:
|
||||||
|
enabled: true
|
||||||
|
amplitude_px: 1.5
|
||||||
|
wavelength_px: 32.0
|
||||||
|
speed_hz: 0.6
|
||||||
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 14 KiB |
@@ -1,155 +0,0 @@
|
|||||||
# Tema de la UI: índexs de paleta, timings, fonts i flags visuals. Tota
|
|
||||||
# clau absent cau al valor compilat a Defaults::UI (veure defaults.hpp).
|
|
||||||
|
|
||||||
colors:
|
|
||||||
render_info_text: 58
|
|
||||||
render_info_shadow: 16
|
|
||||||
canvas_bg: 72
|
|
||||||
notification_text: 232
|
|
||||||
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_label: 58
|
|
||||||
hud_value: 187
|
|
||||||
hud_flash: 237
|
|
||||||
# Rètol de transició de sala (ROOM_INTRO i ROOM_OUTRO). Independents
|
|
||||||
# de notification_* perquè la fase i les notificacions són dos canals
|
|
||||||
# visuals distints: ací pots canviar el color del rètol sense tocar
|
|
||||||
# els missatges del HUD.
|
|
||||||
room_text: 160
|
|
||||||
room_text_outline: 253
|
|
||||||
|
|
||||||
timings:
|
|
||||||
notification_ms: 4000
|
|
||||||
exit_ms: 4000
|
|
||||||
|
|
||||||
fonts:
|
|
||||||
hud:
|
|
||||||
name: "8bithud"
|
|
||||||
gif: "data/fonts/8bithud.gif"
|
|
||||||
fnt: "data/fonts/8bithud.fnt"
|
|
||||||
kerning: 1
|
|
||||||
placeholder:
|
|
||||||
name: "ari-w9500-condensed-display"
|
|
||||||
gif: "data/fonts/ari-w9500-condensed-display.gif"
|
|
||||||
fnt: "data/fonts/ari-w9500-condensed-display.fnt"
|
|
||||||
kerning: 1
|
|
||||||
# Font del rètol de transició de sala (ROOM_INTRO i ROOM_OUTRO).
|
|
||||||
room_text:
|
|
||||||
name: "CaesarDressing-Regular"
|
|
||||||
gif: "data/fonts/CaesarDressing-Regular.gif"
|
|
||||||
fnt: "data/fonts/CaesarDressing-Regular.fnt"
|
|
||||||
kerning: 1
|
|
||||||
menu_title:
|
|
||||||
name: "ari-w9500-display"
|
|
||||||
gif: "data/fonts/ari-w9500-display.gif"
|
|
||||||
fnt: "data/fonts/ari-w9500-display.fnt"
|
|
||||||
kerning: 0
|
|
||||||
menu_items:
|
|
||||||
name: "ari-w9500-condensed-display"
|
|
||||||
gif: "data/fonts/ari-w9500-condensed-display.gif"
|
|
||||||
fnt: "data/fonts/ari-w9500-condensed-display.fnt"
|
|
||||||
kerning: 0
|
|
||||||
|
|
||||||
hud:
|
|
||||||
bg_enabled: false
|
|
||||||
entry_ms: 800 # durada de l'animació d'entrada dels tres blocs
|
|
||||||
flash_ms: 250 # durada del flash quan un jugador rep un hit
|
|
||||||
shake_ms: 300 # durada del shake que acompanya el flash
|
|
||||||
|
|
||||||
# Geometria del popup superior (Notifier). En píxels del canvas, llevat de
|
|
||||||
# slide_ms (durada en ms de l'animació d'entrada i d'eixida; constant
|
|
||||||
# independentment de la mida de la caixa). El padding vertical és asimètric a
|
|
||||||
# propòsit: padding_top > padding_bottom puja una mica el text dins la caixa.
|
|
||||||
# Corbes disponibles: linear, outQuad, inQuad, inOutQuad, outCubic, inCubic.
|
|
||||||
notifier:
|
|
||||||
padding_h: 6
|
|
||||||
padding_top: 4
|
|
||||||
padding_bottom: 3
|
|
||||||
line_gap: 1
|
|
||||||
margin_top: 3
|
|
||||||
border_px: 1
|
|
||||||
slide_ms: 300
|
|
||||||
slide_in_curve: outCubic
|
|
||||||
slide_out_curve: inCubic
|
|
||||||
|
|
||||||
# Pausa graduada de la GameScene: el món no para d'un colp, s'atura amb
|
|
||||||
# easing i torna a arrancar amb una altra corba. Els timings es poden
|
|
||||||
# afinar independentment, i les corbes permeten estils diferents entrant
|
|
||||||
# i sortint (p.ex. frenar suau i arrancar seca). Corbes disponibles:
|
|
||||||
# linear, outQuad, inQuad, inOutQuad, outCubic, inCubic.
|
|
||||||
pause:
|
|
||||||
enter_ms: 400
|
|
||||||
exit_ms: 250
|
|
||||||
enter_curve: outCubic
|
|
||||||
exit_curve: inCubic
|
|
||||||
|
|
||||||
# Sons del menú de servei. Els fitxers viuen a data/sounds/. SELECT
|
|
||||||
# acompanya navegació (amunt/avall) i back; ACCEPT acompanya modificació
|
|
||||||
# d'opció (esquerra/dreta/retorn) o entrada a un submenú.
|
|
||||||
sounds:
|
|
||||||
menu_select: "menu_select.wav"
|
|
||||||
menu_accept: "menu_accept.wav"
|
|
||||||
# Disparat quan un jugador prem START a la TITLE (primera pulsació o
|
|
||||||
# la del segon jugador dins de la finestra de STARTING).
|
|
||||||
player_start: "player_start.wav"
|
|
||||||
|
|
||||||
# Escena TITLE (placeholder visual mentre no hi haja artwork).
|
|
||||||
# `bg_color` i `text_color` són índexs sobre la paleta "ui" (uzebox.pal):
|
|
||||||
# 1 = (36,0,0) gairebé negre amb un mínim de càlid; 31 = (255,109,0)
|
|
||||||
# taronja pur. `duration_ms` és el timeout abans de saltar a la
|
|
||||||
# següent escena (skipable amb tecla no reservada). Els dos
|
|
||||||
# `blink_*_ms` defineixen el cicle del prompt "press start": el text
|
|
||||||
# es veu durant `visible_ms` i s'amaga durant `hidden_ms` repetint-se.
|
|
||||||
title:
|
|
||||||
duration_ms: 30000
|
|
||||||
bg_color: 1
|
|
||||||
text_color: 31
|
|
||||||
copyright_color: 246
|
|
||||||
blink_visible_ms: 800
|
|
||||||
blink_hidden_ms: 200
|
|
||||||
# Fase STARTING (entre la primera pulsació de START i el salt a la
|
|
||||||
# següent escena). `starting_ms` és la durada fixa; durant aquesta
|
|
||||||
# fase el parpadeig accelera per `starting_blink_speedup` (2.0 = doble
|
|
||||||
# de freqüència). Si l'altre jugador prem START dins d'aquesta
|
|
||||||
# finestra, el comptador es reseteja i s'arrenca amb BOTH.
|
|
||||||
starting_ms: 3000
|
|
||||||
# Durant STARTING el prompt commuta a un cicle simètric 50/50
|
|
||||||
# (visible/ocult) amb aquest període total. 200 ms = 5 Hz, prou
|
|
||||||
# ràpid per llegir-se com a "pulsació confirmada".
|
|
||||||
starting_blink_period_ms: 200
|
|
||||||
# Fade-out estil "mosaic dissolve" durant l'última part de STARTING.
|
|
||||||
# `fade_out_ms` ha de ser <= starting_ms (els últims X ms del STARTING
|
|
||||||
# es fan amb el dissolve actiu fins a quedar negre). `fade_out_max_block`
|
|
||||||
# és la mida màxima de bloc en píxels al final del fade.
|
|
||||||
fade_out_ms: 800
|
|
||||||
fade_out_max_block: 32
|
|
||||||
# Fade-out de `title.ogg` en entrar a STARTING. Independent del visual:
|
|
||||||
# la música pot acabar abans o després del mosaic dissolve.
|
|
||||||
music_fade_out_ms: 3000
|
|
||||||
# Fade-to-black per a la sortida d'attract (timeout cap a LOGO). NO és
|
|
||||||
# mosaic — capa negra lineal damunt del canvas. La música no es toca:
|
|
||||||
# `title.ogg` continua sonant cap a LOGO, que ja veurà què en fa.
|
|
||||||
attract_fade_to_black_ms: 800
|
|
||||||
# Fade-in inicial: capa negra al començament que s'esvaeix cap al fons.
|
|
||||||
# Mateix valor que `room_intro.fade_in_ms` (data/scenes/game.yaml) perquè
|
|
||||||
# l'arribada a la TITLE i a una sala de joc tinguin el mateix ritme.
|
|
||||||
intro_fade_in_ms: 500
|
|
||||||
# Distorsió sinusoidal del fons (efecte de calor del desert).
|
|
||||||
# `amplitude_px`: desplaçament màxim de cada fila en píxels.
|
|
||||||
# `wavelength_px`: distància vertical entre dos pics de l'ona.
|
|
||||||
# `speed_hz`: velocitat amb què avança la fase (cicles per segon).
|
|
||||||
# Si `enabled` és false, el fons es pinta sense distorsió.
|
|
||||||
heat_haze:
|
|
||||||
enabled: true
|
|
||||||
amplitude_px: 1.5
|
|
||||||
wavelength_px: 32.0
|
|
||||||
speed_hz: 0.6
|
|
||||||
@@ -22,8 +22,8 @@ real (logo animat, menú de títol, cutscene).
|
|||||||
Fitxer personal de desenvolupament, **fora del repo**. Viu a la mateixa carpeta
|
Fitxer personal de desenvolupament, **fora del repo**. Viu a la mateixa carpeta
|
||||||
de sistema que `config.yaml`:
|
de sistema que `config.yaml`:
|
||||||
|
|
||||||
- Linux (XDG): `$XDG_CONFIG_HOME/jailgames/aee_2026/debug.yaml`
|
- Linux (XDG): `$XDG_CONFIG_HOME/jailgames/aee_arcade/debug.yaml`
|
||||||
- Per defecte: `~/.config/jailgames/aee_2026/debug.yaml`
|
- Per defecte: `~/.config/jailgames/aee_arcade/debug.yaml`
|
||||||
|
|
||||||
### Comportament segons build
|
### Comportament segons build
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Sistema d'entitats
|
# Sistema d'entitats
|
||||||
|
|
||||||
Registre del disseny, els treballs fets i els pendents per al sistema d'entitats (sarcòfags, porta, momia, antorxa, trampes, roca) d'aee_2026. Material de referència per a decisions futures — no és documentació d'API.
|
Registre del disseny, els treballs fets i els pendents per al sistema d'entitats (sarcòfags, porta, momia, antorxa, trampes, roca) d'aee_arcade. Material de referència per a decisions futures — no és documentació d'API.
|
||||||
|
|
||||||
Estats possibles: `DONE`, `WIP`, `DEFERRED`, `WONTFIX`.
|
Estats possibles: `DONE`, `WIP`, `DEFERRED`, `WONTFIX`.
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ Estats possibles: `DONE`, `WIP`, `DEFERRED`, `WONTFIX`.
|
|||||||
|
|
||||||
## 1. Context i objectiu inicial
|
## 1. Context i objectiu inicial
|
||||||
|
|
||||||
aee_2026 és un clon tipus Oh Mummy! amb estètica Amidar. Els sarcòfags eren fins ara rectangles purament geomètrics calculats a [Map::getSarcRect](../source/game/map/map.cpp#L67); no hi havia entitats de joc (momies, antorxes, trampes, portes).
|
aee_arcade és un clon tipus Oh Mummy! amb estètica Amidar. Els sarcòfags eren fins ara rectangles purament geomètrics calculats a [Map::getSarcRect](../source/game/map/map.cpp#L67); no hi havia entitats de joc (momies, antorxes, trampes, portes).
|
||||||
|
|
||||||
**Objectiu inicial** (prompt de partida):
|
**Objectiu inicial** (prompt de partida):
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ Al CMakeLists: `ENTITY_SOURCES` conté els fitxers core; `APP_SOURCES` afegeix `
|
|||||||
- `make build`: OK, 0 warnings nous.
|
- `make build`: OK, 0 warnings nous.
|
||||||
- `./build/test_entities_smoke`: **9/9 OK**.
|
- `./build/test_entities_smoke`: **9/9 OK**.
|
||||||
- `ctest --output-on-failure`: 1/1 suite passed.
|
- `ctest --output-on-failure`: 1/1 suite passed.
|
||||||
- `timeout 5 xvfb-run ./build/aee_2026`: 5s sense crash, stdout inclou:
|
- `timeout 5 xvfb-run ./build/aee_arcade`: 5s sense crash, stdout inclou:
|
||||||
```
|
```
|
||||||
Surface : torch.gif ... LOADED
|
Surface : torch.gif ... LOADED
|
||||||
Surface : sarcophagus.gif ... LOADED (×2)
|
Surface : sarcophagus.gif ... LOADED (×2)
|
||||||
@@ -323,7 +323,7 @@ Dos events encadenats perquè el *pintat* no és la *presència*: es filtra per
|
|||||||
|
|
||||||
- `make debug` net, sense warnings ni de clang-tidy ni de cppcheck als fitxers nous.
|
- `make debug` net, sense warnings ni de clang-tidy ni de cppcheck als fitxers nous.
|
||||||
- `./build/test_entities_smoke`: **ALL OK** (14/14). T9 adaptat (publica `tile_painted` en lloc d'`on_tile_entered`); la resta de tests del sistema d'entitats passen sense tocar.
|
- `./build/test_entities_smoke`: **ALL OK** (14/14). T9 adaptat (publica `tile_painted` en lloc d'`on_tile_entered`); la resta de tests del sistema d'entitats passen sense tocar.
|
||||||
- `timeout 5 xvfb-run ./build/aee_2026`: arrenca, log `PaintLayer: 21x15 tiles (336x240 px), tileset data/tilesets/tomb.gif` després de `EntityManager: 27 entitats carregades`, surt amb `Bye!` sense crash.
|
- `timeout 5 xvfb-run ./build/aee_arcade`: arrenca, log `PaintLayer: 21x15 tiles (336x240 px), tileset data/tilesets/tomb.gif` després de `EntityManager: 27 entitats carregades`, surt amb `Bye!` sense crash.
|
||||||
|
|
||||||
### Pendent derivat
|
### Pendent derivat
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Pla d'implementació — Menú de servei
|
# Pla d'implementació — Menú de servei
|
||||||
|
|
||||||
Pla per a portar el menú de servei de `../aee` (`source/core/rendering/menu.cpp`)
|
Pla per a portar el menú de servei de `../aee` (`source/core/rendering/menu.cpp`)
|
||||||
a `aee_2026`, adaptat a l'arquitectura actual (Scenes + Director instanciat +
|
a `aee_arcade`, adaptat a l'arquitectura actual (Scenes + Director instanciat +
|
||||||
`Options` amb setters persistents + `Screen` ARGB + `Text` bitmap). Document de
|
`Options` amb setters persistents + `Screen` ARGB + `Text` bitmap). Document de
|
||||||
referència per a implementar-ho en una sessió futura; no s'ha escrit cap codi
|
referència per a implementar-ho en una sessió futura; no s'ha escrit cap codi
|
||||||
encara.
|
encara.
|
||||||
@@ -33,7 +33,7 @@ Tot el que segueix és la base; la còpia mental serveix per a no reinventar res
|
|||||||
|
|
||||||
## 2. Diferències d'arquitectura rellevants
|
## 2. Diferències d'arquitectura rellevants
|
||||||
|
|
||||||
| Punt | `../aee` | `aee_2026` |
|
| Punt | `../aee` | `aee_arcade` |
|
||||||
| ----------------- | ---------------------------------------------------- | -------------------------------------------------------------------------- |
|
| ----------------- | ---------------------------------------------------- | -------------------------------------------------------------------------- |
|
||||||
| Overlay | `Overlay::render()` dibuixa Menú + Notifier al buffer | No hi ha Overlay. Cada Scene crida `Notifier::render()` i `RenderInfo::render()` |
|
| Overlay | `Overlay::render()` dibuixa Menú + Notifier al buffer | No hi ha Overlay. Cada Scene crida `Notifier::render()` i `RenderInfo::render()` |
|
||||||
| Director | Singleton `Director::get()` amb `requestQuit/requestRestart` | Classe instanciada; sortida via `SDL_EVENT_QUIT` a `EventBuffer` |
|
| Director | Singleton `Director::get()` amb `requestQuit/requestRestart` | Classe instanciada; sortida via `SDL_EVENT_QUIT` a `EventBuffer` |
|
||||||
@@ -223,7 +223,7 @@ es necessita.
|
|||||||
### Limitació: pàgina Controls diferida
|
### Limitació: pàgina Controls diferida
|
||||||
|
|
||||||
El menú de `../aee` té una pàgina Controls (`buildControls`) que permet
|
El menú de `../aee` té una pàgina Controls (`buildControls`) que permet
|
||||||
remappar `up/down/left/right` i la tecla de menú. A `aee_2026`:
|
remappar `up/down/left/right` i la tecla de menú. A `aee_arcade`:
|
||||||
|
|
||||||
- Les tecles de joc són `Defaults::KeysGame::{UP,DOWN,LEFT,RIGHT}` — constants
|
- Les tecles de joc són `Defaults::KeysGame::{UP,DOWN,LEFT,RIGHT}` — constants
|
||||||
`constexpr`, no mutables.
|
`constexpr`, no mutables.
|
||||||
@@ -273,7 +273,7 @@ y, w, h, argb, alpha)` que opere sobre `canvas_.data()`. El canvas és
|
|||||||
fons del menú pot ser `alpha=220` i es veu el joc apagat per darrere.
|
fons del menú pot ser `alpha=220` i es veu el joc apagat per darrere.
|
||||||
|
|
||||||
Mida fixa recomanada per al MVP: `BOX_W = 220`, altura dinàmica (igual que
|
Mida fixa recomanada per al MVP: `BOX_W = 220`, altura dinàmica (igual que
|
||||||
`../aee`). Canvas lògic d'`aee_2026` és `GameCanvas::WIDTH`/`HEIGHT` —
|
`../aee`). Canvas lògic d'`aee_arcade` és `GameCanvas::WIDTH`/`HEIGHT` —
|
||||||
confirmar mida real (mateixos 320×200?) a `utils/defines.hpp` abans de
|
confirmar mida real (mateixos 320×200?) a `utils/defines.hpp` abans de
|
||||||
hardcoded.
|
hardcoded.
|
||||||
|
|
||||||
|
|||||||
@@ -7,17 +7,17 @@
|
|||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>Aventures en Egipte ARCADE</string>
|
<string>Aventures en Egipte ARCADE</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>aee_2026</string>
|
<string>aee_arcade</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>icon</string>
|
<string>icon</string>
|
||||||
<key>CFBundleIconName</key>
|
<key>CFBundleIconName</key>
|
||||||
<string>icon</string>
|
<string>icon</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>org.jailgames.aee_2026</string>
|
<string>org.jailgames.aee_arcade</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>aee_2026</string>
|
<string>aee_arcade</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// aee_2026.rc
|
// aee_arcade.rc
|
||||||
IDI_ICON1 ICON "release/icons/icon.ico"
|
IDI_ICON1 ICON "release/icons/icon.ico"
|
||||||
@@ -113,6 +113,13 @@ void Audio::stopMusic() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(readability-make-member-function-const)
|
||||||
|
void Audio::setMusicSpeed(float ratio) {
|
||||||
|
if (music_enabled_) {
|
||||||
|
engine_->setMusicSpeed(ratio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reprodueix un so per nom
|
// Reprodueix un so per nom
|
||||||
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_; unique_ptr::operator-> no propaga const
|
// 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) {
|
void Audio::playSound(const std::string& name, Group group) {
|
||||||
@@ -129,6 +136,22 @@ void Audio::playSound(Ja::Sound* sound, Group group) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Variant amb velocitat (i to) escalats. Apliquem el ratio al canal
|
||||||
|
// just retornat per `playSound`: així el `SDL_AudioStream` recent creat
|
||||||
|
// 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);
|
||||||
|
if (sound == nullptr) { return; }
|
||||||
|
const int CH = engine_->playSound(sound, 0, static_cast<int>(group));
|
||||||
|
if (CH >= 0 && speed != 1.0F) {
|
||||||
|
engine_->setChannelSpeed(CH, speed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reprodueix un so processat per un eco definit a sounds.yaml. Si el preset no
|
// 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
|
// 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.
|
// sec — l'usuari sent el so encara que la cua no s'apliqui.
|
||||||
|
|||||||
@@ -76,11 +76,22 @@ class Audio {
|
|||||||
void stopMusic(); // Aturar completament la música
|
void stopMusic(); // Aturar completament la música
|
||||||
void fadeOutMusic(int milliseconds); // Fosa de sortida de la música (muta globals de Ja)
|
void fadeOutMusic(int milliseconds); // Fosa de sortida de la música (muta globals de Ja)
|
||||||
void setOnMusicEnded(std::function<void()> callback); // Callback disparat quan la pista actual acaba de drenar (CONV-03)
|
void setOnMusicEnded(std::function<void()> callback); // Callback disparat quan la pista actual acaba de drenar (CONV-03)
|
||||||
|
// Multiplicador de velocitat de la música actual. 1.0 = normal,
|
||||||
|
// 1.5 = un 50% més ràpid (efecte "chipmunk" — també puja el to).
|
||||||
|
// Es reseteja a 1.0 implícitament a cada `playMusic`. No-op si no
|
||||||
|
// hi ha música activa.
|
||||||
|
void setMusicSpeed(float ratio);
|
||||||
|
|
||||||
// --- Control de sons ---
|
// --- Control de sons ---
|
||||||
void playSound(const std::string& name, Group group = Group::GAME); // Reproduir so puntual per nom (muta globals de Ja)
|
void playSound(const std::string& name, Group group = Group::GAME); // Reproduir so puntual per nom (muta globals de Ja)
|
||||||
void playSound(Ja::Sound* sound, Group group = Group::GAME); // Reproduir so puntual per punter (muta globals de Ja)
|
void playSound(Ja::Sound* sound, Group group = Group::GAME); // Reproduir so puntual per punter (muta globals de Ja)
|
||||||
// Reprodueix un so processat per un efecte definit a data/sounds.yaml
|
// Reprodueix un so amb la velocitat (i to) escalats per `speed`:
|
||||||
|
// 1.0 = normal, 0.95 ≈ -5% (més greu i lent), 1.05 ≈ +5% (més
|
||||||
|
// agut i ràpid). Mateixa semàntica que `setMusicSpeed`. Útil per a
|
||||||
|
// variacions subtils que eviten la fatiga d'escoltar el mateix
|
||||||
|
// sample idèntic (p.ex. obertures de sarcòfag, picks d'ítems).
|
||||||
|
void playSound(const std::string& name, Group group, float speed);
|
||||||
|
// Reprodueix un so processat per un efecte definit a data/config/sounds.yaml
|
||||||
// (preset_name busca a SoundEffectsConfig). Si el preset no existeix
|
// (preset_name busca a SoundEffectsConfig). Si el preset no existeix
|
||||||
// o el motor està al cap de canals amb efecte, fa fallback a playSound
|
// o el motor està al cap de canals amb efecte, fa fallback a playSound
|
||||||
// sec — l'usuari sent el so igualment, sense la cua.
|
// sec — l'usuari sent el so igualment, sense la cua.
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iostream>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "core/audio/jail_audio.hpp"
|
#include "core/audio/jail_audio.hpp"
|
||||||
|
#include "utils/log.hpp"
|
||||||
|
|
||||||
namespace AudioEffects {
|
namespace AudioEffects {
|
||||||
|
|
||||||
@@ -46,11 +46,11 @@ namespace AudioEffects {
|
|||||||
} else if (spec.format == SDL_AUDIO_U8) {
|
} else if (spec.format == SDL_AUDIO_U8) {
|
||||||
bytes_per_sample = 1;
|
bytes_per_sample = 1;
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "AudioEffects: format de so no suportat (només U8 o S16)\n";
|
Log::error("AudioEffects", "format de so no suportat (només U8 o S16)");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (spec.channels < 1 || spec.channels > 2) {
|
if (spec.channels < 1 || spec.channels > 2) {
|
||||||
std::cerr << "AudioEffects: el so ha de ser mono o estèreo\n";
|
Log::error("AudioEffects", "el so ha de ser mono o estèreo");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -286,6 +286,11 @@ namespace Ja {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Engine::setMusicSpeed(float ratio) {
|
||||||
|
if (current_music_ == nullptr || current_music_->stream == nullptr) { return; }
|
||||||
|
SDL_SetAudioStreamFrequencyRatio(current_music_->stream, ratio);
|
||||||
|
}
|
||||||
|
|
||||||
void Engine::pauseMusic() {
|
void Engine::pauseMusic() {
|
||||||
if (current_music_ == nullptr || current_music_->state != MusicState::PLAYING) { return; }
|
if (current_music_ == nullptr || current_music_->state != MusicState::PLAYING) { return; }
|
||||||
|
|
||||||
@@ -425,6 +430,12 @@ namespace Ja {
|
|||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Engine::setChannelSpeed(const int channel, const float ratio) {
|
||||||
|
if (channel < 0 || channel >= MAX_SIMULTANEOUS_CHANNELS) { return; }
|
||||||
|
if (channels_[channel].stream == nullptr) { return; }
|
||||||
|
SDL_SetAudioStreamFrequencyRatio(channels_[channel].stream, ratio);
|
||||||
|
}
|
||||||
|
|
||||||
void Engine::pauseChannel(const int channel) {
|
void Engine::pauseChannel(const int channel) {
|
||||||
forEachTargetChannel(channel, [](Channel& ch) {
|
forEachTargetChannel(channel, [](Channel& ch) {
|
||||||
if (ch.state == ChannelState::PLAYING) {
|
if (ch.state == ChannelState::PLAYING) {
|
||||||
|
|||||||
@@ -171,6 +171,14 @@ namespace Ja {
|
|||||||
void crossfadeMusic(Music* music, int crossfade_ms, int loop = -1);
|
void crossfadeMusic(Music* music, int crossfade_ms, int loop = -1);
|
||||||
[[nodiscard]] auto getMusicState() const -> MusicState;
|
[[nodiscard]] auto getMusicState() const -> MusicState;
|
||||||
auto setMusicVolume(float volume) -> float;
|
auto setMusicVolume(float volume) -> float;
|
||||||
|
// Multiplicador de velocitat de reproducció de la música actual
|
||||||
|
// via `SDL_SetAudioStreamFrequencyRatio`. 1.0 = normal, 2.0 =
|
||||||
|
// doble velocitat. Cal saber que també puja el to (efecte
|
||||||
|
// "chipmunk") — és el comportament arcade clàssic dels comptes
|
||||||
|
// enrere. Cada `playMusic` crea un stream nou amb ratio 1.0,
|
||||||
|
// així que un canvi de track reseteja la velocitat
|
||||||
|
// implícitament. No-op si no hi ha música activa.
|
||||||
|
void setMusicSpeed(float ratio);
|
||||||
// Registra un callback que es disparà quan la música actual acabi de
|
// Registra un callback que es disparà quan la música actual acabi de
|
||||||
// drenar naturalment (times == 0 + stream buit). Es crida DESPRÉS de
|
// drenar naturalment (times == 0 + stream buit). Es crida DESPRÉS de
|
||||||
// stopMusic, així que el callback pot invocar playMusic sense córrer.
|
// stopMusic, així que el callback pot invocar playMusic sense córrer.
|
||||||
@@ -184,6 +192,14 @@ namespace Ja {
|
|||||||
// --- So ---
|
// --- So ---
|
||||||
auto playSound(Sound* sound, int loop = 0, int group = 0) -> int;
|
auto playSound(Sound* sound, int loop = 0, int group = 0) -> int;
|
||||||
auto playSoundOnChannel(Sound* sound, int channel, int loop = 0, int group = 0) -> int;
|
auto playSoundOnChannel(Sound* sound, int channel, int loop = 0, int group = 0) -> int;
|
||||||
|
// Ajusta la velocitat de reproducció d'un canal actiu via
|
||||||
|
// `SDL_SetAudioStreamFrequencyRatio`. 1.0 = normal. Igual que a
|
||||||
|
// `setMusicSpeed`, puja/baixa el to junt amb la velocitat
|
||||||
|
// (efecte "chipmunk"); per a SFX curts arcade és el que volem.
|
||||||
|
// No-op si el canal no està actiu. Cridar-lo just després de
|
||||||
|
// `playSound`/`playSoundOnChannel` perquè el ratio cobreixi
|
||||||
|
// tota la reproducció.
|
||||||
|
void setChannelSpeed(int channel, float ratio);
|
||||||
// Reproducció amb so processat per un efecte. Retorna el canal
|
// Reproducció amb so processat per un efecte. Retorna el canal
|
||||||
// assignat o -1 si no queden slots d'efecte (MAX_EFFECT_CHANNELS).
|
// assignat o -1 si no queden slots d'efecte (MAX_EFFECT_CHANNELS).
|
||||||
// El sound original només s'usa per consultar el spec/buffer; el
|
// El sound original només s'usa per consultar el spec/buffer; el
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#include "core/audio/sound_effects_config.hpp"
|
#include "core/audio/sound_effects_config.hpp"
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "core/resources/resource_helper.hpp"
|
#include "core/resources/resource_helper.hpp"
|
||||||
#include "external/fkyaml_node.hpp"
|
#include "external/fkyaml_node.hpp"
|
||||||
|
#include "utils/log.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ auto SoundEffectsConfig::get() -> SoundEffectsConfig& {
|
|||||||
void SoundEffectsConfig::load(const std::string& file_path) {
|
void SoundEffectsConfig::load(const std::string& file_path) {
|
||||||
auto bytes = Resource::Helper::loadFile(file_path);
|
auto bytes = Resource::Helper::loadFile(file_path);
|
||||||
if (bytes.empty()) {
|
if (bytes.empty()) {
|
||||||
std::cerr << "SoundEffectsConfig: no s'ha pogut obrir " << file_path << " — cap preset d'efecte disponible\n";
|
Log::warn("SoundEffectsConfig", "no s'ha pogut obrir {} — cap preset d'efecte disponible", file_path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,9 +60,9 @@ void SoundEffectsConfig::load(const std::string& file_path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "SoundEffectsConfig: " << echoes_.size() << " preset(s) d'eco i " << reverbs_.size() << " de reverb des de " << file_path << '\n';
|
Log::info("SoundEffectsConfig", "{} preset(s) d'eco i {} de reverb des de {}", echoes_.size(), reverbs_.size(), file_path);
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
std::cerr << "SoundEffectsConfig: error parsejant " << file_path << ": " << e.what() << '\n';
|
Log::error("SoundEffectsConfig", "error parsejant {}: {}", file_path, e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#include "core/audio/jail_audio.hpp" // Per a Ja::EchoParams / Ja::ReverbParams
|
#include "core/audio/jail_audio.hpp" // Per a Ja::EchoParams / Ja::ReverbParams
|
||||||
|
|
||||||
// Catàleg de presets d'efectes carregat des de data/sounds.yaml. La capa
|
// Catàleg de presets d'efectes carregat des de data/config/sounds.yaml. La capa
|
||||||
// Audio (playSoundWithEcho/playSoundWithReverb) hi accedeix per nom: si el
|
// Audio (playSoundWithEcho/playSoundWithReverb) hi accedeix per nom: si el
|
||||||
// preset no existeix, el so es reprodueix sec amb un avís a stderr.
|
// preset no existeix, el so es reprodueix sec amb un avís a stderr.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "external/fkyaml_node.hpp"
|
#include "external/fkyaml_node.hpp"
|
||||||
|
#include "utils/log.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ auto GamepadBindings::get() -> GamepadBindings& {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto GamepadBindings::defaultButton(Action action) -> int {
|
auto GamepadBindings::defaultButton(Action action) -> int {
|
||||||
// Defaults històrics de l'aee_2026 (vegeu Player::accumulateGamepadMovement
|
// Defaults històrics de l'aee_arcade (vegeu Player::accumulateGamepadMovement
|
||||||
// i les consultes del Director per al menú).
|
// i les consultes del Director per al menú).
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case Action::JUMP:
|
case Action::JUMP:
|
||||||
@@ -167,8 +167,7 @@ void GamepadBindings::load(const std::string& file_path) {
|
|||||||
try {
|
try {
|
||||||
auto yaml = fkyaml::node::deserialize(CONTENT);
|
auto yaml = fkyaml::node::deserialize(CONTENT);
|
||||||
if (!yaml.contains("gamepads") || !yaml["gamepads"].is_sequence()) {
|
if (!yaml.contains("gamepads") || !yaml["gamepads"].is_sequence()) {
|
||||||
std::cerr << "GamepadBindings: secció 'gamepads' absent o invàlida a "
|
Log::error("GamepadBindings", "secció 'gamepads' absent o invàlida a {}", file_path);
|
||||||
<< file_path << '\n';
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (const auto& entry : yaml["gamepads"]) {
|
for (const auto& entry : yaml["gamepads"]) {
|
||||||
@@ -188,11 +187,9 @@ void GamepadBindings::load(const std::string& file_path) {
|
|||||||
}
|
}
|
||||||
configs_.push_back(std::move(cfg));
|
configs_.push_back(std::move(cfg));
|
||||||
}
|
}
|
||||||
std::cout << "GamepadBindings: " << configs_.size() << " mandos carregats de "
|
Log::info("GamepadBindings", "{} mandos carregats de {}", configs_.size(), file_path);
|
||||||
<< file_path << '\n';
|
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
std::cerr << "GamepadBindings: error parsejant " << file_path << ": "
|
Log::error("GamepadBindings", "error parsejant {}: {}", file_path, e.what());
|
||||||
<< e.what() << '\n';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +197,7 @@ auto GamepadBindings::save() -> bool {
|
|||||||
if (file_path_.empty()) { return false; }
|
if (file_path_.empty()) { return false; }
|
||||||
|
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << "# AEE 2026 - Vinculacions de mando per dispositiu.\n";
|
out << "# AEE ARCADE - Vinculacions de mando per dispositiu.\n";
|
||||||
out << "# Auto-generat des del menú de servei. Editable a mà.\n";
|
out << "# Auto-generat des del menú de servei. Editable a mà.\n";
|
||||||
out << "gamepads:\n";
|
out << "gamepads:\n";
|
||||||
for (const auto& cfg : configs_) {
|
for (const auto& cfg : configs_) {
|
||||||
@@ -215,7 +212,7 @@ auto GamepadBindings::save() -> bool {
|
|||||||
|
|
||||||
std::ofstream file(file_path_, std::ios::binary);
|
std::ofstream file(file_path_, std::ios::binary);
|
||||||
if (!file) {
|
if (!file) {
|
||||||
std::cerr << "GamepadBindings: no es pot escriure " << file_path_ << '\n';
|
Log::error("GamepadBindings", "no es pot escriure {}", file_path_);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
file << out.str();
|
file << out.str();
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
#include "core/input/gamepad_manager.hpp"
|
#include "core/input/gamepad_manager.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iostream>
|
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "core/locale/locale.hpp"
|
#include "core/locale/locale.hpp"
|
||||||
#include "core/rendering/notification_presets.hpp"
|
#include "core/rendering/notification_presets.hpp"
|
||||||
|
#include "utils/log.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@@ -94,8 +94,7 @@ void GamepadManager::addGamepad(SDL_JoystickID jid, bool notify) {
|
|||||||
if (!SDL_IsGamepad(jid)) { return; }
|
if (!SDL_IsGamepad(jid)) { return; }
|
||||||
SDL_Gamepad* pad = SDL_OpenGamepad(jid);
|
SDL_Gamepad* pad = SDL_OpenGamepad(jid);
|
||||||
if (pad == nullptr) {
|
if (pad == nullptr) {
|
||||||
std::cout << "GamepadManager: SDL_OpenGamepad(" << jid
|
Log::error("GamepadManager", "SDL_OpenGamepad({}) ha fallat: {}", jid, SDL_GetError());
|
||||||
<< ") ha fallat: " << SDL_GetError() << '\n';
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::string name = uniqueName(prettyName(SDL_GetGamepadName(pad)));
|
std::string name = uniqueName(prettyName(SDL_GetGamepadName(pad)));
|
||||||
@@ -104,8 +103,7 @@ void GamepadManager::addGamepad(SDL_JoystickID jid, bool notify) {
|
|||||||
// reconnexió es resoldrà per nom.
|
// reconnexió es resoldrà per nom.
|
||||||
const char* raw_path = SDL_GetGamepadPath(pad);
|
const char* raw_path = SDL_GetGamepadPath(pad);
|
||||||
std::string path = (raw_path != nullptr) ? raw_path : "";
|
std::string path = (raw_path != nullptr) ? raw_path : "";
|
||||||
std::cout << "GamepadManager: connectat " << name
|
Log::info("GamepadManager", "connectat {}{}", name, path.empty() ? std::string{} : " [" + path + "]");
|
||||||
<< (path.empty() ? "" : " [" + path + "]") << '\n';
|
|
||||||
entries_.push_back(Entry{.pad = pad, .instance_id = jid, .pretty_name = name, .path = path});
|
entries_.push_back(Entry{.pad = pad, .instance_id = jid, .pretty_name = name, .path = path});
|
||||||
|
|
||||||
// Hot-plug: si alguna assignació pendent encaixa amb aquest mando,
|
// Hot-plug: si alguna assignació pendent encaixa amb aquest mando,
|
||||||
@@ -129,7 +127,7 @@ void GamepadManager::removeGamepad(SDL_JoystickID jid) {
|
|||||||
std::string name = std::move(it->pretty_name);
|
std::string name = std::move(it->pretty_name);
|
||||||
SDL_Gamepad* pad = it->pad;
|
SDL_Gamepad* pad = it->pad;
|
||||||
if (pad != nullptr) { SDL_CloseGamepad(pad); }
|
if (pad != nullptr) { SDL_CloseGamepad(pad); }
|
||||||
std::cout << "GamepadManager: desconnectat " << name << '\n';
|
Log::info("GamepadManager", "desconnectat {}", name);
|
||||||
entries_.erase(it);
|
entries_.erase(it);
|
||||||
|
|
||||||
// Qualsevol assignació que apuntava a aquest mando queda amb `pad`
|
// Qualsevol assignació que apuntava a aquest mando queda amb `pad`
|
||||||
@@ -145,8 +143,7 @@ void GamepadManager::removeGamepad(SDL_JoystickID jid) {
|
|||||||
void GamepadManager::init() {
|
void GamepadManager::init() {
|
||||||
if (initialized_) { return; }
|
if (initialized_) { return; }
|
||||||
if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD)) {
|
if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD)) {
|
||||||
std::cout << "GamepadManager: SDL_INIT_GAMEPAD ha fallat: "
|
Log::error("GamepadManager", "SDL_INIT_GAMEPAD ha fallat: {}", SDL_GetError());
|
||||||
<< SDL_GetError() << '\n';
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// `gamecontrollerdb.txt` és opcional. Viu a l'arrel del projecte
|
// `gamecontrollerdb.txt` és opcional. Viu a l'arrel del projecte
|
||||||
@@ -161,10 +158,9 @@ void GamepadManager::init() {
|
|||||||
}
|
}
|
||||||
const int ADDED = SDL_AddGamepadMappingsFromFile(db_path.c_str());
|
const int ADDED = SDL_AddGamepadMappingsFromFile(db_path.c_str());
|
||||||
if (ADDED < 0) {
|
if (ADDED < 0) {
|
||||||
std::cout << "GamepadManager: no s'ha pogut carregar " << db_path
|
Log::warn("GamepadManager", "no s'ha pogut carregar {}: {}", db_path, SDL_GetError());
|
||||||
<< ": " << SDL_GetError() << '\n';
|
|
||||||
} else {
|
} else {
|
||||||
std::cout << "GamepadManager: " << ADDED << " mappings carregats de " << db_path << '\n';
|
Log::info("GamepadManager", "{} mappings carregats de {}", ADDED, db_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
@@ -175,7 +171,7 @@ void GamepadManager::init() {
|
|||||||
}
|
}
|
||||||
SDL_free(ids);
|
SDL_free(ids);
|
||||||
}
|
}
|
||||||
std::cout << "GamepadManager: " << entries_.size() << " mando(s) detectat(s) a l'inici\n";
|
Log::info("GamepadManager", "{} mando(s) detectat(s) a l'inici", entries_.size());
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,8 +264,7 @@ void GamepadManager::assignToPlayer(int player_id, const std::string& name, cons
|
|||||||
if (a.pad != nullptr) {
|
if (a.pad != nullptr) {
|
||||||
for (auto& other : assignments_) {
|
for (auto& other : assignments_) {
|
||||||
if (&other != &a && other.pad == a.pad) {
|
if (&other != &a && other.pad == a.pad) {
|
||||||
std::cout << "GamepadManager: mando " << a.name
|
Log::info("GamepadManager", "mando {} reassignat (last-wins)", a.name);
|
||||||
<< " reassignat (last-wins)\n";
|
|
||||||
other.pad = nullptr;
|
other.pad = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,11 +272,9 @@ void GamepadManager::assignToPlayer(int player_id, const std::string& name, cons
|
|||||||
// Traç de l'assignació persistida (vinculada o pendent) perquè quede
|
// Traç de l'assignació persistida (vinculada o pendent) perquè quede
|
||||||
// visible a la consola igual que les autoassignacions.
|
// visible a la consola igual que les autoassignacions.
|
||||||
if (a.pad != nullptr) {
|
if (a.pad != nullptr) {
|
||||||
std::cout << "GamepadManager: assignat " << a.name
|
Log::info("GamepadManager", "assignat {} → P{}", a.name, player_id + 1);
|
||||||
<< " → P" << (player_id + 1) << '\n';
|
|
||||||
} else {
|
} else {
|
||||||
std::cout << "GamepadManager: P" << (player_id + 1) << " pendent ("
|
Log::info("GamepadManager", "P{} pendent ({} no connectat)", player_id + 1, a.name.empty() ? a.path : a.name);
|
||||||
<< (a.name.empty() ? a.path : a.name) << " no connectat)\n";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,8 +300,7 @@ auto GamepadManager::autoAssignFreeSlots() -> std::vector<AutoAssigned> {
|
|||||||
slot.name = e.pretty_name;
|
slot.name = e.pretty_name;
|
||||||
slot.path = e.path;
|
slot.path = e.path;
|
||||||
assigned.push_back(AutoAssigned{.player_id = pid, .name = e.pretty_name, .path = e.path});
|
assigned.push_back(AutoAssigned{.player_id = pid, .name = e.pretty_name, .path = e.path});
|
||||||
std::cout << "GamepadManager: auto-assigned " << e.pretty_name
|
Log::info("GamepadManager", "auto-assigned {} → P{}", e.pretty_name, pid + 1);
|
||||||
<< " → P" << (pid + 1) << '\n';
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <format>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "core/input/key_config.hpp"
|
#include "core/input/key_config.hpp"
|
||||||
@@ -26,7 +27,7 @@ namespace GlobalInputs {
|
|||||||
// La lliguem a la durada de la notificació EXIT perquè el missatge i
|
// La lliguem a la durada de la notificació EXIT perquè el missatge i
|
||||||
// la confirmació expiren alhora — si no, l'usuari podria veure el
|
// la confirmació expiren alhora — si no, l'usuari podria veure el
|
||||||
// text però ja no ser dins del rang de confirmació (o al revés).
|
// text però ja no ser dins del rang de confirmació (o al revés).
|
||||||
// El valor concret viu a data/ui.yaml (timings.exit_ms) via UiConfig.
|
// El valor concret viu a data/config/ui.yaml (timings.exit_ms) via UiConfig.
|
||||||
constexpr int MIN_ZOOM = 1;
|
constexpr int MIN_ZOOM = 1;
|
||||||
constexpr int MAX_ZOOM = 6;
|
constexpr int MAX_ZOOM = 6;
|
||||||
|
|
||||||
@@ -69,15 +70,26 @@ namespace GlobalInputs {
|
|||||||
notifyLocale(NEW_VSYNC ? "input.vsync_on" : "input.vsync_off");
|
notifyLocale(NEW_VSYNC ? "input.vsync_on" : "input.vsync_off");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Volca el canvas ARGB actual a PNG dins de `screenshots/`. Si ha
|
// Volca el canvas ARGB actual a PNG dins del directori de
|
||||||
// fallat l'escriptura, el handler no mostra notificació (retorn buit).
|
// captures (configurat pel Director). La notificació informa
|
||||||
|
// del nom i de la ruta absoluta — així l'usuari sap on anar
|
||||||
|
// a buscar-la encara que el binari s'executi des d'un sitio
|
||||||
|
// estrany. Si l'escriptura falla, no es mostra notificació.
|
||||||
void onScreenshot() {
|
void onScreenshot() {
|
||||||
const auto* buffer = Screen::get().getCanvasArgb();
|
const auto* buffer = Screen::get().getCanvasArgb();
|
||||||
const int W = Screen::get().getCanvasWidth();
|
const int W = Screen::get().getCanvasWidth();
|
||||||
const int H = Screen::get().getCanvasHeight();
|
const int H = Screen::get().getCanvasHeight();
|
||||||
const std::string FILENAME = Screenshot::save(buffer, W, H);
|
const auto R = Screenshot::save(buffer, W, H);
|
||||||
if (!FILENAME.empty()) {
|
if (!R.filename.empty()) {
|
||||||
Notification::notify(Locale::get().text("input.screenshot") + ' ' + FILENAME);
|
// `make_format_args` retorna un store que conté referències
|
||||||
|
// als arguments; cal lligar-lo a una lvalue local perquè
|
||||||
|
// sobrevisqui fins que `vformat` el consumeixi. Passar-lo
|
||||||
|
// directament a `vformat` és UB en alguns compiladors
|
||||||
|
// (DR de C++20 / fix de C++23) i ha provocat un segfault
|
||||||
|
// post-captura en aquest projecte.
|
||||||
|
const std::string TEMPLATE = Locale::get().text("input.screenshot");
|
||||||
|
auto args = std::make_format_args(R.filename, R.folder);
|
||||||
|
Notification::notify(std::vformat(TEMPLATE, args));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +173,7 @@ namespace GlobalInputs {
|
|||||||
// Debug helper: dispara una notificació prou llarga perquè
|
// Debug helper: dispara una notificació prou llarga perquè
|
||||||
// `wrapToWidth` la parteixi en (almenys) dues línies. Útil per a
|
// `wrapToWidth` la parteixi en (almenys) dues línies. Útil per a
|
||||||
// verificar visualment el padding asimètric, el line_gap i la caixa
|
// verificar visualment el padding asimètric, el line_gap i la caixa
|
||||||
// multilinia després de tocar la geometria a `data/ui.yaml`.
|
// multilinia després de tocar la geometria a `data/config/ui.yaml`.
|
||||||
void onDebugLongNotification() {
|
void onDebugLongNotification() {
|
||||||
Notification::notify("Notificació de prova llarga per a comprovar que el text es parteix correctament en dues línies dins la caixa.");
|
Notification::notify("Notificació de prova llarga per a comprovar que el text es parteix correctament en dues línies dins la caixa.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
#include "core/input/key_config.hpp"
|
#include "core/input/key_config.hpp"
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "core/resources/resource_helper.hpp"
|
#include "core/resources/resource_helper.hpp"
|
||||||
#include "external/fkyaml_node.hpp"
|
#include "external/fkyaml_node.hpp"
|
||||||
|
#include "utils/log.hpp"
|
||||||
|
|
||||||
auto KeyConfig::get() -> KeyConfig& {
|
auto KeyConfig::get() -> KeyConfig& {
|
||||||
static KeyConfig instance_;
|
static KeyConfig instance_;
|
||||||
@@ -15,7 +15,7 @@ auto KeyConfig::get() -> KeyConfig& {
|
|||||||
void KeyConfig::load(const std::string& file_path) {
|
void KeyConfig::load(const std::string& file_path) {
|
||||||
auto bytes = Resource::Helper::loadFile(file_path);
|
auto bytes = Resource::Helper::loadFile(file_path);
|
||||||
if (bytes.empty()) {
|
if (bytes.empty()) {
|
||||||
std::cerr << "KeyConfig: no s'ha pogut obrir " << file_path << '\n';
|
Log::error("KeyConfig", "no s'ha pogut obrir {}", file_path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ void KeyConfig::load(const std::string& file_path) {
|
|||||||
const auto* end = begin + bytes.size();
|
const auto* end = begin + bytes.size();
|
||||||
auto yaml = fkyaml::node::deserialize(begin, end);
|
auto yaml = fkyaml::node::deserialize(begin, end);
|
||||||
if (!yaml.contains("keys") || !yaml["keys"].is_sequence()) {
|
if (!yaml.contains("keys") || !yaml["keys"].is_sequence()) {
|
||||||
std::cerr << "KeyConfig: falta la secció 'keys' a " << file_path << '\n';
|
Log::error("KeyConfig", "falta la secció 'keys' a {}", file_path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bindings_.clear();
|
bindings_.clear();
|
||||||
@@ -34,14 +34,14 @@ void KeyConfig::load(const std::string& file_path) {
|
|||||||
const auto CODE = entry["code"].get_value<std::string>();
|
const auto CODE = entry["code"].get_value<std::string>();
|
||||||
const SDL_Scancode SC = SDL_GetScancodeFromName(CODE.c_str());
|
const SDL_Scancode SC = SDL_GetScancodeFromName(CODE.c_str());
|
||||||
if (SC == SDL_SCANCODE_UNKNOWN) {
|
if (SC == SDL_SCANCODE_UNKNOWN) {
|
||||||
std::cerr << "KeyConfig: codi SDL desconegut '" << CODE << "' per id '" << ID << "'\n";
|
Log::warn("KeyConfig", "codi SDL desconegut '{}' per id '{}'", CODE, ID);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
bindings_[ID] = SC;
|
bindings_[ID] = SC;
|
||||||
}
|
}
|
||||||
std::cout << "KeyConfig: " << bindings_.size() << " bindings des de " << file_path << '\n';
|
Log::info("KeyConfig", "{} bindings des de {}", bindings_.size(), file_path);
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
std::cerr << "KeyConfig: error parsejant " << file_path << ": " << e.what() << '\n';
|
Log::error("KeyConfig", "error parsejant {}: {}", file_path, e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
// Mapeig id → SDL_Scancode carregat des de data/input/keys.yaml. Consumit
|
// Mapeig id → SDL_Scancode carregat des de data/config/input.yaml. Consumit
|
||||||
// per GlobalInputs i (en el futur) per menús de remapat. No té cap cost
|
// per GlobalInputs i (en el futur) per menús de remapat. No té cap cost
|
||||||
// global: si la clau no existeix, retorna SDL_SCANCODE_UNKNOWN i GlobalInputs
|
// global: si la clau no existeix, retorna SDL_SCANCODE_UNKNOWN i GlobalInputs
|
||||||
// ignora l'acció. D'aquesta manera podem afegir nous bindings al YAML sense
|
// ignora l'acció. D'aquesta manera podem afegir nous bindings al YAML sense
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
#include "core/locale/locale.hpp"
|
#include "core/locale/locale.hpp"
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "core/resources/resource_helper.hpp"
|
#include "core/resources/resource_helper.hpp"
|
||||||
#include "external/fkyaml_node.hpp"
|
#include "external/fkyaml_node.hpp"
|
||||||
|
#include "utils/log.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ auto Locale::get() -> Locale& {
|
|||||||
void Locale::load(const std::string& file_path) {
|
void Locale::load(const std::string& file_path) {
|
||||||
auto bytes = Resource::Helper::loadFile(file_path);
|
auto bytes = Resource::Helper::loadFile(file_path);
|
||||||
if (bytes.empty()) {
|
if (bytes.empty()) {
|
||||||
std::cerr << "Locale: no s'ha pogut obrir " << file_path << '\n';
|
Log::error("Locale", "no s'ha pogut obrir {}", file_path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,15 +46,15 @@ void Locale::load(const std::string& file_path) {
|
|||||||
auto yaml = fkyaml::node::deserialize(begin, end);
|
auto yaml = fkyaml::node::deserialize(begin, end);
|
||||||
strings_.clear();
|
strings_.clear();
|
||||||
flatten(yaml, "", strings_);
|
flatten(yaml, "", strings_);
|
||||||
std::cout << "Locale: " << strings_.size() << " traduccions des de " << file_path << '\n';
|
Log::info("Locale", "{} traduccions des de {}", strings_.size(), file_path);
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
std::cerr << "Locale: error parsejant " << file_path << ": " << e.what() << '\n';
|
Log::error("Locale", "error parsejant {}: {}", file_path, e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Locale::text(const std::string& key) const -> std::string {
|
auto Locale::text(const std::string& key) const -> std::string {
|
||||||
auto it = strings_.find(key);
|
auto it = strings_.find(key);
|
||||||
if (it != strings_.end()) { return it->second; }
|
if (it != strings_.end()) { return it->second; }
|
||||||
std::cerr << "Locale: clau no trobada: " << key << '\n';
|
Log::warn("Locale", "clau no trobada: {}", key);
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
#include "core/rendering/effects/scan_reveal.hpp"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <numbers>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
#include "core/rendering/screen.hpp"
|
||||||
|
#include "core/rendering/surface.hpp"
|
||||||
|
|
||||||
|
namespace Effects {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Hash determinista per a (row, time_bucket): retorna float a
|
||||||
|
// [-1, 1]. Usat per generar el jitter horitzontal de la banda
|
||||||
|
// sense necessitat d'un RNG d'estat (cada frame el resultat
|
||||||
|
// canvia perquè el time_bucket canvia, però dins d'un mateix
|
||||||
|
// bucket dues files diferents donen valors distints).
|
||||||
|
auto rowJitter(int row, int time_bucket) -> float {
|
||||||
|
const auto SEED = static_cast<std::uint32_t>((row * 73856093) ^ (time_bucket * 19349663));
|
||||||
|
// Mix barato (xorshift) per descorrelacionar bits.
|
||||||
|
std::uint32_t x = SEED;
|
||||||
|
x ^= x << 13;
|
||||||
|
x ^= x >> 17;
|
||||||
|
x ^= x << 5;
|
||||||
|
// Normalitzem a [-1, 1].
|
||||||
|
const float NORM = static_cast<float>(x) / static_cast<float>(UINT32_MAX);
|
||||||
|
return (NORM * 2.0F) - 1.0F;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bucket de temps per al jitter: agrupant en blocs de ~50ms el
|
||||||
|
// jitter "salta" 20 cops per segon — visualment "viu" sense
|
||||||
|
// ser caos pur a 60fps.
|
||||||
|
constexpr int JITTER_TIME_BUCKET_MS = 50;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
ScanReveal::ScanReveal()
|
||||||
|
: rng_(std::random_device{}()) {}
|
||||||
|
|
||||||
|
void ScanReveal::begin(const Params& params) {
|
||||||
|
params_ = params;
|
||||||
|
elapsed_ms_ = 0;
|
||||||
|
flash_remaining_ms_ = 0;
|
||||||
|
flash_started_ = false;
|
||||||
|
burst_remaining_ms_ = 0;
|
||||||
|
burst_y_norm_ = 0.0F;
|
||||||
|
burst_dx_ = 0;
|
||||||
|
tail_started_ = false;
|
||||||
|
tail_remaining_ms_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScanReveal::tick(int delta_ms) {
|
||||||
|
if (params_.duration_ms <= 0) { return; }
|
||||||
|
|
||||||
|
// 1) Avanç del scan (clampejat a duration_ms).
|
||||||
|
elapsed_ms_ = std::min(elapsed_ms_ + delta_ms, params_.duration_ms);
|
||||||
|
|
||||||
|
// 2) Tick del burst VHS actiu, si n'hi ha.
|
||||||
|
if (burst_remaining_ms_ > 0) {
|
||||||
|
burst_remaining_ms_ = std::max(0, burst_remaining_ms_ - delta_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Spawn d'un nou burst. Permès durant el scan i durant el
|
||||||
|
// glitch tail post-flash; bloquejat exactament durant el
|
||||||
|
// flash perquè aquell és un punt visual de tancament net.
|
||||||
|
// Probabilitat per frame ≈ (per_sec/1000) · delta_ms.
|
||||||
|
const bool TAIL_ACTIVE = tail_started_ && tail_remaining_ms_ > 0;
|
||||||
|
const bool SCAN_ONGOING = elapsed_ms_ < params_.duration_ms;
|
||||||
|
if (burst_remaining_ms_ == 0 && (SCAN_ONGOING || TAIL_ACTIVE) && params_.vhs_burst_per_sec > 0 && params_.vhs_burst_duration_ms > 0) {
|
||||||
|
const float P = (static_cast<float>(params_.vhs_burst_per_sec) / 1000.0F) * static_cast<float>(delta_ms);
|
||||||
|
std::uniform_real_distribution<float> dist01(0.0F, 1.0F);
|
||||||
|
if (dist01(rng_) < P) {
|
||||||
|
burst_remaining_ms_ = params_.vhs_burst_duration_ms;
|
||||||
|
burst_y_norm_ = dist01(rng_);
|
||||||
|
std::uniform_int_distribution<int> dist_sign(0, 1);
|
||||||
|
const int SIGN = (dist_sign(rng_) == 0) ? 1 : -1;
|
||||||
|
burst_dx_ = SIGN * params_.vhs_burst_amplitude_px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) Dispara el flash quan el scan acaba (una sola vegada).
|
||||||
|
if (!flash_started_ && elapsed_ms_ >= params_.duration_ms && params_.flash_ms > 0) {
|
||||||
|
flash_started_ = true;
|
||||||
|
flash_remaining_ms_ = params_.flash_ms;
|
||||||
|
}
|
||||||
|
// Decrement del flash mentre estiga actiu.
|
||||||
|
if (flash_remaining_ms_ > 0) {
|
||||||
|
flash_remaining_ms_ = std::max(0, flash_remaining_ms_ - delta_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5) Glitch tail: arranca un sol cop quan el scan ha acabat i el
|
||||||
|
// flash s'ha esgotat. Si no hi havia flash configurat, també
|
||||||
|
// arranca tot just acabar el scan.
|
||||||
|
if (!tail_started_ && elapsed_ms_ >= params_.duration_ms && flash_remaining_ms_ <= 0 && params_.tail_glitch_ms > 0) {
|
||||||
|
tail_started_ = true;
|
||||||
|
tail_remaining_ms_ = params_.tail_glitch_ms;
|
||||||
|
}
|
||||||
|
if (tail_remaining_ms_ > 0) {
|
||||||
|
tail_remaining_ms_ = std::max(0, tail_remaining_ms_ - delta_ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ScanReveal::isActive() const -> bool {
|
||||||
|
return params_.duration_ms > 0 && !isFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ScanReveal::isFinished() const -> bool {
|
||||||
|
if (params_.duration_ms <= 0) { return true; }
|
||||||
|
// Acabat = scan al final + flash exhaurit + glitch tail
|
||||||
|
// exhaurit (o sense tail configurat).
|
||||||
|
const bool SCAN_DONE = elapsed_ms_ >= params_.duration_ms;
|
||||||
|
const bool FLASH_DONE = flash_remaining_ms_ <= 0;
|
||||||
|
const bool TAIL_DONE = (params_.tail_glitch_ms <= 0) || (tail_started_ && tail_remaining_ms_ <= 0);
|
||||||
|
return SCAN_DONE && FLASH_DONE && TAIL_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScanReveal::render(const Surface& src, int x, int y) const {
|
||||||
|
if (params_.duration_ms <= 0) {
|
||||||
|
// Sense efecte: render pla.
|
||||||
|
src.render(x, y);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int H = src.getHeight();
|
||||||
|
const auto W_F = static_cast<float>(src.getWidth());
|
||||||
|
const float T = static_cast<float>(elapsed_ms_) / static_cast<float>(params_.duration_ms);
|
||||||
|
|
||||||
|
// Banda: comença a y=-band_height i acaba a y=H. Així al t=0 cap
|
||||||
|
// fila és visible i al t=1 totes ho són.
|
||||||
|
const float SWEEP_F = -static_cast<float>(params_.band_height_px) + (T * static_cast<float>(H + params_.band_height_px));
|
||||||
|
const int BAND_TOP = static_cast<int>(std::floor(SWEEP_F));
|
||||||
|
const int BAND_BOT = BAND_TOP + params_.band_height_px;
|
||||||
|
|
||||||
|
const int TIME_BUCKET = elapsed_ms_ / JITTER_TIME_BUCKET_MS;
|
||||||
|
const int BURST_Y_PX = static_cast<int>(burst_y_norm_ * static_cast<float>(H));
|
||||||
|
const bool BURST_ACTIVE = burst_remaining_ms_ > 0;
|
||||||
|
// Durant el glitch tail post-flash, BAND_TOP queda just damunt
|
||||||
|
// del peu del logo, fet que (amb el codi de jitter que segueix)
|
||||||
|
// generaria jitter residual a les últimes files. Aquí el
|
||||||
|
// suprimim explícitament: el tail només viu de bursts VHS.
|
||||||
|
const bool TAIL_ACTIVE = tail_started_ && tail_remaining_ms_ > 0;
|
||||||
|
|
||||||
|
// Render fila a fila (només files revelades).
|
||||||
|
for (int row = 0; row <= BAND_BOT && row < H; ++row) {
|
||||||
|
int dx = 0;
|
||||||
|
|
||||||
|
// Jitter de banda: amplitud màxima a la banda, falloff
|
||||||
|
// lineal cap als marges del radi de glitch. Inactiu durant
|
||||||
|
// el glitch tail (no hi ha banda).
|
||||||
|
const int DIST_TO_BAND = std::abs(row - BAND_TOP);
|
||||||
|
if (!TAIL_ACTIVE && params_.glitch_band_radius_px > 0 && DIST_TO_BAND <= params_.glitch_band_radius_px && params_.glitch_amplitude_px > 0) {
|
||||||
|
const float FALLOFF = 1.0F - (static_cast<float>(DIST_TO_BAND) / static_cast<float>(params_.glitch_band_radius_px));
|
||||||
|
const float JIT = rowJitter(row, TIME_BUCKET);
|
||||||
|
dx += static_cast<int>(std::lround(
|
||||||
|
FALLOFF * static_cast<float>(params_.glitch_amplitude_px) * JIT));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Burst VHS: només damunt la part ja revelada (row < BAND_TOP)
|
||||||
|
// perquè per sota encara no hi ha imatge "estable" que poder
|
||||||
|
// distorsionar visualment.
|
||||||
|
if (BURST_ACTIVE && row < BAND_TOP && row >= BURST_Y_PX && row < BURST_Y_PX + params_.vhs_burst_height_px) {
|
||||||
|
dx += burst_dx_;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_FRect row_rect{0.0F, static_cast<float>(row), W_F, 1.0F};
|
||||||
|
src.render(x + dx, y + row, &row_rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& screen = Screen::get();
|
||||||
|
|
||||||
|
// Capa lluminosa de la banda. Verticalment només cobreix les
|
||||||
|
// files del logo (és on viu el scan), però horitzontalment
|
||||||
|
// travessa tot el canvas com un feix CRT, així sobre fons negre
|
||||||
|
// no es veuen les costures del rectangle del Surface. L'alpha
|
||||||
|
// base es modula amb un pulse sinusoidal a `band_pulse_hz` per
|
||||||
|
// simular el ritme de refresc d'un CRT.
|
||||||
|
if (params_.band_alpha > 0 && params_.band_height_px > 0) {
|
||||||
|
const int VISIBLE_TOP = std::clamp(BAND_TOP, 0, H);
|
||||||
|
const int VISIBLE_BOT = std::clamp(BAND_BOT, 0, H);
|
||||||
|
const int VISIBLE_H = VISIBLE_BOT - VISIBLE_TOP;
|
||||||
|
if (VISIBLE_H > 0) {
|
||||||
|
// Pulse: fracció ∈ [0,1] avançada a la freqüència donada,
|
||||||
|
// mapejada a un multiplicador ∈ [1-depth, 1] sobre l'alpha
|
||||||
|
// base. Amb depth=0 el pulse és inactiu (multiplicador 1).
|
||||||
|
const float PULSE_DEPTH = std::clamp(params_.band_pulse_depth, 0.0F, 1.0F);
|
||||||
|
const float TWO_PI = 2.0F * std::numbers::pi_v<float>;
|
||||||
|
const float PHASE = TWO_PI * params_.band_pulse_hz * (static_cast<float>(elapsed_ms_) / 1000.0F);
|
||||||
|
const float WAVE = 0.5F + (0.5F * std::sin(PHASE)); // 0..1
|
||||||
|
const float MULT = (1.0F - PULSE_DEPTH) + (PULSE_DEPTH * WAVE);
|
||||||
|
const float ALPHA_F = static_cast<float>(params_.band_alpha) * MULT;
|
||||||
|
const auto ALPHA = static_cast<std::uint8_t>(
|
||||||
|
std::clamp(static_cast<int>(std::round(ALPHA_F)), 0, 255));
|
||||||
|
if (ALPHA > 0) {
|
||||||
|
screen.blendRect(0, y + VISIBLE_TOP, screen.getCanvasWidth(), VISIBLE_H, 0xFFFFFFFFU, ALPHA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flash final: capa blanca damunt de TOT el canvas (no només
|
||||||
|
// del rectangle del logo) — així el flash banya l'escena
|
||||||
|
// sencera com un CRT i no es veuen les costures del Surface
|
||||||
|
// sobre fons negre. Alpha proporcional a `flash_remaining_ms_
|
||||||
|
// / flash_ms` (decreix suau cap a 0 al final del flash).
|
||||||
|
if (flash_remaining_ms_ > 0 && params_.flash_ms > 0) {
|
||||||
|
const float FLASH_T = static_cast<float>(flash_remaining_ms_) / static_cast<float>(params_.flash_ms);
|
||||||
|
const auto ALPHA = static_cast<std::uint8_t>(
|
||||||
|
std::round(255.0F * std::clamp(FLASH_T, 0.0F, 1.0F)));
|
||||||
|
if (ALPHA > 0) {
|
||||||
|
screen.blendRect(0, 0, screen.getCanvasWidth(), screen.getCanvasHeight(), 0xFFFFFFFFU, ALPHA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Effects
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
class Surface;
|
||||||
|
|
||||||
|
namespace Effects {
|
||||||
|
|
||||||
|
// Revelat tipus "scanline CRT" d'una Surface. Una banda lluminosa
|
||||||
|
// baixa des de dalt cap a baix de la imatge: les files damunt de la
|
||||||
|
// banda es renderitzen normalment (ja revelades), les files sota
|
||||||
|
// queden invisibles, i a la banda mateixa s'aplica un overlay
|
||||||
|
// blanc semi-transparent. Al voltant de la banda les files reben un
|
||||||
|
// jitter horitzontal aleatori (falloff lineal amb la distància) i
|
||||||
|
// damunt la part ja revelada apareixen ocasionalment "bursts VHS" —
|
||||||
|
// franges horitzontals desplaçades durant uns ms — per a un toc
|
||||||
|
// demoscene/VHS deteriorat.
|
||||||
|
//
|
||||||
|
// Quan la banda surt per sota del logo i s'esgota `duration_ms`,
|
||||||
|
// dispara un flash blanc breu. Si `tail_glitch_ms > 0`, després del
|
||||||
|
// flash el logo queda totalment visible mentre els bursts VHS
|
||||||
|
// continuen saltant durant aquest temps (glitch tail). Quan el tail
|
||||||
|
// s'exhaureix `isFinished()=true`. El caller decideix què fa amb el
|
||||||
|
// control de fase a partir d'aquí.
|
||||||
|
//
|
||||||
|
// Ús:
|
||||||
|
// ScanReveal sr;
|
||||||
|
// sr.begin({.duration_ms = 1500, ...});
|
||||||
|
// // cada frame:
|
||||||
|
// sr.tick(delta_ms);
|
||||||
|
// sr.render(*logo, x, y);
|
||||||
|
// if (sr.isFinished()) { ... canvi de fase ... }
|
||||||
|
class ScanReveal {
|
||||||
|
public:
|
||||||
|
struct Params {
|
||||||
|
int duration_ms;
|
||||||
|
int band_height_px;
|
||||||
|
int band_alpha;
|
||||||
|
float band_pulse_hz;
|
||||||
|
float band_pulse_depth;
|
||||||
|
int glitch_amplitude_px;
|
||||||
|
int glitch_band_radius_px;
|
||||||
|
int vhs_burst_per_sec;
|
||||||
|
int vhs_burst_duration_ms;
|
||||||
|
int vhs_burst_height_px;
|
||||||
|
int vhs_burst_amplitude_px;
|
||||||
|
int flash_ms;
|
||||||
|
// Glitch tail post-flash: el logo queda visible sencer i els
|
||||||
|
// bursts VHS continuen saltant durant aquest temps. 0 ho
|
||||||
|
// desactiva (efecte acaba al flash).
|
||||||
|
int tail_glitch_ms;
|
||||||
|
};
|
||||||
|
|
||||||
|
ScanReveal();
|
||||||
|
|
||||||
|
void begin(const Params& params);
|
||||||
|
void tick(int delta_ms);
|
||||||
|
|
||||||
|
// Renderitza un frame del revelat sobre la Surface `src`. El
|
||||||
|
// caller s'encarrega de pintar negre per sota (les files no
|
||||||
|
// revelades simplement no es dibuixen — queda el que hi haja al
|
||||||
|
// canvas, normalment negre).
|
||||||
|
void render(const Surface& src, int x, int y) const;
|
||||||
|
|
||||||
|
[[nodiscard]] auto isActive() const -> bool;
|
||||||
|
[[nodiscard]] auto isFinished() const -> bool;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Params params_{};
|
||||||
|
int elapsed_ms_{0};
|
||||||
|
// Flash post-scan: arranca quan el scan toca fons; decreix amb
|
||||||
|
// `delta_ms` fins a 0. Mentre `flash_active_` és true encara no
|
||||||
|
// hem reportat finished (donant marge per pintar el flash sencer).
|
||||||
|
int flash_remaining_ms_{0};
|
||||||
|
bool flash_started_{false};
|
||||||
|
|
||||||
|
// Burst VHS actiu (com a màxim un alhora — simplifica i ja és
|
||||||
|
// suficient per al ritme visual). y_norm ∈ [0,1] es resol en
|
||||||
|
// píxels al `render()` perquè aquí no coneixem l'alçada del logo.
|
||||||
|
int burst_remaining_ms_{0};
|
||||||
|
float burst_y_norm_{0.0F};
|
||||||
|
int burst_dx_{0};
|
||||||
|
|
||||||
|
// Glitch tail post-flash: arranca quan el flash s'esgota i
|
||||||
|
// permet que els bursts continuen saltant amb el logo ja del tot
|
||||||
|
// visible. `isFinished()` espera al final d'aquest tail.
|
||||||
|
bool tail_started_{false};
|
||||||
|
int tail_remaining_ms_{0};
|
||||||
|
|
||||||
|
// RNG per als bursts. Seed des de random_device a `begin()`
|
||||||
|
// perquè cada execució sigui visualment diferent.
|
||||||
|
std::minstd_rand rng_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Effects
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#include "core/rendering/effects/shadow.hpp"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "core/rendering/surface.hpp"
|
||||||
|
#include "core/resources/resource_cache.hpp"
|
||||||
|
|
||||||
|
namespace Effects::Shadow {
|
||||||
|
|
||||||
|
void render(int center_x, int feet_y, float height_factor) {
|
||||||
|
// Lookup únic al primer ús: la Surface viu a Cache durant tota la
|
||||||
|
// sessió, així evitem la cerca lineal a cada frame.
|
||||||
|
static const auto SURFACE = Resource::Cache::get().getSurface(CACHE_KEY);
|
||||||
|
|
||||||
|
const int FRAME_COUNT = SURFACE->getWidth() / FRAME_SIZE;
|
||||||
|
if (FRAME_COUNT <= 0) { return; }
|
||||||
|
|
||||||
|
// Mapping a bandes uniformes: factor 0 → últim (gran), factor 1 →
|
||||||
|
// primer (petit). Per a 3 fotogrames: [0, 1/3) → 2, [1/3, 2/3) → 1,
|
||||||
|
// [2/3, 1] → 0. Generalitza a qualsevol nombre de fotogrames per
|
||||||
|
// si afegim mides intermèdies.
|
||||||
|
const float CLAMPED = std::clamp(height_factor, 0.0F, 1.0F);
|
||||||
|
int frame_index = static_cast<int>((1.0F - CLAMPED) * static_cast<float>(FRAME_COUNT));
|
||||||
|
frame_index = std::clamp(frame_index, 0, FRAME_COUNT - 1);
|
||||||
|
|
||||||
|
SDL_FRect clip{
|
||||||
|
static_cast<float>(frame_index * FRAME_SIZE),
|
||||||
|
0.0F,
|
||||||
|
static_cast<float>(FRAME_SIZE),
|
||||||
|
static_cast<float>(FRAME_SIZE),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ancoratge: vora inferior del fotograma a `feet_y` — així el
|
||||||
|
// quadrat 16x16 del sprite encaixa exactament al 16x16 inferior
|
||||||
|
// d'una entitat 16x16/16x32 centrada en `center_x`.
|
||||||
|
const int X = center_x - (FRAME_SIZE / 2);
|
||||||
|
const int Y = feet_y - FRAME_SIZE;
|
||||||
|
SURFACE->render(X, Y, &clip);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Effects::Shadow
|
||||||