Compare commits

..
18 Commits
Author SHA1 Message Date
JailDesigner 0d58da536c chore(version): bump 0.37.0 -> 0.38.0 2026-05-08 11:16:22 +02:00
JailDesigner 583acb9053 fix(config): rutes per plataforma a createSystemFolder 2026-05-08 11:15:15 +02:00
JailDesigner babb111b6f chore(version): bump 0.36.0 -> 0.37.0 2026-05-08 09:59:23 +02:00
JailDesignerandClaude Opus 4.7 866e133aa9 Merge branch 'feat/logger'
Sistema de log unificat (Log:: wrapper sobre SDL_Log) substituint
els 176 std::cout/std::cerr dispersos per source/. API basada en
std::format, filtre runtime via AEE_LOG (sintaxi *:info,Tag:debug),
i marcadors literals via Log::raw().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 09:59:16 +02:00
JailDesignerandClaude Opus 4.7 170521aba6 feat(log): afegeix Log::raw() per a marcadors sense prefix
Recupera el comportament històric dels marcadors "Game start" i "Bye!"
que han de sortir pelats (no com a "App: Game start"). Log::raw(level, msg)
és una variant prima sobre SDL_LogMessage que no anteposa cap tag i cau a
SDL_LOG_CATEGORY_APPLICATION (filtrable per AEE_LOG global).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 09:54:13 +02:00
JailDesignerandClaude Opus 4.7 c8882a3f6e refactor(log): migra els subsistemes restants i elimina iostream
Acaba la migració dels std::cout/cerr a Log::*:

- core/locale, core/ui/ui_config, core/rendering/{screen,text,gif,
  surface, sprite/animated_sprite, sdl3gpu/*}.
- game/{options, debug_config, editor/editor_config, levels/level_manager,
  map/{map,tilemap,paint_layer}, entities/entity_manager_loadlevel}.

Severitats coherents: warns per a configs absents amb fallback,
errors per a YAML malformat o E/S fallida, infos per a estats.

Resultat:
- 0 occurrences de std::cout/std::cerr a source/.
- Cap #include <iostream> orfà (eliminat de surface.cpp).
- Output funcional idèntic al històric; AEE_LOG filtra (ex.
  `AEE_LOG=warn` només mostra warnings, `AEE_LOG=*:warn,Tag:info`
  combina globals i overrides).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 09:34:24 +02:00
JailDesignerandClaude Opus 4.7 5fce52e0ad refactor(log): migra game/scenes i game/ui a Log::
- logo_scene, title_scene, intro_scene, room_fsm: missatges de
  cicle d'escena com a Log::info amb tag "scene" o "RoomFsm".
  Eliminats els TODO obsolets sobre el logger pendent.
- game_scene (16 logs): events del bus (LEVEL COMPLETE, PLAYER HIT,
  PLAYER KILLED) com a info; transicions de ROOM com a info; warns
  als casos d'àudio absent o música no arrencada.
- hud (3 logs): init i events com a info.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 09:24:11 +02:00
JailDesignerandClaude Opus 4.7 0b708f1515 refactor(log): migra core/input i core/audio a Log::
- gamepad_manager.cpp (12 logs): connect/disconnect com a info,
  errors d'open/init com a error, warning per BD no carregada.
- key_config.cpp (5 logs): error per arxius malformats, info
  per al recompte de bindings carregats.
- gamepad_bindings.cpp (4 logs): error/info anàleg.
- audio_effects.cpp (2 errors de format).
- sound_effects_config.cpp (3 logs): warn quan no hi ha presets,
  error de parse.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 09:17:12 +02:00
JailDesignerandClaude Opus 4.7 6e99fd0a8b refactor(log): migra core/resources i core/system a Log::
- resource_pack.cpp i resource_cache.cpp: tots els std::cerr passen a
  Log::error.
- director.cpp: std::cout/cerr/fprintf → Log::info / error / critical
  segons severitat.
- printWithDots (utils.cpp): ara és un wrapper sobre Log::info que
  manté l'amplada total (~50 chars) reproduint l'alineació de punts.
  Els callers pasen el tag sense " : " (ja l'afegeix el logger).
- pack_resources: afegit log.cpp i SDL3 al target perquè
  resource_pack.cpp ara depèn de Log::.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 09:10:03 +02:00
JailDesignerandClaude Opus 4.7 1243b457be feat(log): introdueix Log:: wrapper sobre SDL_Log
Logger lleuger a source/utils/log.{hpp,cpp} amb API basada en std::format
i filtre runtime via la variable d'entorn AEE_LOG (sintaxi
*:info,Resource::Helper:warn,scene:debug). Substitueix gradualment els
std::cout/cerr dispersos.

- Categories CUSTOM registrades sota demanda; per defecte INFO.
- Categories SDL natives no es toquen tret que l'usuari posi *:level.
- Migra resource_helper.cpp i els marcadors d'inici/fi (main.cpp) com a
  testimoni; la resta de subsistemes es migra en commits següents.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 09:01:09 +02:00
JailDesigner c910a16834 tune(whip): puja swing a 320ms (80ms × 4 frames) 2026-05-07 20:36:15 +02:00
JailDesigner 0ec1a2548a feat(mummy): so mummy_hurt en colpejada de fuet 2026-05-07 20:25:23 +02:00
JailDesigner 4ea8ab7b6d art(whip): actualitza els 4 GIFs direccionals del fuet 2026-05-07 20:12:57 +02:00
JailDesigner 05465b791e refactor(data): reorganitza data/ i centralitza paths a defaults.hpp 2026-05-07 19:28:28 +02:00
JailDesigner 9949096fba chore(rename): aee_2026 -> aee_arcade 2026-05-07 18:39:02 +02:00
JailDesigner ea5762265e chore(version): bump 0.35.0 -> 0.36.0 2026-05-07 16:46:11 +02:00
JailDesigner 78732042cb merge: feature/logo-timeline-redesign 2026-05-07 16:45:34 +02:00
JailDesigner f10671d9c0 feat(logo): timeline lineal de 7 fases amb glitch tail i durades al YAML 2026-05-07 16:44:34 +02:00
114 changed files with 1136 additions and 683 deletions

No files matched your search

+2 -2
View File
@@ -8,8 +8,8 @@ dist/
*.dylib
*.dll
*.exe
aee_2026
_aee_2026
aee_arcade
_aee_arcade
.vscode/
.idea/
.cache/
+10 -1
View File
@@ -1,5 +1,5 @@
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)
set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE)
@@ -104,6 +104,7 @@ set(APP_SOURCES
source/core/resources/resource_pack.cpp
source/core/resources/palette_loader.cpp
source/utils/easing.cpp
source/utils/log.cpp
source/utils/utils.cpp
source/game/debug_config.cpp
source/game/options.cpp
@@ -218,8 +219,10 @@ target_link_libraries(${PROJECT_NAME} PRIVATE SDL3::SDL3)
add_executable(pack_resources EXCLUDE_FROM_ALL
tools/pack_resources/pack_resources.cpp
source/core/resources/resource_pack.cpp
source/utils/log.cpp
)
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)
# --- REGENERACIÓ AUTOMÀTICA DE build/resources.pack ---
@@ -361,9 +364,11 @@ if(BUILD_TESTS)
add_executable(test_entities_smoke
tests/test_entities_smoke.cpp
tests/hole_player_stubs.cpp
tests/audio_cache_stubs.cpp
source/core/resources/resource_helper.cpp
source/core/resources/resource_pack.cpp
source/game/map/map.cpp
source/utils/log.cpp
${ENTITY_SOURCES_BASE}
)
target_include_directories(test_entities_smoke PRIVATE
@@ -389,9 +394,11 @@ if(BUILD_TESTS)
tests/test_whip_attack.cpp
tests/whip_attack_player_stubs.cpp
tests/hole_player_stubs.cpp
tests/audio_cache_stubs.cpp
source/core/resources/resource_helper.cpp
source/core/resources/resource_pack.cpp
source/game/map/map.cpp
source/utils/log.cpp
${ENTITY_SOURCES_BASE}
source/game/entities/concrete/whip_attack.cpp
)
@@ -416,6 +423,7 @@ if(BUILD_TESTS)
source/core/input/key_config.cpp
source/core/resources/resource_helper.cpp
source/core/resources/resource_pack.cpp
source/utils/log.cpp
)
target_include_directories(test_scene_flow PRIVATE
"${CMAKE_SOURCE_DIR}/source"
@@ -455,6 +463,7 @@ if(BUILD_TESTS)
source/core/resources/resource_helper.cpp
source/core/resources/resource_pack.cpp
source/utils/easing.cpp
source/utils/log.cpp
)
target_include_directories(test_game_fsm PRIVATE
"${CMAKE_SOURCE_DIR}/source"
+1 -1
View File
@@ -1,4 +1,4 @@
PROJECT := aee_2026
PROJECT := aee_arcade
BUILDDIR := build
# Detecció de plataforma. En Windows CMake defaulteja a "NMake Makefiles"
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Binary file not shown.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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
# de moviment del jugador viuen a part (Defaults::KeysGame); ací només hi ha
File renamed without changes.
+115
View File
@@ -0,0 +1,115 @@
# 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
# 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
# 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/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"
+3
View File
@@ -58,3 +58,6 @@ hurt:
duration_ms: 600
speed: 60
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
File renamed without changes.
File renamed without changes.
@@ -40,10 +40,10 @@ animations:
hurt_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
hurt_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
attack_south: { frames: [20, 21, 22, 23], frame_ms: 50 }
attack_north: { frames: [24, 25, 26, 27], frame_ms: 50 }
attack_east: { frames: [28, 29, 30, 31], frame_ms: 50 }
attack_west: { frames: [28, 29, 30, 31], frame_ms: 50, flip_h: true }
attack_south: { frames: [20, 21, 22, 23], frame_ms: 80 }
attack_north: { frames: [24, 25, 26, 27], frame_ms: 80 }
attack_east: { frames: [28, 29, 30, 31], frame_ms: 80 }
attack_west: { frames: [28, 29, 30, 31], frame_ms: 80, flip_h: true }
recover_south: { frames: [23] }
recover_north: { frames: [27] }
@@ -119,7 +119,7 @@ hurt:
# recuperació en què la hitbox ja no fa dany.
whip:
range_tiles: 3
swing_ms: 200
swing_ms: 320
cooldown_ms: 200
damage: 1
sound: whip.wav
@@ -136,7 +136,7 @@ death:
# poden ferir ni matar el jugador (els forats sí que el maten — la
# caiguda no és dany convencional). Visualment es manifesta amb un
# 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
# mecànica queda desactivada i el comportament és l'antic.
immunity:
File renamed without changes.
@@ -40,10 +40,10 @@ animations:
hurt_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
hurt_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
attack_south: { frames: [20, 21, 22, 23], frame_ms: 50 }
attack_north: { frames: [24, 25, 26, 27], frame_ms: 50 }
attack_east: { frames: [28, 29, 30, 31], frame_ms: 50 }
attack_west: { frames: [28, 29, 30, 31], frame_ms: 50, flip_h: true }
attack_south: { frames: [20, 21, 22, 23], frame_ms: 80 }
attack_north: { frames: [24, 25, 26, 27], frame_ms: 80 }
attack_east: { frames: [28, 29, 30, 31], frame_ms: 80 }
attack_west: { frames: [28, 29, 30, 31], frame_ms: 80, flip_h: true }
recover_south: { frames: [23] }
recover_north: { frames: [27] }
@@ -119,7 +119,7 @@ hurt:
# recuperació en què la hitbox ja no fa dany.
whip:
range_tiles: 3
swing_ms: 200
swing_ms: 320
cooldown_ms: 200
damage: 1
sound: whip.wav
@@ -136,7 +136,7 @@ death:
# poden ferir ni matar el jugador (els forats sí que el maten — la
# caiguda no és dany convencional). Visualment es manifesta amb un
# 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
# mecànica queda desactivada i el comportament és l'antic.
immunity:
+2 -2
View File
@@ -25,7 +25,7 @@ sprites:
# 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.
# - `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`).
# - `active_frames` : valors dins de `frames` durant els quals la
# hitbox aplica dany. Wind-up (0) i recovery (3) no
@@ -33,6 +33,6 @@ sprites:
animations:
swing:
frames: [0, 1, 2, 3]
frame_ms: 50
frame_ms: 80
loop: false
active_frames: [1, 2]
Binary file not shown.

Before

Width:  |  Height:  |  Size: 313 B

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 B

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 369 B

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 313 B

After

Width:  |  Height:  |  Size: 327 B

+1 -1
View File
@@ -1,4 +1,4 @@
# AEE 2026 — Català
# AEE ARCADE — Català
# lang: ca
notification:
+1 -1
View File
@@ -1,4 +1,4 @@
# AEE 2026 — English
# AEE ARCADE — English
# lang: en
notification:
@@ -1,6 +1,6 @@
# 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 +
# i font del text es reutilitzen del tema UI (data/config/ui.yaml > pause +
# notification). Si manca el fitxer o alguna clau, GameScene cau als
# fallbacks compilats; veure `readSceneConfig` a game_scene.cpp.
@@ -17,7 +17,7 @@ room_intro:
# 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
# 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.
@@ -33,7 +33,7 @@ room_outro:
text:
# Noms d'easing tal com els resol UiConfig::curveByName (vegeu
# `data/ui.yaml`). El text entra suau i surt accelerant.
# `data/config/ui.yaml`). El text entra suau i surt accelerant.
enter_curve: outCubic
exit_curve: inCubic
+24
View File
@@ -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
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+77
View File
@@ -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
File renamed without changes.
-217
View File
@@ -1,217 +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
# 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
# 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"
# Soroll/scratch sincronitzat amb la fase SCAN de LogoScene (scanline
# reveal). Es dispara al constructor de l'escena.
logo_glitch: "glitch.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
# 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
# Escena LOGO (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
+2 -2
View File
@@ -22,8 +22,8 @@ real (logo animat, menú de títol, cutscene).
Fitxer personal de desenvolupament, **fora del repo**. Viu a la mateixa carpeta
de sistema que `config.yaml`:
- Linux (XDG): `$XDG_CONFIG_HOME/jailgames/aee_2026/debug.yaml`
- Per defecte: `~/.config/jailgames/aee_2026/debug.yaml`
- Linux (XDG): `$XDG_CONFIG_HOME/jailgames/aee_arcade/debug.yaml`
- Per defecte: `~/.config/jailgames/aee_arcade/debug.yaml`
### Comportament segons build
+4 -4
View File
@@ -1,6 +1,6 @@
# 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`.
@@ -24,7 +24,7 @@ Estats possibles: `DONE`, `WIP`, `DEFERRED`, `WONTFIX`.
## 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):
@@ -158,7 +158,7 @@ Al CMakeLists: `ENTITY_SOURCES` conté els fitxers core; `APP_SOURCES` afegeix `
- `make build`: OK, 0 warnings nous.
- `./build/test_entities_smoke`: **9/9 OK**.
- `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 : 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.
- `./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
+4 -4
View File
@@ -1,7 +1,7 @@
# Pla d'implementació — Menú de servei
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
referència per a implementar-ho en una sessió futura; no s'ha escrit cap codi
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
| 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()` |
| 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
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
`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.
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
hardcoded.
+3 -3
View File
@@ -7,17 +7,17 @@
<key>CFBundleDisplayName</key>
<string>Aventures en Egipte ARCADE</string>
<key>CFBundleExecutable</key>
<string>aee_2026</string>
<string>aee_arcade</string>
<key>CFBundleIconFile</key>
<string>icon</string>
<key>CFBundleIconName</key>
<string>icon</string>
<key>CFBundleIdentifier</key>
<string>org.jailgames.aee_2026</string>
<string>org.jailgames.aee_arcade</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>aee_2026</string>
<string>aee_arcade</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
@@ -1,2 +1,2 @@
// aee_2026.rc
// aee_arcade.rc
IDI_ICON1 ICON "release/icons/icon.ico"
+1 -1
View File
@@ -80,7 +80,7 @@ class Audio {
// --- Control de sons ---
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)
// Reprodueix un so processat per un efecte definit a data/sounds.yaml
// Reprodueix un so processat per un efecte definit a data/config/sounds.yaml
// (preset_name busca a SoundEffectsConfig). Si el preset no existeix
// o el motor està al cap de canals amb efecte, fa fallback a playSound
// sec — l'usuari sent el so igualment, sense la cua.
+3 -3
View File
@@ -5,10 +5,10 @@
#include <cmath>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <vector>
#include "core/audio/jail_audio.hpp"
#include "utils/log.hpp"
namespace AudioEffects {
@@ -46,11 +46,11 @@ namespace AudioEffects {
} else if (spec.format == SDL_AUDIO_U8) {
bytes_per_sample = 1;
} 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;
}
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;
}
+4 -4
View File
@@ -1,10 +1,10 @@
#include "core/audio/sound_effects_config.hpp"
#include <iostream>
#include <string>
#include "core/resources/resource_helper.hpp"
#include "external/fkyaml_node.hpp"
#include "utils/log.hpp"
namespace {
@@ -27,7 +27,7 @@ auto SoundEffectsConfig::get() -> SoundEffectsConfig& {
void SoundEffectsConfig::load(const std::string& file_path) {
auto bytes = Resource::Helper::loadFile(file_path);
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;
}
@@ -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) {
std::cerr << "SoundEffectsConfig: error parsejant " << file_path << ": " << e.what() << '\n';
Log::error("SoundEffectsConfig", "error parsejant {}: {}", file_path, e.what());
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
#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
// preset no existeix, el so es reprodueix sec amb un avís a stderr.
//
+7 -10
View File
@@ -3,12 +3,12 @@
#include <algorithm>
#include <array>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string_view>
#include <utility>
#include "external/fkyaml_node.hpp"
#include "utils/log.hpp"
namespace {
@@ -90,7 +90,7 @@ auto GamepadBindings::get() -> GamepadBindings& {
}
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ú).
switch (action) {
case Action::JUMP:
@@ -167,8 +167,7 @@ void GamepadBindings::load(const std::string& file_path) {
try {
auto yaml = fkyaml::node::deserialize(CONTENT);
if (!yaml.contains("gamepads") || !yaml["gamepads"].is_sequence()) {
std::cerr << "GamepadBindings: secció 'gamepads' absent o invàlida a "
<< file_path << '\n';
Log::error("GamepadBindings", "secció 'gamepads' absent o invàlida a {}", file_path);
return;
}
for (const auto& entry : yaml["gamepads"]) {
@@ -188,11 +187,9 @@ void GamepadBindings::load(const std::string& file_path) {
}
configs_.push_back(std::move(cfg));
}
std::cout << "GamepadBindings: " << configs_.size() << " mandos carregats de "
<< file_path << '\n';
Log::info("GamepadBindings", "{} mandos carregats de {}", configs_.size(), file_path);
} catch (const std::exception& e) {
std::cerr << "GamepadBindings: error parsejant " << file_path << ": "
<< e.what() << '\n';
Log::error("GamepadBindings", "error parsejant {}: {}", file_path, e.what());
}
}
@@ -200,7 +197,7 @@ auto GamepadBindings::save() -> bool {
if (file_path_.empty()) { return false; }
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 << "gamepads:\n";
for (const auto& cfg : configs_) {
@@ -215,7 +212,7 @@ auto GamepadBindings::save() -> bool {
std::ofstream file(file_path_, std::ios::binary);
if (!file) {
std::cerr << "GamepadBindings: no es pot escriure " << file_path_ << '\n';
Log::error("GamepadBindings", "no es pot escriure {}", file_path_);
return false;
}
file << out.str();
+12 -20
View File
@@ -1,13 +1,13 @@
#include "core/input/gamepad_manager.hpp"
#include <algorithm>
#include <iostream>
#include <iterator>
#include <string>
#include <utility>
#include "core/locale/locale.hpp"
#include "core/rendering/notification_presets.hpp"
#include "utils/log.hpp"
namespace {
@@ -94,8 +94,7 @@ void GamepadManager::addGamepad(SDL_JoystickID jid, bool notify) {
if (!SDL_IsGamepad(jid)) { return; }
SDL_Gamepad* pad = SDL_OpenGamepad(jid);
if (pad == nullptr) {
std::cout << "GamepadManager: SDL_OpenGamepad(" << jid
<< ") ha fallat: " << SDL_GetError() << '\n';
Log::error("GamepadManager", "SDL_OpenGamepad({}) ha fallat: {}", jid, SDL_GetError());
return;
}
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.
const char* raw_path = SDL_GetGamepadPath(pad);
std::string path = (raw_path != nullptr) ? raw_path : "";
std::cout << "GamepadManager: connectat " << name
<< (path.empty() ? "" : " [" + path + "]") << '\n';
Log::info("GamepadManager", "connectat {}{}", name, path.empty() ? std::string{} : " [" + 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,
@@ -129,7 +127,7 @@ void GamepadManager::removeGamepad(SDL_JoystickID jid) {
std::string name = std::move(it->pretty_name);
SDL_Gamepad* pad = it->pad;
if (pad != nullptr) { SDL_CloseGamepad(pad); }
std::cout << "GamepadManager: desconnectat " << name << '\n';
Log::info("GamepadManager", "desconnectat {}", name);
entries_.erase(it);
// Qualsevol assignació que apuntava a aquest mando queda amb `pad`
@@ -145,8 +143,7 @@ void GamepadManager::removeGamepad(SDL_JoystickID jid) {
void GamepadManager::init() {
if (initialized_) { return; }
if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD)) {
std::cout << "GamepadManager: SDL_INIT_GAMEPAD ha fallat: "
<< SDL_GetError() << '\n';
Log::error("GamepadManager", "SDL_INIT_GAMEPAD ha fallat: {}", SDL_GetError());
return;
}
// `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());
if (ADDED < 0) {
std::cout << "GamepadManager: no s'ha pogut carregar " << db_path
<< ": " << SDL_GetError() << '\n';
Log::warn("GamepadManager", "no s'ha pogut carregar {}: {}", db_path, SDL_GetError());
} else {
std::cout << "GamepadManager: " << ADDED << " mappings carregats de " << db_path << '\n';
Log::info("GamepadManager", "{} mappings carregats de {}", ADDED, db_path);
}
int count = 0;
@@ -175,7 +171,7 @@ void GamepadManager::init() {
}
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;
}
@@ -268,8 +264,7 @@ void GamepadManager::assignToPlayer(int player_id, const std::string& name, cons
if (a.pad != nullptr) {
for (auto& other : assignments_) {
if (&other != &a && other.pad == a.pad) {
std::cout << "GamepadManager: mando " << a.name
<< " reassignat (last-wins)\n";
Log::info("GamepadManager", "mando {} reassignat (last-wins)", a.name);
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
// visible a la consola igual que les autoassignacions.
if (a.pad != nullptr) {
std::cout << "GamepadManager: assignat " << a.name
<< " → P" << (player_id + 1) << '\n';
Log::info("GamepadManager", "assignat {} → P{}", a.name, player_id + 1);
} else {
std::cout << "GamepadManager: P" << (player_id + 1) << " pendent ("
<< (a.name.empty() ? a.path : a.name) << " no connectat)\n";
Log::info("GamepadManager", "P{} pendent ({} no connectat)", player_id + 1, a.name.empty() ? a.path : a.name);
}
}
@@ -307,8 +300,7 @@ auto GamepadManager::autoAssignFreeSlots() -> std::vector<AutoAssigned> {
slot.name = e.pretty_name;
slot.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
<< " → P" << (pid + 1) << '\n';
Log::info("GamepadManager", "auto-assigned {} → P{}", e.pretty_name, pid + 1);
break;
}
}
+2 -2
View File
@@ -26,7 +26,7 @@ namespace GlobalInputs {
// La lliguem a la durada de la notificació EXIT perquè el missatge i
// 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).
// 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 MAX_ZOOM = 6;
@@ -161,7 +161,7 @@ namespace GlobalInputs {
// Debug helper: dispara una notificació prou llarga perquè
// `wrapToWidth` la parteixi en (almenys) dues línies. Útil per a
// 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() {
Notification::notify("Notificació de prova llarga per a comprovar que el text es parteix correctament en dues línies dins la caixa.");
}
+6 -6
View File
@@ -1,11 +1,11 @@
#include "core/input/key_config.hpp"
#include <fstream>
#include <iostream>
#include <string>
#include "core/resources/resource_helper.hpp"
#include "external/fkyaml_node.hpp"
#include "utils/log.hpp"
auto KeyConfig::get() -> KeyConfig& {
static KeyConfig instance_;
@@ -15,7 +15,7 @@ auto KeyConfig::get() -> KeyConfig& {
void KeyConfig::load(const std::string& file_path) {
auto bytes = Resource::Helper::loadFile(file_path);
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;
}
@@ -24,7 +24,7 @@ void KeyConfig::load(const std::string& file_path) {
const auto* end = begin + bytes.size();
auto yaml = fkyaml::node::deserialize(begin, end);
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;
}
bindings_.clear();
@@ -34,14 +34,14 @@ void KeyConfig::load(const std::string& file_path) {
const auto CODE = entry["code"].get_value<std::string>();
const SDL_Scancode SC = SDL_GetScancodeFromName(CODE.c_str());
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;
}
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) {
std::cerr << "KeyConfig: error parsejant " << file_path << ": " << e.what() << '\n';
Log::error("KeyConfig", "error parsejant {}: {}", file_path, e.what());
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
#include <string>
#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
// 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
+5 -5
View File
@@ -1,11 +1,11 @@
#include "core/locale/locale.hpp"
#include <fstream>
#include <iostream>
#include <string>
#include "core/resources/resource_helper.hpp"
#include "external/fkyaml_node.hpp"
#include "utils/log.hpp"
namespace {
@@ -36,7 +36,7 @@ auto Locale::get() -> Locale& {
void Locale::load(const std::string& file_path) {
auto bytes = Resource::Helper::loadFile(file_path);
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;
}
@@ -46,15 +46,15 @@ void Locale::load(const std::string& file_path) {
auto yaml = fkyaml::node::deserialize(begin, end);
strings_.clear();
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) {
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 it = strings_.find(key);
if (it != strings_.end()) { return it->second; }
std::cerr << "Locale: clau no trobada: " << key << '\n';
Log::warn("Locale", "clau no trobada: {}", key);
return key;
}
+33 -6
View File
@@ -50,6 +50,8 @@ namespace Effects {
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) {
@@ -63,9 +65,13 @@ namespace Effects {
burst_remaining_ms_ = std::max(0, burst_remaining_ms_ - delta_ms);
}
// 3) Spawn d'un nou burst (només durant el scan, no al flash).
// 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.
if (burst_remaining_ms_ == 0 && elapsed_ms_ < params_.duration_ms && params_.vhs_burst_per_sec > 0 && params_.vhs_burst_duration_ms > 0) {
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) {
@@ -86,6 +92,17 @@ namespace Effects {
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 {
@@ -94,8 +111,12 @@ namespace Effects {
auto ScanReveal::isFinished() const -> bool {
if (params_.duration_ms <= 0) { return true; }
// Acabat = scan al final + flash exhaurit (o sense flash).
return elapsed_ms_ >= params_.duration_ms && flash_remaining_ms_ <= 0;
// 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 {
@@ -118,15 +139,21 @@ namespace Effects {
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.
// 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 (params_.glitch_band_radius_px > 0 && DIST_TO_BAND <= params_.glitch_band_radius_px && params_.glitch_amplitude_px > 0) {
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(
+15 -2
View File
@@ -18,8 +18,11 @@ namespace Effects {
// demoscene/VHS deteriorat.
//
// Quan la banda surt per sota del logo i s'esgota `duration_ms`,
// dispara un flash blanc breu i passa a `isFinished()=true`. El
// caller decideix què fa amb el control de fase a partir d'aquí.
// 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;
@@ -43,6 +46,10 @@ namespace Effects {
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();
@@ -75,6 +82,12 @@ namespace Effects {
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_;
+6 -7
View File
@@ -1,10 +1,11 @@
#include "core/rendering/gif.hpp"
#include <cstring>
#include <iostream>
#include <stdexcept>
#include <string>
#include "utils/log.hpp"
namespace GIF {
namespace {
@@ -111,7 +112,7 @@ namespace GIF {
while (cur_code != -1) {
out[dictionary[cur_code].len - 1] = dictionary[cur_code].byte;
if (dictionary[cur_code].prev == cur_code) {
std::cerr << "Internal error; self-reference detected." << '\n';
Log::error("GIF", "internal error; self-reference detected.");
throw std::runtime_error("Internal error in decompress: self-reference");
}
cur_code = dictionary[cur_code].prev;
@@ -155,8 +156,7 @@ namespace GIF {
if (prev > -1 && code_length < LZW_MAX_CODE_LENGTH) {
if (code > dictionary_ind) {
std::cerr << "code = " << std::hex << code
<< ", but dictionary_ind = " << dictionary_ind << '\n';
Log::error("GIF", "code = {:x}, but dictionary_ind = {}", code, dictionary_ind);
throw std::runtime_error("LZW error: code exceeds dictionary_ind.");
}
@@ -167,8 +167,7 @@ namespace GIF {
// Verifica que 'code' sea un índice válido antes de usarlo.
if (code < 0 || static_cast<size_t>(code) >= dictionary.size()) {
std::cerr << "Invalid LZW code " << code
<< ", dictionary size " << dictionary.size() << '\n';
Log::error("GIF", "invalid LZW code {}, dictionary size {}", code, dictionary.size());
throw std::runtime_error("LZW error: invalid code encountered");
}
@@ -287,7 +286,7 @@ namespace GIF {
} else if (block_type == IMAGE_DESCRIPTOR) {
return processImageDescriptor(buffer);
} else {
std::cerr << "Unrecognized block type " << std::hex << static_cast<int>(block_type) << '\n';
Log::error("GIF", "unrecognized block type {:x}", static_cast<int>(block_type));
return std::vector<uint8_t>{};
}
block_type = *buffer++;
@@ -9,7 +9,7 @@
// Temes per a les notificacions de pantalla. Un "preset" captura els dos
// colors (text i fons, índexs sobre la paleta "ui") i el temps de visibilitat
// en mil·lisegons. Els valors concrets viuen a data/ui.yaml (carregat per
// en mil·lisegons. Els valors concrets viuen a data/config/ui.yaml (carregat per
// UiConfig); per llançar una notificació amb un preset concret, cridar
// Notification::notify(text, preset). Sense preset s'usa el DEFAULT.
namespace Notification {
@@ -22,7 +22,7 @@ namespace Notification {
// Preset per defecte: text clar sobre fons fosc. Es construeix en cada
// crida a partir d'UiConfig perquè els valors no són constexpr: viuen a
// data/ui.yaml i es carreguen a l'arrencada.
// data/config/ui.yaml i es carreguen a l'arrencada.
[[nodiscard]] inline auto defaultPreset() -> Preset {
return {
.text_color = UiConfig::get().notificationText(),
+3 -3
View File
@@ -6,13 +6,13 @@
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <ranges>
#include "core/rendering/canvas.hpp"
#include "core/rendering/sdl3gpu/viewport.hpp"
#include "utils/defines.hpp"
#include "utils/log.hpp"
namespace {
@@ -112,11 +112,11 @@ void Screen::createSdl() {
backend->setVSync(config_.vsync);
if (backend->init(window_, canvas_width_, canvas_height_)) {
gpu_driver_ = backend->getDriverName();
std::cout << "Screen: path GPU actiu (driver: " << gpu_driver_ << ")\n";
Log::info("Screen", "path GPU actiu (driver: {})", gpu_driver_);
shader_backend_ = std::move(backend);
return;
}
std::cerr << "Screen: GPU no disponible, fallback a SDL_Renderer\n";
Log::warn("Screen", "GPU no disponible, fallback a SDL_Renderer");
}
gpu_driver_ = "sdl";
@@ -1,10 +1,10 @@
#include "core/rendering/sdl3gpu/pipelines/crtpi_pipeline.hpp"
#include <cstring>
#include <iostream>
#include "core/rendering/sdl3gpu/shader_factory.hpp"
#include "core/rendering/sdl3gpu/viewport.hpp"
#include "utils/log.hpp"
#ifdef __APPLE__
#include "core/rendering/sdl3gpu/msl/crtpi_frag.msl.h"
@@ -29,7 +29,7 @@ namespace Rendering {
SDL_GPUShader* frag = createShaderSPIRV(device_, kcrtpi_frag_spv, kcrtpi_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
#endif
if (vert == nullptr || frag == nullptr) {
std::cerr << "CrtPiPipeline: creació de shaders fallida: " << SDL_GetError() << '\n';
Log::error("CrtPiPipeline", "creació de shaders fallida: {}", SDL_GetError());
if (vert != nullptr) { SDL_ReleaseGPUShader(device_, vert); }
if (frag != nullptr) { SDL_ReleaseGPUShader(device_, frag); }
return false;
@@ -57,7 +57,7 @@ namespace Rendering {
SDL_ReleaseGPUShader(device_, frag);
if (pipeline_ == nullptr) {
std::cerr << "CrtPiPipeline: creació del pipeline ha fallat: " << SDL_GetError() << '\n';
Log::error("CrtPiPipeline", "creació del pipeline ha fallat: {}", SDL_GetError());
return false;
}
@@ -2,10 +2,10 @@
#include <cstdint>
#include <cstring>
#include <iostream>
#include "core/rendering/sdl3gpu/shader_factory.hpp"
#include "core/rendering/sdl3gpu/viewport.hpp"
#include "utils/log.hpp"
#ifdef __APPLE__
#include "core/rendering/sdl3gpu/msl/postfx_frag.msl.h"
@@ -59,11 +59,11 @@ namespace Rendering {
const char* driver = preferred_driver_.empty() ? nullptr : preferred_driver_.c_str();
device_ = SDL_CreateGPUDevice(FORMAT, false, driver);
if (device_ == nullptr) {
std::cerr << "SDL3GPUShader: SDL_CreateGPUDevice ha fallat: " << SDL_GetError() << '\n';
Log::error("SDL3GPUShader", "SDL_CreateGPUDevice ha fallat: {}", SDL_GetError());
return false;
}
if (!SDL_ClaimWindowForGPUDevice(device_, window_)) {
std::cerr << "SDL3GPUShader: SDL_ClaimWindowForGPUDevice ha fallat: " << SDL_GetError() << '\n';
Log::error("SDL3GPUShader", "SDL_ClaimWindowForGPUDevice ha fallat: {}", SDL_GetError());
return false;
}
SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, bestPresentMode(vsync_));
@@ -82,7 +82,7 @@ namespace Rendering {
fragment_shader_ = createShaderSPIRV(device_, kpostfx_frag_spv, kpostfx_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
#endif
if (vertex_shader_ == nullptr || fragment_shader_ == nullptr) {
std::cerr << "SDL3GPUShader: SDL_CreateGPUShader ha fallat: " << SDL_GetError() << '\n';
Log::error("SDL3GPUShader", "SDL_CreateGPUShader ha fallat: {}", SDL_GetError());
return false;
}
return true;
@@ -105,7 +105,7 @@ namespace Rendering {
pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &info);
if (pipeline_ == nullptr) {
std::cerr << "SDL3GPUShader: SDL_CreateGPUGraphicsPipeline ha fallat: " << SDL_GetError() << '\n';
Log::error("SDL3GPUShader", "SDL_CreateGPUGraphicsPipeline ha fallat: {}", SDL_GetError());
return false;
}
return true;
@@ -137,7 +137,7 @@ namespace Rendering {
sampler_ = SDL_CreateGPUSampler(device_, &samp_info);
if (scene_texture_ == nullptr || upload_buffer_ == nullptr || sampler_ == nullptr) {
std::cerr << "SDL3GPUShader: creació de recursos GPU ha fallat: " << SDL_GetError() << '\n';
Log::error("SDL3GPUShader", "creació de recursos GPU ha fallat: {}", SDL_GetError());
return false;
}
return true;
@@ -1,12 +1,12 @@
#include "core/rendering/sprite/animated_sprite.hpp"
#include <cmath>
#include <iostream>
#include <numeric>
#include <utility>
#include "core/rendering/surface.hpp"
#include "core/resources/resource_cache.hpp"
#include "utils/log.hpp"
// El parser pure de YAML d'animacions (parseAnimationsFromYaml + helpers)
// viu a `animated_sprite_parser.cpp` perquè és testable sense Surface ni
@@ -74,10 +74,10 @@ AnimatedSprite::AnimatedSprite(const AnimationResource& cached_data) {
if (!first.frames.empty()) { setClip(first.frames[0].rect); }
}
} catch (const fkyaml::exception& e) {
std::cerr << "YAML parsing error in animation " << cached_data.name << ": " << e.what() << '\n';
Log::error("AnimatedSprite", "YAML parsing error in animation {}: {}", cached_data.name, e.what());
throw;
} catch (const std::exception& e) {
std::cerr << "Error loading animation " << cached_data.name << ": " << e.what() << '\n';
Log::error("AnimatedSprite", "Error loading animation {}: {}", cached_data.name, e.what());
throw;
}
}
@@ -140,7 +140,7 @@ void AnimatedSprite::setCurrentAnimation(const std::string& name) {
if (current_animation_ == name) { return; }
auto it = animations_.find(name);
if (it == animations_.end()) {
std::cout << "** Warning: could not find \"" << name << "\" animation" << '\n';
Log::warn("AnimatedSprite", "could not find \"{}\" animation", name);
return;
}
current_animation_ = name;
+1 -2
View File
@@ -2,7 +2,6 @@
#include <algorithm>
#include <cstring>
#include <iostream>
#include <stdexcept>
#include <vector>
@@ -63,7 +62,7 @@ Surface::Surface(const std::string& file_path) {
palette_[i] = Rendering::OPAQUE_ALPHA | rgb[i];
}
printWithDots("Surface : ", getFileName(file_path), "[ LOADED ]");
printWithDots("Surface", getFileName(file_path), "[ LOADED ]");
}
void Surface::clear(Uint8 color) {
+3 -3
View File
@@ -3,7 +3,6 @@
#include <SDL3/SDL.h>
#include <cstddef>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <string>
@@ -11,6 +10,7 @@
#include "core/rendering/sprite/sprite.hpp"
#include "core/rendering/surface.hpp"
#include "core/resources/resource_helper.hpp"
#include "utils/log.hpp"
#include "utils/utils.hpp"
// Decodifica el pròxim codepoint UTF-8 de la cadena i avança 'pos' al byte
@@ -55,7 +55,7 @@ auto Text::loadTextFile(const std::string& file_path) -> std::shared_ptr<File> {
auto file_data = Resource::Helper::loadFile(file_path);
if (file_data.empty()) {
std::cerr << "Error: Fitxer no trobat " << getFileName(file_path) << '\n';
Log::error("Text File", "fitxer no trobat: {}", getFileName(file_path));
throw std::runtime_error("Fitxer no trobat: " + getFileName(file_path));
}
@@ -101,7 +101,7 @@ auto Text::loadTextFile(const std::string& file_path) -> std::shared_ptr<File> {
}
}
printWithDots("Text File : ", getFileName(file_path), "[ LOADED ]");
printWithDots("Text File", getFileName(file_path), "[ LOADED ]");
return tf;
}
+10 -10
View File
@@ -1,12 +1,12 @@
#include "core/resources/resource_cache.hpp"
#include <algorithm>
#include <iostream>
#include <stdexcept>
#include "core/audio/jail_audio.hpp"
#include "core/resources/palette_loader.hpp"
#include "core/resources/resource_helper.hpp"
#include "utils/log.hpp"
#include "utils/utils.hpp"
// Deleters dels recursos d'àudio: necessiten veure el tipus complet de
@@ -37,19 +37,19 @@ namespace Resource {
throw std::runtime_error("Cache: cannot load animation " + file_path);
}
animations_.emplace_back(AnimationResource{.name = name, .yaml_data = std::move(bytes)});
printWithDots("Animation : ", getFileName(file_path), "[ LOADED ]");
printWithDots("Animation", getFileName(file_path), "[ LOADED ]");
}
void Cache::loadPalette(const std::string& name, const std::string& file_path) {
auto palette = PaletteLoader::loadFromFile(file_path);
palettes_.emplace_back(PaletteResource{.name = name, .palette = palette});
printWithDots("Palette : ", getFileName(file_path), "[ LOADED ]");
printWithDots("Palette", getFileName(file_path), "[ LOADED ]");
}
auto Cache::getSurface(const std::string& name) -> std::shared_ptr<Surface> {
auto it = std::ranges::find_if(surfaces_, [&name](const auto& s) { return s.name == name; });
if (it == surfaces_.end()) {
std::cerr << "Cache: surface not found: " << name << '\n';
Log::error("Cache", "surface not found: {}", name);
throw std::runtime_error("Surface not found: " + name);
}
return it->surface;
@@ -58,7 +58,7 @@ namespace Resource {
auto Cache::getAnimationData(const std::string& name) -> const AnimationResource& {
auto it = std::ranges::find_if(animations_, [&name](const auto& a) { return a.name == name; });
if (it == animations_.end()) {
std::cerr << "Cache: animation not found: " << name << '\n';
Log::error("Cache", "animation not found: {}", name);
throw std::runtime_error("Animation not found: " + name);
}
return *it;
@@ -67,7 +67,7 @@ namespace Resource {
auto Cache::getPalette(const std::string& name) -> const Palette& {
auto it = std::ranges::find_if(palettes_, [&name](const auto& p) { return p.name == name; });
if (it == palettes_.end()) {
std::cerr << "Cache: palette not found: " << name << '\n';
Log::error("Cache", "palette not found: {}", name);
throw std::runtime_error("Palette not found: " + name);
}
return it->palette;
@@ -83,7 +83,7 @@ namespace Resource {
throw std::runtime_error("Cache: Ja::loadMusic failed for " + file_path);
}
musics_.emplace_back(MusicResource{.name = name, .music = std::unique_ptr<Ja::Music, MusicDeleter>(raw)});
printWithDots("Music : ", getFileName(file_path), "[ LOADED ]");
printWithDots("Music", getFileName(file_path), "[ LOADED ]");
}
void Cache::loadSound(const std::string& name, const std::string& file_path) {
@@ -96,13 +96,13 @@ namespace Resource {
throw std::runtime_error("Cache: Ja::loadSound failed for " + file_path);
}
sounds_.emplace_back(SoundResource{.name = name, .sound = std::unique_ptr<Ja::Sound, SoundDeleter>(raw)});
printWithDots("Sound : ", getFileName(file_path), "[ LOADED ]");
printWithDots("Sound", getFileName(file_path), "[ LOADED ]");
}
auto Cache::getMusic(const std::string& name) -> Ja::Music* {
auto it = std::ranges::find_if(musics_, [&name](const auto& m) { return m.name == name; });
if (it == musics_.end()) {
std::cerr << "Cache: music not found: " << name << '\n';
Log::error("Cache", "music not found: {}", name);
throw std::runtime_error("Music not found: " + name);
}
return it->music.get();
@@ -111,7 +111,7 @@ namespace Resource {
auto Cache::getSound(const std::string& name) -> Ja::Sound* {
auto it = std::ranges::find_if(sounds_, [&name](const auto& s) { return s.name == name; });
if (it == sounds_.end()) {
std::cerr << "Cache: sound not found: " << name << '\n';
Log::error("Cache", "sound not found: {}", name);
throw std::runtime_error("Sound not found: " + name);
}
return it->sound.get();
+8 -10
View File
@@ -1,11 +1,12 @@
#include "core/resources/resource_helper.hpp"
#include <fstream>
#include <iostream>
#include <string>
#include <string_view>
#include "core/resources/resource_pack.hpp"
#include "utils/defaults.hpp"
#include "utils/log.hpp"
namespace Resource::Helper {
@@ -26,14 +27,14 @@ namespace Resource::Helper {
// Silenciem si veníem de prefer_disk: aleshores caurem
// al pack i no és cap error real que el fitxer no estigui
// al disc.
if (verbose) { std::cerr << "Resource::Helper: cannot open " << FULL << '\n'; }
if (verbose) { Log::error("Resource::Helper", "cannot open {}", FULL); }
return {};
}
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<std::uint8_t> data(static_cast<std::size_t>(size));
if (!file.read(reinterpret_cast<char*>(data.data()), size)) {
std::cerr << "Resource::Helper: failed to read " << FULL << '\n';
Log::error("Resource::Helper", "failed to read {}", FULL);
return {};
}
return data;
@@ -51,14 +52,11 @@ namespace Resource::Helper {
g_pack_loaded = g_pack.loadPack(pack_file);
if (g_pack_loaded) {
std::cout << "Resource::Helper: pack loaded (" << g_pack.getResourceCount()
<< " entries) from " << pack_file << '\n';
Log::info("Resource::Helper", "pack loaded ({} entries) from {}", g_pack.getResourceCount(), pack_file);
} else if (enable_fallback) {
std::cout << "Resource::Helper: no pack at " << pack_file
<< " — using filesystem fallback\n";
Log::info("Resource::Helper", "no pack at {} — using filesystem fallback", pack_file);
} else {
std::cerr << "Resource::Helper: FATAL — no pack at " << pack_file
<< " and fallback disabled\n";
Log::critical("Resource::Helper", "FATAL — no pack at {} and fallback disabled", pack_file);
return false;
}
return true;
@@ -92,7 +90,7 @@ namespace Resource::Helper {
// carpeta `data/`).
const auto FROM_PACK = [&filepath]() -> std::vector<std::uint8_t> {
if (!g_pack_loaded) { return {}; }
constexpr std::string_view PREFIX = "data/";
const std::string_view PREFIX{Defaults::Paths::DATA_ROOT};
std::string key = filepath;
if (key.starts_with(PREFIX)) { key.erase(0, PREFIX.size()); }
if (g_pack.hasResource(key)) { return g_pack.getResource(key); }
+11 -10
View File
@@ -4,11 +4,12 @@
#include <array>
#include <filesystem>
#include <fstream>
#include <iostream>
#include "utils/log.hpp"
namespace Resource {
const std::string Pack::DEFAULT_ENCRYPT_KEY = "AEE_2026_PACK_KEY";
const std::string Pack::DEFAULT_ENCRYPT_KEY = "AEE_ARCADE_PACK_KEY";
namespace {
constexpr const char* MAGIC = "AE26";
@@ -40,14 +41,14 @@ namespace Resource {
std::array<char, 4> header{};
file.read(header.data(), 4);
if (std::string(header.data(), 4) != MAGIC) {
std::cerr << "Resource::Pack: invalid pack file format (bad magic): " << pack_file << '\n';
Log::error("Resource::Pack", "invalid pack file format (bad magic): {}", pack_file);
return false;
}
std::uint32_t version = 0;
file.read(reinterpret_cast<char*>(&version), sizeof(version));
if (version != VERSION) {
std::cerr << "Resource::Pack: unsupported pack version: " << version << '\n';
Log::error("Resource::Pack", "unsupported pack version: {}", version);
return false;
}
@@ -86,7 +87,7 @@ namespace Resource {
auto Pack::savePack(const std::string& pack_file) const -> bool {
std::ofstream file(pack_file, std::ios::binary);
if (!file) {
std::cerr << "Resource::Pack: could not create pack file: " << pack_file << '\n';
Log::error("Resource::Pack", "could not create pack file: {}", pack_file);
return false;
}
@@ -120,7 +121,7 @@ namespace Resource {
auto Pack::addFile(const std::string& filename, const std::string& filepath) -> bool {
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file) {
std::cerr << "Resource::Pack: could not open file: " << filepath << '\n';
Log::error("Resource::Pack", "could not open file: {}", filepath);
return false;
}
@@ -129,7 +130,7 @@ namespace Resource {
std::vector<std::uint8_t> file_data(file_size);
if (!file.read(reinterpret_cast<char*>(file_data.data()), file_size)) {
std::cerr << "Resource::Pack: could not read file: " << filepath << '\n';
Log::error("Resource::Pack", "could not read file: {}", filepath);
return false;
}
@@ -146,7 +147,7 @@ namespace Resource {
auto Pack::addDirectory(const std::string& directory) -> bool {
if (!std::filesystem::exists(directory)) {
std::cerr << "Resource::Pack: directory does not exist: " << directory << '\n';
Log::error("Resource::Pack", "directory does not exist: {}", directory);
return false;
}
@@ -169,7 +170,7 @@ namespace Resource {
const PackEntry& entry = it->second;
if (entry.offset + entry.size > data_.size()) {
std::cerr << "Resource::Pack: invalid resource data: " << filename << '\n';
Log::error("Resource::Pack", "invalid resource data: {}", filename);
return {};
}
@@ -179,7 +180,7 @@ namespace Resource {
std::uint32_t checksum = calculateChecksum(result);
if (checksum != entry.checksum) {
std::cerr << "Resource::Pack: checksum mismatch for: " << filename << '\n';
Log::error("Resource::Pack", "checksum mismatch for: {}", filename);
}
return result;
}
+59 -41
View File
@@ -4,10 +4,8 @@
#include <algorithm>
#include <any>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <iostream>
#include <memory>
#include <string>
#include <utility>
@@ -42,10 +40,11 @@
#include "game/scenes/title_scene.hpp"
#include "game/ui/service_menu.hpp"
#include "utils/defaults.hpp"
#include "utils/log.hpp"
Director::Director() {
if (!SDL_Init(0)) {
std::fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError());
Log::critical("Director", "SDL_Init failed: {}", SDL_GetError());
}
// Sistema de recursos: el pack viu al costat del binari (build/), el
@@ -66,12 +65,13 @@ Director::Director() {
constexpr bool ENABLE_FALLBACK = false;
#endif
if (!Resource::Helper::initializeResourceSystem(pack_file, ENABLE_FALLBACK)) {
std::fprintf(stderr, "Director: sistema de recursos no disponible; avortem\n");
Log::critical("Director", "sistema de recursos no disponible; avortem");
std::exit(1);
}
// Prepara la carpeta de sistema (~/.config/jailgames/aee_2026/) i carrega
// les opcions; si no existeix config.yaml el crea amb els defaults.
// Prepara la carpeta de sistema (per plataforma: %APPDATA% a Windows,
// ~/Library/Application Support a macOS, ~/.config a Linux) i carrega les
// opcions; si no existeix config.yaml el crea amb els defaults.
createSystemFolder();
// Gamepad: descobreix mandos presents silenciosament. Cal fer-ho ABANS
@@ -85,7 +85,7 @@ Director::Director() {
Options::setConfigFile(system_folder_ + "config.yaml");
if (!Options::loadFromFile()) {
std::cerr << "Director: no s'ha pogut carregar ni desar config.yaml a " << system_folder_ << '\n';
Log::error("Director", "no s'ha pogut carregar ni desar config.yaml a {}", system_folder_);
}
// Vinculacions de mando per dispositiu — fitxer aïllat de `config.yaml`
@@ -97,12 +97,12 @@ Director::Director() {
// es crea amb presets per defecte (off, crt, ntsc).
Options::setPostFXFile(system_folder_ + Defaults::Video::POSTFX_FILE);
if (!Options::loadPostFXFromFile()) {
std::cerr << "Director: no s'ha pogut carregar ni desar postfx.yaml.\n";
Log::error("Director", "no s'ha pogut carregar ni desar postfx.yaml.");
}
// Presets del shader CRT-Pi (shaders/crtpi.yaml).
Options::setCrtPiFile(system_folder_ + Defaults::Video::CRTPI_FILE);
if (!Options::loadCrtPiFromFile()) {
std::cerr << "Director: no s'ha pogut carregar ni desar crtpi.yaml.\n";
Log::error("Director", "no s'ha pogut carregar ni desar crtpi.yaml.");
}
// Screen es configura ara (subsystem + finestra + GPU/renderer); la seua
@@ -118,22 +118,29 @@ Director::Director() {
Locale::get().load(std::string(Defaults::I18n::LOCALE_DIR) + Options::locale() + ".yaml");
// Hotkeys globals. GlobalInputs::update() les consumirà a cada frame.
KeyConfig::get().load("data/input/keys.yaml");
KeyConfig::get().load(Defaults::Paths::INPUT_YAML);
// Tema d'UI (colors, timings, fonts, flags). Qualsevol clau absent a
// data/ui.yaml cau al valor compilat a Defaults::UI.
UiConfig::get().load("data/ui.yaml");
// data/config/ui.yaml cau al valor compilat a Defaults::UI.
UiConfig::get().load(Defaults::Paths::UI_YAML);
// Configuració específica de les escenes TITLE i LOGO. Es carreguen
// additivament sobre el mateix UiConfig: cada fitxer només conté la
// seua subclau (`title:` o `logo:`), i les claus absents a la base
// (ui.yaml) o als yamls d'escena cauen al valor compilat a
// Defaults::UI::Title / Defaults::UI::Logo.
UiConfig::get().load(Defaults::Paths::TITLE_YAML);
UiConfig::get().load(Defaults::Paths::LOGO_YAML);
#ifdef _DEBUG
// Tema visual de l'editor de tilemap (Debug-only). Claus absents
// cauen als valors compilats a Defaults::Editor.
EditorConfig::get().load("data/editor/editor.yaml");
EditorConfig::get().load(Defaults::Paths::EDITOR_YAML);
#endif
// Recursos compartits d'UI (paleta + fonts). Es carreguen abans que els
// overlays perquè tots els reaprofiten sense duplicar la lògica. Els
// noms de font viuen al fitxer data/ui.yaml via UiConfig.
Resource::Cache::get().loadPalette("ui", "data/palettes/uzebox.pal");
// noms de font viuen al fitxer data/config/ui.yaml via UiConfig.
Resource::Cache::get().loadPalette("ui", Defaults::Paths::UI_PALETTE);
const std::string HUD_FONT_GIF_KEY = UiConfig::get().hudFont().name + ".gif";
Resource::Cache::get().loadSurface(HUD_FONT_GIF_KEY, UiConfig::get().hudFont().gif);
Resource::Cache::get().getSurface(HUD_FONT_GIF_KEY)->setPalette(Resource::Cache::get().getPalette("ui"));
@@ -152,33 +159,33 @@ Director::Director() {
// Pistes de música del cicle de sala (intro/playing/outro). Es preloden
// ací perquè formen part del preload d'assets, com la font i la paleta;
// la lògica de quina sona en cada estat viu a `GameScene`.
Resource::Cache::get().loadMusic("room_intro.ogg", "data/music/room_intro.ogg");
Resource::Cache::get().loadMusic("playing.ogg", "data/music/playing.ogg");
Resource::Cache::get().loadMusic("room_outro.ogg", "data/music/room_outro.ogg");
Resource::Cache::get().loadMusic("room_intro.ogg", Defaults::Paths::MUSIC_ROOM_INTRO);
Resource::Cache::get().loadMusic("playing.ogg", Defaults::Paths::MUSIC_PLAYING);
Resource::Cache::get().loadMusic("room_outro.ogg", Defaults::Paths::MUSIC_ROOM_OUTRO);
// Música de la TITLE: arrenca al constructor de l'escena i es fa
// fade-out quan l'escena entra a STARTING.
Resource::Cache::get().loadMusic("title.ogg", "data/music/title.ogg");
Resource::Cache::get().loadMusic("title.ogg", Defaults::Paths::MUSIC_TITLE);
// Sons del menú de servei. Els noms venen de `data/ui.yaml` (amb
// Sons del menú de servei. Els noms venen de `data/config/ui.yaml` (amb
// fallback a `Defaults::UI::MENU_*_SOUND`); el ServiceMenu els
// dispara via Audio::Group::INTERFACE.
const auto& menu_select = UiConfig::get().menuSelectSound();
const auto& menu_accept = UiConfig::get().menuAcceptSound();
Resource::Cache::get().loadSound(menu_select, "data/sounds/" + menu_select);
Resource::Cache::get().loadSound(menu_accept, "data/sounds/" + menu_accept);
Resource::Cache::get().loadSound(menu_select, std::string{Defaults::Paths::AUDIO_SOUNDS_DIR} + menu_select);
Resource::Cache::get().loadSound(menu_accept, std::string{Defaults::Paths::AUDIO_SOUNDS_DIR} + menu_accept);
// So del START a la TITLE: el dispara TitleScene quan un jugador
// prem START (vore enterStarting i el reset del segon jugador).
const auto& player_start = UiConfig::get().playerStartSound();
Resource::Cache::get().loadSound(player_start, "data/sounds/" + player_start);
Resource::Cache::get().loadSound(player_start, std::string{Defaults::Paths::AUDIO_SOUNDS_DIR} + player_start);
// Soroll de LogoScene (scanline reveal). El dispara LogoScene al
// ctor; amb fallback al nom de Defaults si la clau YAML no hi és.
const auto& logo_glitch = UiConfig::get().logoGlitchSound();
Resource::Cache::get().loadSound(logo_glitch, "data/sounds/" + logo_glitch);
Resource::Cache::get().loadSound(logo_glitch, std::string{Defaults::Paths::AUDIO_SOUNDS_DIR} + logo_glitch);
// Catàleg de presets d'efectes per als sons. Es carrega abans d'Audio
// perquè els primers playSoundWith* (al començament del joc) ja
// troben els presets disponibles.
SoundEffectsConfig::get().load("data/sounds.yaml");
SoundEffectsConfig::get().load(Defaults::Paths::SOUNDS_YAML);
// Audio: singleton manual propietari d'un SDL audio device. Es destrueix
// explícitament a ~Director abans que Resource::Cache (Meyers) buide els
@@ -216,7 +223,7 @@ Director::Director() {
// Font del menú de servei (F9). El títol va amb la variant `display`
// i els items amb `condensed-display` (més compacte perquè caben més
// files). Els dos noms i kernings viuen a data/ui.yaml via UiConfig.
// files). Els dos noms i kernings viuen a data/config/ui.yaml via UiConfig.
auto load_menu_font = [&](const UiConfig::Font& font) {
const std::string KEY = font.name + ".gif";
Resource::Cache::get().loadSurface(KEY, font.gif);
@@ -233,7 +240,7 @@ Director::Director() {
// sobreescrivim amb la "ui" perquè el GIF ja porta els colors finals
// del logo).
constexpr const char* MINI_LOGO_KEY = "logo_jailgames_mini.gif";
Resource::Cache::get().loadSurface(MINI_LOGO_KEY, "data/logo/logo_jailgames_mini.gif");
Resource::Cache::get().loadSurface(MINI_LOGO_KEY, Defaults::Paths::LOGO_MINI_GIF);
auto mini_logo_surface = Resource::Cache::get().getSurface(MINI_LOGO_KEY);
mini_logo_surface_ = mini_logo_surface;
@@ -241,20 +248,20 @@ Director::Director() {
// conserva la seua paleta nadiua: els colors finals del logo viuen
// dins del propi GIF i no han de passar per la paleta UI.
constexpr const char* LOGO_FULL_KEY = "logo_jailgames.gif";
Resource::Cache::get().loadSurface(LOGO_FULL_KEY, "data/logo/logo_jailgames.gif");
Resource::Cache::get().loadSurface(LOGO_FULL_KEY, Defaults::Paths::LOGO_FULL_GIF);
logo_full_surface_ = Resource::Cache::get().getSurface(LOGO_FULL_KEY);
// Logotip del joc per a la TITLE (substitueix el text "Aventures en
// Egipte ARCADE"). Igual que la resta de logos, paleta nadiua —
// l'art ja porta els colors finals i no passa per la UI.
constexpr const char* GAME_LOGO_KEY = "game_logo.gif";
Resource::Cache::get().loadSurface(GAME_LOGO_KEY, "data/title/game_logo.gif");
Resource::Cache::get().loadSurface(GAME_LOGO_KEY, Defaults::Paths::TITLE_GAME_LOGO_GIF);
game_logo_surface_ = Resource::Cache::get().getSurface(GAME_LOGO_KEY);
// Fons sencer de la TITLE (320×240). Igual que el minilogo, conserva
// la seua paleta nadiua perquè el GIF ja porta els colors finals.
constexpr const char* TITLE_BG_KEY = "title_bg.gif";
Resource::Cache::get().loadSurface(TITLE_BG_KEY, "data/title/title_bg.gif");
Resource::Cache::get().loadSurface(TITLE_BG_KEY, Defaults::Paths::TITLE_BG_GIF);
auto title_bg_surface = Resource::Cache::get().getSurface(TITLE_BG_KEY);
title_bg_surface_ = title_bg_surface;
ServiceMenu::get().init(menu_title_text, menu_items_text, ui_palette);
@@ -288,8 +295,7 @@ Director::Director() {
const auto START = DebugConfig::StartScene::LOGO;
const auto STARTING_PLAYERS = DebugConfig::StartingPlayers::BOTH;
#endif
std::cout << "Director: start_scene=" << DebugConfig::toString(START)
<< " starting_players=" << DebugConfig::toString(STARTING_PLAYERS) << '\n';
Log::info("Director", "start_scene={} starting_players={}", DebugConfig::toString(START), DebugConfig::toString(STARTING_PLAYERS));
switch (START) {
case DebugConfig::StartScene::LOGO:
@@ -326,25 +332,37 @@ Director::~Director() {
}
void Director::createSystemFolder() {
// Volem la config a $XDG_CONFIG_HOME/jailgames/aee_2026/ (defecte
// ~/.config/jailgames/aee_2026/). SDL_GetPrefPath apunta a XDG_DATA_HOME
// (.local/share), que és el lloc per a dades, no per a configuració.
const char* xdg = std::getenv("XDG_CONFIG_HOME");
// Carpeta de configuració per plataforma:
// - Windows: %APPDATA%\jailgames\aee_arcade\
// - macOS: ~/Library/Application Support/jailgames/aee_arcade/
// - Linux: $XDG_CONFIG_HOME/jailgames/aee_arcade/ (defecte
// ~/.config/jailgames/aee_arcade/)
// No fem servir SDL_GetPrefPath: a Linux apunta a XDG_DATA_HOME
// (~/.local/share), que és per a dades, no per a configuració.
std::string base;
#ifdef _WIN32
const char* appdata = std::getenv("APPDATA");
base = (appdata != nullptr && *appdata != '\0') ? std::string(appdata) : ".";
#elif defined(__APPLE__)
const char* home = std::getenv("HOME");
base = (home != nullptr) ? std::string(home) + "/Library/Application Support" : ".";
#else
const char* xdg = std::getenv("XDG_CONFIG_HOME");
if (xdg != nullptr && *xdg != '\0') {
base = xdg;
} else {
const char* home = std::getenv("HOME");
base = (home != nullptr) ? std::string(home) + "/.config" : ".";
}
system_folder_ = base + "/jailgames/aee_2026/";
#endif
system_folder_ = base + "/jailgames/aee_arcade/";
std::error_code error;
std::filesystem::create_directories(system_folder_, error);
// Subcarpeta per als presets de shaders. std::filesystem és tolerant si ja existeix.
std::filesystem::create_directories(system_folder_ + "shaders/", error);
if (error) {
std::cerr << "Director: no s'ha pogut crear " << system_folder_ << ": " << error.message() << '\n';
Log::error("Director", "no s'ha pogut crear {}: {}", system_folder_, error.message());
}
}
@@ -426,7 +444,7 @@ void Director::switchSceneIfRequested() {
// l'escena en aquest mateix frame — l'usuari ha cancel·lat l'estat
// de joc explícitament i no ens interessa mantenir-ne la sortida.
if (auto menu_req = ServiceMenu::get().takePendingSceneRequest(); menu_req.has_value()) {
std::cout << "Director: scene transition (menu) → " << toString(menu_req->next) << '\n';
Log::info("Director", "scene transition (menu) → {}", toString(menu_req->next));
current_scene_ = buildScene(menu_req->next, menu_req->params);
return;
}
@@ -438,10 +456,10 @@ void Director::switchSceneIfRequested() {
if (!request.has_value()) { return; }
try {
const auto& req = std::any_cast<const ExitRequest&>(request);
std::cout << "Director: scene transition → " << toString(req.next) << '\n';
Log::info("Director", "scene transition → {}", toString(req.next));
current_scene_ = buildScene(req.next, req.params);
} catch (const std::bad_any_cast&) {
std::cerr << "Director: exit_request_ amb tipus inesperat — ignorant\n";
Log::error("Director", "exit_request_ amb tipus inesperat — ignorant");
}
}
+23 -6
View File
@@ -1,6 +1,5 @@
#include "core/ui/ui_config.hpp"
#include <iostream>
#include <string>
#include <string_view>
#include <unordered_map>
@@ -9,6 +8,7 @@
#include "external/fkyaml_node.hpp"
#include "utils/defaults.hpp"
#include "utils/easing.hpp"
#include "utils/log.hpp"
namespace {
@@ -27,7 +27,7 @@ namespace {
if (!node.contains(key)) { return; }
const int VALUE = node[key].get_value<int>();
if (VALUE < 0 || VALUE > 255) {
std::cerr << "UiConfig: índex '" << key << "'=" << VALUE << " fora de rang 0..255 — ignorat\n";
Log::warn("UiConfig", "índex '{}'={} fora de rang 0..255 — ignorat", key, VALUE);
return;
}
dst = static_cast<Uint8>(VALUE);
@@ -131,6 +131,7 @@ UiConfig::UiConfig()
},
logo_scan_{
.duration_ms = Defaults::UI::Logo::Scan::DURATION_MS,
.tail_glitch_ms = Defaults::UI::Logo::Scan::TAIL_GLITCH_MS,
.band_height_px = Defaults::UI::Logo::Scan::BAND_HEIGHT_PX,
.band_alpha = Defaults::UI::Logo::Scan::BAND_ALPHA,
.band_pulse_hz = Defaults::UI::Logo::Scan::BAND_PULSE_HZ,
@@ -143,6 +144,13 @@ UiConfig::UiConfig()
.vhs_burst_amplitude_px = Defaults::UI::Logo::Scan::VHS_BURST_AMPLITUDE_PX,
.flash_ms = Defaults::UI::Logo::Scan::FLASH_MS,
},
logo_timeline_{
.black_intro_ms = Defaults::UI::Logo::Timeline::BLACK_INTRO_MS,
.hold_silent_ms = Defaults::UI::Logo::Timeline::HOLD_SILENT_MS,
.hold_with_music_ms = Defaults::UI::Logo::Timeline::HOLD_WITH_MUSIC_MS,
.fade_out_ms = Defaults::UI::Logo::Timeline::FADE_OUT_MS,
.black_outro_ms = Defaults::UI::Logo::Timeline::BLACK_OUTRO_MS,
},
menu_select_sound_(Defaults::UI::MENU_SELECT_SOUND),
menu_accept_sound_(Defaults::UI::MENU_ACCEPT_SOUND),
player_start_sound_(Defaults::UI::PLAYER_START_SOUND),
@@ -175,7 +183,7 @@ auto UiConfig::curveByName(const std::string& name) -> CurveFn {
const auto& reg = curveRegistry();
const auto IT = reg.find(name);
if (IT == reg.end()) {
std::cerr << "UiConfig: corba '" << name << "' desconeguda — s'usa linear\n";
Log::warn("UiConfig", "corba '{}' desconeguda — s'usa linear", name);
return &Easing::linear;
}
return IT->second;
@@ -184,7 +192,7 @@ auto UiConfig::curveByName(const std::string& name) -> CurveFn {
void UiConfig::load(const std::string& file_path) {
auto bytes = Resource::Helper::loadFile(file_path);
if (bytes.empty()) {
std::cerr << "UiConfig: no s'ha pogut obrir " << file_path << " — s'usaran els defaults compilats\n";
Log::warn("UiConfig", "no s'ha pogut obrir {} — s'usaran els defaults compilats", file_path);
return;
}
@@ -301,6 +309,7 @@ void UiConfig::load(const std::string& file_path) {
if (l.contains("scan")) {
const auto& s = l["scan"];
readField(s, "duration_ms", logo_scan_.duration_ms);
readField(s, "tail_glitch_ms", logo_scan_.tail_glitch_ms);
readField(s, "band_height_px", logo_scan_.band_height_px);
readField(s, "band_alpha", logo_scan_.band_alpha);
readField(s, "band_pulse_hz", logo_scan_.band_pulse_hz);
@@ -313,6 +322,14 @@ void UiConfig::load(const std::string& file_path) {
readField(s, "vhs_burst_amplitude_px", logo_scan_.vhs_burst_amplitude_px);
readField(s, "flash_ms", logo_scan_.flash_ms);
}
if (l.contains("timeline")) {
const auto& tl = l["timeline"];
readField(tl, "black_intro_ms", logo_timeline_.black_intro_ms);
readField(tl, "hold_silent_ms", logo_timeline_.hold_silent_ms);
readField(tl, "hold_with_music_ms", logo_timeline_.hold_with_music_ms);
readField(tl, "fade_out_ms", logo_timeline_.fade_out_ms);
readField(tl, "black_outro_ms", logo_timeline_.black_outro_ms);
}
}
if (yaml.contains("notifier")) {
@@ -328,8 +345,8 @@ void UiConfig::load(const std::string& file_path) {
readCurve(n, "slide_out_curve", notifier_.slide_out_curve);
}
std::cout << "UiConfig: carregat des de " << file_path << '\n';
Log::info("UiConfig", "carregat des de {}", file_path);
} catch (const std::exception& e) {
std::cerr << "UiConfig: error parsejant " << file_path << ": " << e.what() << '\n';
Log::error("UiConfig", "error parsejant {}: {}", file_path, e.what());
}
}
+22 -5
View File
@@ -5,7 +5,7 @@
#include <string>
// Configuració del tema UI (índexs de paleta, timings, fonts i flags)
// carregada des de data/ui.yaml. Cada camp s'inicialitza amb el valor
// carregada des de data/config/ui.yaml. Cada camp s'inicialitza amb el valor
// de Defaults::UI com a fallback: si el fitxer falta o li manca una
// clau, el joc s'executa amb el tema compilat.
//
@@ -21,12 +21,12 @@ class UiConfig {
};
// Signatura d'una funció d'easing: t ∈ [0, 1] → valor ∈ [0, 1].
// Permet triar corbes des de data/ui.yaml per nom (cadena) i guardar
// Permet triar corbes des de data/config/ui.yaml per nom (cadena) i guardar
// el punter a la corresponent funció de `utils/easing.hpp`.
using CurveFn = auto (*)(float) -> float;
// Geometria del Notifier (popup superior). Carregada des de
// data/ui.yaml secció `notifier:`; els valors per defecte vénen de
// data/config/ui.yaml secció `notifier:`; els valors per defecte vénen de
// Defaults::UI::Notifier. El moviment és per temps (slide_ms) i no per
// velocitat, perquè una caixa més gran no triga més a entrar/eixir.
struct Notifier {
@@ -124,9 +124,12 @@ class UiConfig {
[[nodiscard]] auto titleHeatHaze() const -> const TitleHeatHaze& { return title_heat_haze_; }
// Escena LOGO — paràmetres del scanline reveal del logo de l'editora.
// Tot s'aplica només a la fase SCAN (substitueix l'antic FADE_IN).
// `duration_ms` és la durada de la línia descendent; `tail_glitch_ms`
// és el temps post-flash en què el logo queda totalment visible
// però els bursts VHS encara hi salten (glitch tail).
struct LogoScan {
int duration_ms;
int tail_glitch_ms;
int band_height_px;
int band_alpha;
float band_pulse_hz;
@@ -142,6 +145,19 @@ class UiConfig {
[[nodiscard]] auto logoScan() const -> const LogoScan& { return logo_scan_; }
// Timeline lineal de la LogoScene. Cada camp és la durada (ms) d'una
// fase. La SCAN no apareix aquí perquè la marca el propi
// `LogoScan` (duration_ms + tail_glitch_ms + flash_ms).
struct LogoTimeline {
int black_intro_ms;
int hold_silent_ms;
int hold_with_music_ms;
int fade_out_ms;
int black_outro_ms;
};
[[nodiscard]] auto logoTimeline() const -> const LogoTimeline& { return logo_timeline_; }
// Corbes d'easing triades al YAML. Si el nom no és conegut, s'usa
// Easing::linear com a fallback.
[[nodiscard]] auto pauseEnterCurve() const -> CurveFn { return pause_enter_curve_; }
@@ -154,7 +170,7 @@ class UiConfig {
[[nodiscard]] static auto curveByName(const std::string& name) -> CurveFn;
// Sons del menú de servei. Claus de Resource::Cache::loadSound (els
// fitxers viuen a `data/sounds/`).
// fitxers viuen a `data/audio/sounds/`).
[[nodiscard]] auto menuSelectSound() const -> const std::string& { return menu_select_sound_; }
[[nodiscard]] auto menuAcceptSound() const -> const std::string& { return menu_accept_sound_; }
[[nodiscard]] auto playerStartSound() const -> const std::string& { return player_start_sound_; }
@@ -229,6 +245,7 @@ class UiConfig {
TitleIntro title_intro_;
TitleHeatHaze title_heat_haze_;
LogoScan logo_scan_;
LogoTimeline logo_timeline_;
std::string menu_select_sound_;
std::string menu_accept_sound_;
+10 -10
View File
@@ -3,11 +3,11 @@
#include <exception>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include "external/fkyaml_node.hpp"
#include "utils/log.hpp"
namespace DebugConfig {
@@ -53,12 +53,12 @@ namespace DebugConfig {
if (!yaml.contains("start_scene")) { return StartScene::GAME; }
const auto RAW = yaml["start_scene"].get_value<std::string>();
if (!isKnown(RAW)) {
std::cerr << "debug.yaml: start_scene \"" << RAW << "\" invàlid, arrancant a GAME\n";
Log::warn("debug.yaml", "start_scene \"{}\" invàlid, arrancant a GAME", RAW);
return StartScene::GAME;
}
return fromName(RAW);
} catch (const std::exception& e) {
std::cerr << "debug.yaml: error parsejant: " << e.what() << " — arrancant a GAME\n";
Log::error("debug.yaml", "error parsejant: {} — arrancant a GAME", e.what());
return StartScene::GAME;
}
}
@@ -72,7 +72,7 @@ namespace DebugConfig {
}
const auto CONTENT = readFile(file_path);
if (CONTENT.empty()) {
std::cerr << "debug.yaml: fitxer buit a " << file_path << ", arrancant a GAME\n";
Log::warn("debug.yaml", "fitxer buit a {}, arrancant a GAME", file_path);
return StartScene::GAME;
}
return parseStartScene(CONTENT);
@@ -85,12 +85,12 @@ namespace DebugConfig {
if (!yaml.contains("starting_players")) { return StartingPlayers::BOTH; }
const auto RAW = yaml["starting_players"].get_value<std::string>();
if (!isKnownPlayers(RAW)) {
std::cerr << "debug.yaml: starting_players \"" << RAW << "\" invàlid, arrancant amb BOTH\n";
Log::warn("debug.yaml", "starting_players \"{}\" invàlid, arrancant amb BOTH", RAW);
return StartingPlayers::BOTH;
}
return playersFromName(RAW);
} catch (const std::exception& e) {
std::cerr << "debug.yaml: error parsejant starting_players: " << e.what() << " — arrancant amb BOTH\n";
Log::error("debug.yaml", "error parsejant starting_players: {} — arrancant amb BOTH", e.what());
return StartingPlayers::BOTH;
}
}
@@ -110,7 +110,7 @@ namespace DebugConfig {
if (!yaml.contains("starting_level")) { return std::nullopt; }
const auto& sl = yaml["starting_level"];
if (!sl.contains("pyramid") || !sl.contains("room")) {
std::cerr << "debug.yaml: starting_level necessita els camps `pyramid` i `room` — ignorat\n";
Log::warn("debug.yaml", "starting_level necessita els camps `pyramid` i `room` — ignorat");
return std::nullopt;
}
return StartingLevel{
@@ -118,7 +118,7 @@ namespace DebugConfig {
.room = sl["room"].get_value<int>(),
};
} catch (const std::exception& e) {
std::cerr << "debug.yaml: error parsejant starting_level: " << e.what() << " — ignorat\n";
Log::error("debug.yaml", "error parsejant starting_level: {} — ignorat", e.what());
return std::nullopt;
}
}
@@ -162,7 +162,7 @@ namespace DebugConfig {
if (std::filesystem::exists(file_path, error)) { return; }
std::ofstream file(file_path);
if (!file) {
std::cerr << "debug.yaml: no s'ha pogut crear " << file_path << '\n';
Log::error("debug.yaml", "no s'ha pogut crear {}", file_path);
return;
}
file << "# debug.yaml — ajust personal de dev (no versionat).\n";
@@ -173,7 +173,7 @@ namespace DebugConfig {
file << "# clau, comença a la primera (P1-R1).\n";
file << "start_scene: GAME\n";
file << "starting_players: BOTH\n";
std::cout << "debug.yaml: creat a " << file_path << " amb start_scene=GAME starting_players=BOTH\n";
Log::info("debug.yaml", "creat a {} amb start_scene=GAME starting_players=BOTH", file_path);
}
} // namespace DebugConfig
+4 -3
View File
@@ -5,9 +5,10 @@
#include <string>
// Configuració personal de desenvolupament, carregada de `debug.yaml` dins la
// carpeta de sistema (`$XDG_CONFIG_HOME/jailgames/aee_2026/debug.yaml`). No es
// versiona: cada dev el crea a la seua màquina. Si no existeix, s'aplica el
// valor per defecte (GAME) sense warning de nivell error.
// carpeta de sistema del joc (vegeu `Director::createSystemFolder` per la
// ubicació real per plataforma). No es versiona: cada dev el crea a la seua
// màquina. Si no existeix, s'aplica el valor per defecte (GAME) sense warning
// de nivell error.
namespace DebugConfig {
enum class StartScene : std::uint8_t { LOGO,
+1 -1
View File
@@ -234,7 +234,7 @@ void Editor::ensureEditableEntities() {
// llança si no troba — provem primer i, en cas de miss, carreguem.
const auto REL = sp["path"].get_value<std::string>();
const std::string KEY = std::filesystem::path(REL).filename().string();
std::string full = "data/entities/";
std::string full = Defaults::Paths::ENTITIES_DIR;
full += name;
full += '/';
full += REL;
+5 -5
View File
@@ -3,13 +3,13 @@
#ifdef _DEBUG
#include <cstdint>
#include <iostream>
#include <stdexcept>
#include <string>
#include "core/resources/resource_helper.hpp"
#include "external/fkyaml_node.hpp"
#include "utils/defaults.hpp"
#include "utils/log.hpp"
namespace {
@@ -34,7 +34,7 @@ namespace {
if (!s.empty() && s.front() == '#') { s.erase(0, 1); }
dst = static_cast<std::uint32_t>(std::stoul(s, nullptr, 0));
} catch (const std::exception& e) {
std::cerr << "EditorConfig: color '" << key << "' invàlid (" << e.what() << ") — ignorat\n";
Log::warn("EditorConfig", "color '{}' invàlid ({}) — ignorat", key, e.what());
}
}
@@ -56,7 +56,7 @@ auto EditorConfig::get() -> EditorConfig& {
void EditorConfig::load(const std::string& file_path) {
auto bytes = Resource::Helper::loadFile(file_path);
if (bytes.empty()) {
std::cerr << "EditorConfig: no s'ha pogut obrir " << file_path << " — s'usaran els defaults compilats\n";
Log::warn("EditorConfig", "no s'ha pogut obrir {} — s'usaran els defaults compilats", file_path);
return;
}
try {
@@ -76,9 +76,9 @@ void EditorConfig::load(const std::string& file_path) {
readArgb(c, "picker_hover", picker_hover_argb_);
}
std::cout << "EditorConfig: carregat des de " << file_path << '\n';
Log::info("EditorConfig", "carregat des de {}", file_path);
} catch (const std::exception& e) {
std::cerr << "EditorConfig: error parsejant " << file_path << ": " << e.what() << '\n';
Log::error("EditorConfig", "error parsejant {}: {}", file_path, e.what());
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
#include <string>
// Configuració visual de l'editor de tilemap (Debug-only). Carregada des
// de data/editor/editor.yaml; les claus absents cauen als valors compilats
// de data/config/editor.yaml; les claus absents cauen als valors compilats
// a Defaults::Editor. Mateix patró que UiConfig (Meyers singleton + load).
class EditorConfig {
public:
+14
View File
@@ -8,11 +8,14 @@
#include <string>
#include <utility>
#include "core/audio/audio.hpp"
#include "core/resources/resource_cache.hpp"
#include "game/entities/entity_factory.hpp"
#include "game/entities/entity_manager.hpp"
#include "game/entities/event_bus.hpp"
#include "game/entities/player.hpp"
#include "game/map/map.hpp"
#include "utils/defaults.hpp"
namespace {
@@ -133,6 +136,14 @@ Mummy::Mummy(const fkyaml::node& yaml)
if (h.contains("whippable")) {
whippable_ = h["whippable"].get_value<bool>();
}
// So opcional: el basename val tant de clau al Resource::Cache com de
// path (`Defaults::Paths::AUDIO_SOUNDS_DIR + key`). Mateix patró que
// els sons del Player. Si el camp manca, la clau queda buida i
// `enterHurt` no dispara playSound.
if (h.contains("sound")) {
hurt_config_.sound_key = h["sound"].get_value<std::string>();
Resource::Cache::get().loadSound(hurt_config_.sound_key, std::string{Defaults::Paths::AUDIO_SOUNDS_DIR} + hurt_config_.sound_key);
}
}
}
@@ -511,6 +522,9 @@ void Mummy::enterHurt(Direction flee_dir) {
state_ = State::HURT;
hurt_remaining_s_ = hurt_config_.duration_s;
hurt_speed_ = (hurt_config_.speed_px_per_s > 0.0F) ? hurt_config_.speed_px_per_s : speed_;
if (!hurt_config_.sound_key.empty() && Audio::get() != nullptr) {
Audio::get()->playSound(hurt_config_.sound_key, Audio::Group::GAME);
}
const Direction CHOSEN = chooseHurtDirection(flee_dir);
if (CHOSEN == Direction::NONE) {
+2
View File
@@ -3,6 +3,7 @@
#include <climits>
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
#include "external/fkyaml_node.hpp"
@@ -35,6 +36,7 @@ class Mummy : public DynamicHazard {
struct HurtConfig {
float duration_s{0.0F};
float speed_px_per_s{0.0F};
std::string sound_key; // Buit = sense so. El fitxer viu a Defaults::Paths::AUDIO_SOUNDS_DIR.
};
explicit Mummy(const fkyaml::node& yaml);
+2 -1
View File
@@ -6,6 +6,7 @@
#include "core/resources/resource_helper.hpp"
#include "game/entities/entity.hpp"
#include "game/entities/entity_factory.hpp"
#include "utils/defaults.hpp"
namespace EntityLoader {
@@ -20,7 +21,7 @@ namespace EntityLoader {
}
auto templatePath(const std::string& name) -> std::string {
return "data/entities/" + name + "/" + name + ".yaml";
return std::string{Defaults::Paths::ENTITIES_DIR} + name + "/" + name + ".yaml";
}
} // namespace
@@ -5,7 +5,6 @@
// stand-alone) enllacen `entity_manager.cpp` sense arrossegar aquest fitxer.
#include <filesystem>
#include <iostream>
#include <memory>
#include <string>
#include <utility>
@@ -21,6 +20,8 @@
#include "game/entities/entity_manager.hpp"
#include "game/entities/player.hpp"
#include "game/map/map.hpp"
#include "utils/defaults.hpp"
#include "utils/log.hpp"
// Render centralitzat al manager per confinar la dependència cap a
// `Surface`. Les entitats exposen `getSurface()` + `getPixelRect()` i el
@@ -124,7 +125,7 @@ namespace {
if (s.contains("path")) {
const auto REL = s["path"].get_value<std::string>();
info.key = std::filesystem::path(REL).filename().string();
info.full_path = "data/entities/" + type + "/" + REL;
info.full_path = std::string{Defaults::Paths::ENTITIES_DIR} + type + "/" + REL;
info.valid = true;
}
if (s.contains("size")) {
@@ -236,12 +237,12 @@ namespace {
try {
tmpl = EntityLoader::loadTemplate(type);
} catch (const std::exception& e) {
std::cerr << "EntityManager: " << e.what() << '\n';
Log::error("EntityManager", "{}", e.what());
return nullptr;
}
auto entity = EntityFactory::instance().create(type, tmpl);
if (entity == nullptr) {
std::cerr << "EntityManager: tipus desconegut '" << type << "'\n";
Log::error("EntityManager", "tipus desconegut '{}'", type);
return nullptr;
}
sprite_out = readSpriteInfo(tmpl, type);
@@ -266,7 +267,7 @@ namespace {
namespace {
auto placeSarcophagus(EntityManager& manager, std::unique_ptr<Entity> entity, const SpriteInfo& sprite, const fkyaml::node& spawn) -> bool {
if (!spawn.contains("sarc_position")) {
std::cerr << "EntityManager: sarcophagus sense sarc_position\n";
Log::error("EntityManager", "sarcophagus sense sarc_position");
return false;
}
const auto& sp = spawn["sarc_position"];
@@ -303,7 +304,7 @@ namespace {
// un `Sarcophagus`. Usant el retorn de `release` al ctor del nou
// `unique_ptr` evitem el lint `bugprone-unused-return-value`.
if (dynamic_cast<Sarcophagus*>(entity.get()) == nullptr) {
std::cerr << "EntityManager: la factory no ha creat un Sarcophagus\n";
Log::error("EntityManager", "la factory no ha creat un Sarcophagus");
return false;
}
std::unique_ptr<Sarcophagus> sarc(static_cast<Sarcophagus*>(entity.release()));
@@ -322,7 +323,7 @@ namespace {
// queden tapats — comportament intencional.
auto placeDoor(EntityManager& manager, std::unique_ptr<Entity> entity, const SpriteInfo& sprite) -> bool {
if (Map::door_col <= 0 || Map::tile_size <= 0) {
std::cerr << "EntityManager: door requereix `geometry.door_col` definit al mapa\n";
Log::error("EntityManager", "door requereix `geometry.door_col` definit al mapa");
return false;
}
const float CENTER_X = static_cast<float>((Map::door_col * Map::tile_size) + Map::tilemap_origin_x) + (static_cast<float>(Map::tile_size) / 2.0F);
@@ -342,7 +343,7 @@ namespace {
auto placeGeneric(EntityManager& manager, std::unique_ptr<Entity> entity, const std::string& type, const SpriteInfo& sprite, const std::string& anchor, const fkyaml::node& spawn) -> bool {
if (!spawn.contains("position")) {
std::cerr << "EntityManager: " << type << " sense position\n";
Log::error("EntityManager", "{} sense position", type);
return false;
}
const auto& p = spawn["position"];
@@ -443,10 +444,8 @@ void EntityManager::loadFromLevel(const std::string& level_yaml_path) {
++decal_broadcast;
}
if (decal_broadcast > 0) {
std::cout << "EntityManager: " << decal_broadcast
<< " tile_painted broadcast inicial (decals de sòl)\n";
Log::info("EntityManager", "{} tile_painted broadcast inicial (decals de sòl)", decal_broadcast);
}
std::cout << "EntityManager: " << LOADED << " entitats carregades des de "
<< level_yaml_path << '\n';
Log::info("EntityManager", "{} entitats carregades des de {}", LOADED, level_yaml_path);
}
+10 -9
View File
@@ -179,16 +179,16 @@ void Player::readJumpBlock(const fkyaml::node& yaml) {
if (j.contains("gravity")) { jump_config.gravity_px_per_s2 = j["gravity"].get_value<float>(); }
if (j.contains("landing_stun_ms")) { jump_config.landing_stun_ms = j["landing_stun_ms"].get_value<int>(); }
if (j.contains("shadow_y_offset")) { jump_config.shadow_y_offset = j["shadow_y_offset"].get_value<int>(); }
// Sons opcionals: el nom del fitxer (relatiu a data/sounds/) val tant
// de clau al Resource::Cache com de basename per a la càrrega. Si el
// camp manca, la clau queda buida i `playSound` no es crida.
// Sons opcionals: el nom del fitxer (relatiu a Defaults::Paths::AUDIO_SOUNDS_DIR)
// val tant de clau al Resource::Cache com de basename per a la càrrega.
// Si el camp manca, la clau queda buida i `playSound` no es crida.
if (j.contains("sound")) {
jump_config.sound_key = j["sound"].get_value<std::string>();
Resource::Cache::get().loadSound(jump_config.sound_key, "data/sounds/" + jump_config.sound_key);
Resource::Cache::get().loadSound(jump_config.sound_key, std::string{Defaults::Paths::AUDIO_SOUNDS_DIR} + jump_config.sound_key);
}
if (j.contains("land_sound")) {
jump_config.land_sound_key = j["land_sound"].get_value<std::string>();
Resource::Cache::get().loadSound(jump_config.land_sound_key, "data/sounds/" + jump_config.land_sound_key);
Resource::Cache::get().loadSound(jump_config.land_sound_key, std::string{Defaults::Paths::AUDIO_SOUNDS_DIR} + jump_config.land_sound_key);
}
}
@@ -201,7 +201,7 @@ void Player::readHurtBlock(const fkyaml::node& yaml) {
// So d'entrada a HURT — mateix patró que `jump.sound` i `whip.sound`.
if (h.contains("sound")) {
hurt_config.sound_key = h["sound"].get_value<std::string>();
Resource::Cache::get().loadSound(hurt_config.sound_key, "data/sounds/" + hurt_config.sound_key);
Resource::Cache::get().loadSound(hurt_config.sound_key, std::string{Defaults::Paths::AUDIO_SOUNDS_DIR} + hurt_config.sound_key);
}
}
@@ -220,10 +220,11 @@ void Player::readImmunityBlock(const fkyaml::node& yaml) {
if (im.contains("duration_ms")) { immunity_config.duration_ms = im["duration_ms"].get_value<float>(); }
if (im.contains("blink_period_ms")) { immunity_config.blink_period_ms = im["blink_period_ms"].get_value<int>(); }
// Paleta del blink: el basename val tant de clau al Resource::Cache
// com de path (`data/player/<key>.pal`). Mateix patró que els sons.
// com de path (`Defaults::Paths::PLAYER_DIR + key + .pal`). Mateix patró
// que els sons.
if (im.contains("palette")) {
immunity_config.palette_key = im["palette"].get_value<std::string>();
Resource::Cache::get().loadPalette(immunity_config.palette_key, "data/player/" + immunity_config.palette_key + ".pal");
Resource::Cache::get().loadPalette(immunity_config.palette_key, std::string{Defaults::Paths::PLAYER_DIR} + immunity_config.palette_key + ".pal");
}
}
@@ -239,7 +240,7 @@ void Player::readWhipBlock(const fkyaml::node& yaml) {
// clau al cache i de path. Si manca, queda buit i no es dispara.
if (w.contains("sound")) {
whip_config.sound_key = w["sound"].get_value<std::string>();
Resource::Cache::get().loadSound(whip_config.sound_key, "data/sounds/" + whip_config.sound_key);
Resource::Cache::get().loadSound(whip_config.sound_key, std::string{Defaults::Paths::AUDIO_SOUNDS_DIR} + whip_config.sound_key);
}
}
+7 -7
View File
@@ -91,7 +91,7 @@ class Player : public AnimatedSprite {
DYING,
};
// Geometria carregada des del YAML del player (p.ex. `data/player/sam.yaml`).
// Geometria carregada des del YAML del player (p.ex. `data/entities/player/sam.yaml`).
// `sprite_*` són
// la mida del frame (sprite 12×24 amb perspectiva falsa: visualment, els
// 12 píxels superiors són el cap i els 12 inferiors la base).
@@ -362,7 +362,7 @@ class Player : public AnimatedSprite {
// al respawn post-mort: mateixa entrada cinemàtica.
void spawnFromAir(float x, float y);
// Arma un preset de reverb (catàleg a `data/sounds.yaml`) per al
// Arma un preset de reverb (catàleg a `data/config/sounds.yaml`) per al
// pròxim aterratge d'aquest jugador. Pensat per a la cinemàtica
// d'INTRO de sala: GameScene crida aquesta funció just després de
// `spawnFromAir`, i el primer LAND consumeix el preset (queda buit
@@ -529,7 +529,7 @@ class Player : public AnimatedSprite {
// gestiona `intended_vx_/vy_` escalats pel resolver.
bool being_pushed_{false};
// Preset de reverb (catàleg de `data/sounds.yaml`) que el pròxim
// Preset de reverb (catàleg de `data/config/sounds.yaml`) que el pròxim
// LAND ha de fer servir; buit = so sec. L'arma `armNextLandReverb`
// (cridada per GameScene al spawn aeri d'INTRO) i el consumeix
// `integrateJump` quan el jugador toca terra. Cap altre punt
@@ -636,7 +636,7 @@ class Player : public AnimatedSprite {
struct HurtConfig {
float initial_speed_px_per_s{0.0F}; // Velocitat amb què surt projectat (px/s).
int brake_time_ms{0}; // Temps en que la velocitat decau a 0.
// Fitxer .wav reproduït en entrar a HURT (relatiu a `data/sounds/`).
// Fitxer .wav reproduït en entrar a HURT (relatiu a `data/audio/sounds/`).
// Mateixa convenció que la resta de sons del jugador: el basename val
// de clau al Resource::Cache i de path. Buit o absent = entrada
// silenciosa.
@@ -660,7 +660,7 @@ class Player : public AnimatedSprite {
int cooldown_ms{0};
int damage{0};
// Fitxer .wav reproduït al moment de disparar el swing (relatiu a
// `data/sounds/`). Convenció: el basename val tant de clau al
// `data/audio/sounds/`). Convenció: el basename val tant de clau al
// Resource::Cache com de path. Buit o absent = swing silenciós.
std::string sound_key;
};
@@ -697,7 +697,7 @@ class Player : public AnimatedSprite {
float duration_ms{0.0F};
int blink_period_ms{0};
// Clau al Resource::Cache de la paleta plana usada al blink (basename
// del .pal a `data/player/`). Buida = no hi ha alternança visual,
// del .pal a `data/entities/player/`). Buida = no hi ha alternança visual,
// només la finestra lògica d'immunitat (que sense feedback visual
// pot ser confusa, però no és incorrecte).
std::string palette_key;
@@ -706,7 +706,7 @@ class Player : public AnimatedSprite {
// Llig el bloc `immunity:` del YAML i n'omple `immunity_config`. Si
// hi ha `palette`, registra la paleta corresponent al Resource::Cache
// (path: `data/player/<palette_key>.pal`). Mateix patró que els altres
// (path: `data/entities/player/<palette_key>.pal`). Mateix patró que els altres
// readers — `loadFrom` el crida al final.
static void readImmunityBlock(const fkyaml::node& yaml);
+7 -13
View File
@@ -1,13 +1,13 @@
#include "game/levels/level_manager.hpp"
#include <algorithm>
#include <iostream>
#include <stdexcept>
#include <utility>
#include "core/resources/resource_helper.hpp"
#include "external/fkyaml_node.hpp"
#include "utils/defaults.hpp"
#include "utils/log.hpp"
namespace Levels {
@@ -46,8 +46,7 @@ namespace Levels {
throw std::runtime_error("LevelManager: la seqüència de " + PATH + " és buida");
}
index_ = 0;
std::cout << "LevelManager: " << entries_.size() << " sales carregades de "
<< PATH << " (començant a " << currentLabel() << ")\n";
Log::info("LevelManager", "{} sales carregades de {} (començant a {})", entries_.size(), PATH, currentLabel());
}
auto LevelManager::currentPath() const -> const std::string& {
@@ -67,22 +66,20 @@ namespace Levels {
auto LevelManager::advance() -> bool {
if (index_ + 1 >= count()) { return false; }
++index_;
std::cout << "LevelManager: avança a " << currentLabel()
<< " (" << currentPath() << ")\n";
Log::info("LevelManager", "avança a {} ({})", currentLabel(), currentPath());
return true;
}
void LevelManager::restart() {
index_ = 0;
std::cout << "LevelManager: restart a " << currentLabel() << '\n';
Log::info("LevelManager", "restart a {}", currentLabel());
}
void LevelManager::setIndex(int new_index) {
const int CLAMPED = std::clamp(new_index, 0, count() - 1);
if (CLAMPED != index_) {
index_ = CLAMPED;
std::cout << "LevelManager: setIndex(" << new_index << ") → "
<< currentLabel() << " (" << currentPath() << ")\n";
Log::info("LevelManager", "setIndex({}) → {} ({})", new_index, currentLabel(), currentPath());
}
}
@@ -90,14 +87,11 @@ namespace Levels {
for (std::size_t i = 0; i < entries_.size(); ++i) {
if (entries_[i].pyramid == pyramid && entries_[i].room == room) {
index_ = static_cast<int>(i);
std::cout << "LevelManager: starting_point P" << pyramid << "-R" << room
<< " trobat a l'índex " << i << " (" << currentPath() << ")\n";
Log::info("LevelManager", "starting_point P{}-R{} trobat a l'índex {} ({})", pyramid, room, i, currentPath());
return true;
}
}
std::cerr << "LevelManager: starting_point P" << pyramid << "-R" << room
<< " no trobat a levels.yaml — es manté l'índex actual ("
<< currentLabel() << ")\n";
Log::warn("LevelManager", "starting_point P{}-R{} no trobat a levels.yaml — es manté l'índex actual ({})", pyramid, room, currentLabel());
return false;
}
+2 -6
View File
@@ -1,11 +1,11 @@
#include "game/map/map.hpp"
#include <cmath>
#include <iostream>
#include <stdexcept>
#include "core/resources/resource_helper.hpp"
#include "external/fkyaml_node.hpp"
#include "utils/log.hpp"
namespace {
@@ -90,11 +90,7 @@ namespace Map {
// canviarà el flag a true quan transiti a OPEN.
door_open = false;
std::cout << "Map: geometria " << grid_cols << "x" << grid_rows
<< " sarcs " << sarc_w << "x" << sarc_h
<< " pitch " << pitch_x << "x" << pitch_y
<< " door_col " << door_col
<< " des de " << yaml_path << '\n';
Log::info("Map", "geometria {}x{} sarcs {}x{} pitch {}x{} door_col {} des de {}", grid_cols, grid_rows, sarc_w, sarc_h, pitch_x, pitch_y, door_col, yaml_path);
}
auto getSarcRect(int col, int row) -> SDL_FRect {
+2 -4
View File
@@ -3,7 +3,6 @@
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <variant>
#include "core/rendering/screen.hpp"
@@ -11,6 +10,7 @@
#include "core/resources/resource_cache.hpp"
#include "game/map/map.hpp"
#include "game/map/tilemap.hpp"
#include "utils/log.hpp"
// CONTRACTE BUS DE PINTAT
// -----------------------
@@ -89,9 +89,7 @@ PaintLayer::PaintLayer(const Tilemap& tilemap, EventBus* bus)
});
}
std::cout << "PaintLayer: " << cols_ << "x" << rows_ << " tiles ("
<< width_px_ << "x" << height_px_ << " px), tileset "
<< tileset_path_ << '\n';
Log::info("PaintLayer", "{}x{} tiles ({}x{} px), tileset {}", cols_, rows_, width_px_, height_px_, tileset_path_);
}
PaintLayer::~PaintLayer() {
+2 -3
View File
@@ -3,7 +3,6 @@
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <stdexcept>
#include <vector>
@@ -12,6 +11,7 @@
#include "core/resources/resource_cache.hpp"
#include "core/resources/resource_helper.hpp"
#include "external/fkyaml_node.hpp"
#include "utils/log.hpp"
#ifdef _DEBUG
#include "game/map/tilemap_writer.hpp"
@@ -97,8 +97,7 @@ Tilemap::Tilemap(const std::string& yaml_path) {
}
}
std::cout << "Tilemap: " << cols_ << "x" << rows_ << " tiles ("
<< width_px_ << "x" << height_px_ << " px) des de " << yaml_path << '\n';
Log::info("Tilemap", "{}x{} tiles ({}x{} px) des de {}", cols_, rows_, width_px_, height_px_, yaml_path);
}
#ifdef _DEBUG
+14 -15
View File
@@ -3,7 +3,6 @@
#include <algorithm>
#include <array>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <string_view>
@@ -12,6 +11,7 @@
#include "core/input/gamepad_manager.hpp"
#include "external/fkyaml_node.hpp"
#include "utils/defines.hpp"
#include "utils/log.hpp"
namespace {
@@ -104,7 +104,7 @@ namespace {
std::ofstream dst(path + ".bak", std::ios::binary);
if (!dst) { return; }
dst << src.rdbuf();
std::cout << "Options: còpia de seguretat a " << path << ".bak\n";
Log::info("Options", "còpia de seguretat a {}.bak", path);
}
void parseWindow(const fkyaml::node& yaml) {
@@ -463,7 +463,7 @@ namespace {
try {
auto yaml = fkyaml::node::deserialize(CONTENT);
if (!yaml.contains("presets") || !yaml["presets"].is_sequence()) {
std::cout << "Options: " << kind_label << " manca `presets:`; reescrivint amb defaults.\n";
Log::warn("Options", "{} manca `presets:`; reescrivint amb defaults.", kind_label);
backupFile(path);
presets = defaults_fn();
return save_fn();
@@ -475,15 +475,15 @@ namespace {
presets.push_back(preset);
}
if (presets.empty()) {
std::cout << "Options: " << kind_label << " sense presets; reescrivint amb defaults.\n";
Log::warn("Options", "{} sense presets; reescrivint amb defaults.", kind_label);
backupFile(path);
presets = defaults_fn();
return save_fn();
}
return true;
} catch (const std::exception& e) {
std::cout << "Options: error parsejant " << kind_label << ": " << e.what() << '\n';
std::cout << "Options: reescrivint el fitxer amb presets per defecte.\n";
Log::error("Options", "error parsejant {}: {}", kind_label, e.what());
Log::info("Options", "reescrivint el fitxer amb presets per defecte.");
backupFile(path);
presets = defaults_fn();
return save_fn();
@@ -655,8 +655,7 @@ namespace Options {
// amb els defaults de la versió nova. El fitxer original queda
// a `<path>.bak` per si l'usuari volia preservar tuning manual.
if (file_version != Texts::VERSION) {
std::cout << "Options: config.yaml versió \"" << file_version
<< "\" != \"" << Texts::VERSION << "\"; reescrivint amb defaults.\n";
Log::warn("Options", "config.yaml versió \"{}\" != \"{}\"; reescrivint amb defaults.", file_version, Texts::VERSION);
backupFile(g_config_file_path);
return saveToFile();
}
@@ -679,8 +678,8 @@ namespace Options {
if (CHANGED) { saveToFile(); }
return true;
} catch (const std::exception& e) {
std::cout << "Options: error parsejant config.yaml: " << e.what() << '\n';
std::cout << "Options: reescrivint el fitxer amb defaults.\n";
Log::error("Options", "error parsejant config.yaml: {}", e.what());
Log::info("Options", "reescrivint el fitxer amb defaults.");
backupFile(g_config_file_path);
return saveToFile();
}
@@ -692,7 +691,7 @@ namespace Options {
g_config_version = Texts::VERSION;
std::ostringstream out;
out << "# Configuració d'AEE 2026. Generat automàticament; editable a mà.\n";
out << "# Configuració d'AEE ARCADE. Generat automàticament; editable a mà.\n";
out << "version: \"" << g_config_version << "\"\n";
out << "locale: \"" << g_locale << "\"\n\n";
@@ -780,7 +779,7 @@ namespace Options {
g_postfx_file_path,
g_postfx_presets,
&writePostFXPreset,
"Presets PostFX per a AEE 2026. Editable a mà.",
"Presets PostFX per a AEE ARCADE. Editable a mà.",
"Els noms han de coincidir amb config.yaml > video.shader.current_postfx_preset.");
}
@@ -803,7 +802,7 @@ namespace Options {
g_crtpi_file_path,
g_crtpi_presets,
&writeCrtPiPreset,
"Presets CRT-Pi per a AEE 2026. Editable a mà.",
"Presets CRT-Pi per a AEE ARCADE. Editable a mà.",
"Els noms han de coincidir amb config.yaml > video.shader.current_crtpi_preset.");
}
@@ -843,7 +842,7 @@ namespace Options {
const auto IT = std::ranges::find_if(g_crtpi_presets,
[](const CrtPiPreset& preset) { return preset.name == g_video.shader.current_crtpi_preset; });
if (IT == g_crtpi_presets.end()) {
std::cout << "Options: preset CrtPi \"" << g_video.shader.current_crtpi_preset << "\" no trobat; aplicant defaults.\n";
Log::warn("Options", "preset CrtPi \"{}\" no trobat; aplicant defaults.", g_video.shader.current_crtpi_preset);
return {};
}
return {
@@ -871,7 +870,7 @@ namespace Options {
const auto IT = std::ranges::find_if(g_postfx_presets,
[](const PostFXPreset& preset) { return preset.name == g_video.shader.current_postfx_preset; });
if (IT == g_postfx_presets.end()) {
std::cout << "Options: preset PostFX \"" << g_video.shader.current_postfx_preset << "\" no trobat; aplicant passthrough.\n";
Log::warn("Options", "preset PostFX \"{}\" no trobat; aplicant passthrough.", g_video.shader.current_postfx_preset);
return {};
}
return {
+30 -34
View File
@@ -7,7 +7,6 @@
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <iostream>
#include <iterator>
#include <utility>
#include <variant>
@@ -41,6 +40,7 @@
#include "utils/defaults.hpp"
#include "utils/defines.hpp"
#include "utils/easing.hpp"
#include "utils/log.hpp"
namespace {
@@ -229,10 +229,10 @@ auto GameScene::defaultConfig() -> Config {
// concret de sala — només cap a on apunta la progressió ara mateix.
return {
.map_yaml = Levels::LevelManager::get().currentPath(),
.player_sprite = Defaults::Pyramid::PLAYER_SPRITE,
.player_yaml = Defaults::Pyramid::PLAYER_YAML,
.player2_sprite = Defaults::Pyramid::PLAYER_SPRITE_P2,
.player2_yaml = Defaults::Pyramid::PLAYER_YAML_P2,
.player_sprite = Defaults::Pyramid::PLAYER_SAM_GIF,
.player_yaml = Defaults::Pyramid::PLAYER_SAM_YAML,
.player2_sprite = Defaults::Pyramid::PLAYER_MORCUS_GIF,
.player2_yaml = Defaults::Pyramid::PLAYER_MORCUS_YAML,
.music_track = Defaults::Pyramid::MUSIC_TRACK,
.starting_players = DebugConfig::StartingPlayers::BOTH,
};
@@ -290,13 +290,12 @@ GameScene::GameScene(Config config)
// re-emissions es filtren per estat per no re-entrar mai des d'una
// transició ja en marxa.
entity_manager_.getBus().subscribe("level_complete", [this](const Event& ev) {
std::cout << "[LEVEL COMPLETE] player_id=" << ev.player_id << '\n';
Log::info("LEVEL COMPLETE", "player_id={}", ev.player_id);
if (state_ == RoomFsm::State::PLAYING) { setState(RoomFsm::State::ROOM_OUTRO); }
});
entity_manager_.getBus().subscribe("player_hit", [](const Event& ev) {
const auto* dmg = std::get_if<int>(&ev.payload);
std::cout << "[PLAYER HIT] player_id=" << ev.player_id
<< " dmg=" << (dmg != nullptr ? *dmg : 0) << '\n';
Log::info("PLAYER HIT", "player_id={} dmg={}", ev.player_id, dmg != nullptr ? *dmg : 0);
});
// `player_killed` el publica un hazard letal (mòmia amb
// `kills_outright`) després del `player_hit`. Aquí resolem el
@@ -312,7 +311,7 @@ GameScene::GameScene(Config config)
// També ens subscrivim a `player_jumped` i `player_landed` per
// spawnejar el polvet de despegue / aterratge a la posició dels
// peus que el Player ens dóna al payload.
Resource::Cache::get().loadSurface("dust.gif", "data/entities/dust/dust.gif");
Resource::Cache::get().loadSurface("dust.gif", Defaults::Paths::DUST_GIF);
entity_manager_.getBus().subscribe("player_jumped", [this](const Event& ev) {
const auto* pos = std::get_if<WorldPos>(&ev.payload);
if (pos != nullptr) { spawnDust(pos->x, pos->y, false); }
@@ -325,10 +324,10 @@ GameScene::GameScene(Config config)
// Whip: precarreguem les 4 surfaces (una per cardinal) abans del
// primer spawn perquè el Player no facte disc al primer atac.
// Subscriu `whip_spawn` per crear el `WhipAttack` corresponent.
Resource::Cache::get().loadSurface("whip_east.gif", "data/entities/whip_attack/whip_east.gif");
Resource::Cache::get().loadSurface("whip_west.gif", "data/entities/whip_attack/whip_west.gif");
Resource::Cache::get().loadSurface("whip_north.gif", "data/entities/whip_attack/whip_north.gif");
Resource::Cache::get().loadSurface("whip_south.gif", "data/entities/whip_attack/whip_south.gif");
Resource::Cache::get().loadSurface("whip_east.gif", Defaults::Paths::WHIP_EAST_GIF);
Resource::Cache::get().loadSurface("whip_west.gif", Defaults::Paths::WHIP_WEST_GIF);
Resource::Cache::get().loadSurface("whip_north.gif", Defaults::Paths::WHIP_NORTH_GIF);
Resource::Cache::get().loadSurface("whip_south.gif", Defaults::Paths::WHIP_SOUTH_GIF);
entity_manager_.getBus().subscribe("whip_spawn", [this](const Event& ev) {
const auto* p = std::get_if<WhipSpawnPayload>(&ev.payload);
if (p != nullptr) { spawnWhip(*p); }
@@ -479,7 +478,7 @@ GameScene::GameScene(Config config)
// Timings i corbes de la FSM de sala. Llegits del YAML una sola
// vegada; canvis requereixen reiniciar el joc.
transition_config_ = RoomFsm::loadConfig(Defaults::GameScene::CONFIG_YAML);
transition_config_ = RoomFsm::loadConfig(Defaults::GameScene::GAME_YAML);
// Cinemàtica d'entrada de la primera sala: arrenca la pista
// `room_intro.ogg` i arma el callback de fi que farà spawn + salt
@@ -556,8 +555,7 @@ void GameScene::loadRoom(const std::string& yaml_path) {
hud_->setPyramidRoom(mgr.currentPyramid(), mgr.currentRoom());
}
std::cout << "[ROOM] load " << yaml_path << ": entities reloaded, paint cleared, "
<< ACTIVE_COUNT << " active player(s) disabled (awaiting spawn cinematic)\n";
Log::info("ROOM", "load {}: entities reloaded, paint cleared, {} active player(s) disabled (awaiting spawn cinematic)", yaml_path, ACTIVE_COUNT);
}
void GameScene::advanceToNextRoom() {
@@ -565,7 +563,7 @@ void GameScene::advanceToNextRoom() {
if (!mgr.advance()) {
// Política temporal mentre no hi haja pantalla de "Fi de joc":
// arribar al final del levels.yaml fa loop a la primera sala.
std::cout << "[ROOM] final del levels.yaml — bucle a la primera sala\n";
Log::info("ROOM", "final del levels.yaml — bucle a la primera sala");
mgr.restart();
}
loadRoom(mgr.currentPath());
@@ -576,9 +574,9 @@ void GameScene::enterRoomIntro() {
// Anotem la transició al log si veníem d'un altre estat; si ja
// estàvem a INTRO (cas típic d'un salt debug) també queda ben clar.
if (state_ != RoomFsm::State::ROOM_INTRO) {
std::cout << "[ROOM] " << RoomFsm::stateName(state_) << " -> ROOM_INTRO\n";
Log::info("ROOM", "{} -> ROOM_INTRO", RoomFsm::stateName(state_));
} else {
std::cout << "[ROOM] ROOM_INTRO restart (cinemàtica reiniciada)\n";
Log::info("ROOM", "ROOM_INTRO restart (cinemàtica reiniciada)");
}
state_ = RoomFsm::State::ROOM_INTRO;
transition_ = {};
@@ -602,7 +600,7 @@ void GameScene::enterRoomIntro() {
auto* audio = Audio::get();
if (audio == nullptr) {
std::cout << "[ROOM] INTRO: Audio absent — sense música, timeline només contra text\n";
Log::warn("ROOM", "INTRO: Audio absent — sense música, timeline només contra text");
return;
}
audio->stopMusic();
@@ -611,10 +609,9 @@ void GameScene::enterRoomIntro() {
if (Audio::getMusicState() == Audio::MusicState::PLAYING) {
const int DUR_MS = Audio::getMusicDurationMs("room_intro.ogg");
transition_config_.intro_music_tail_ms = std::max(0, DUR_MS - TEXT_TOTAL_MS);
std::cout << "[ROOM] INTRO: música " << DUR_MS << " ms, music_tail "
<< transition_config_.intro_music_tail_ms << " ms\n";
Log::info("ROOM", "INTRO: música {} ms, music_tail {} ms", DUR_MS, transition_config_.intro_music_tail_ms);
} else {
std::cout << "[ROOM] INTRO: música no arrencada — timeline només contra text\n";
Log::warn("ROOM", "INTRO: música no arrencada — timeline només contra text");
}
}
@@ -638,7 +635,7 @@ void GameScene::respawnPlayersInstant() {
state_ = RoomFsm::State::PLAYING;
intro_completed_ = true;
transition_ = {};
std::cout << "[ROOM] post-editor: spawn instantani, estat -> PLAYING\n";
Log::info("ROOM", "post-editor: spawn instantani, estat -> PLAYING");
}
#endif
@@ -671,11 +668,11 @@ void GameScene::triggerPlayerSpawn(std::size_t slot_index) {
// al YAML, l'arma queda buida i el LAND és sec igualment.
players_[slot_index]->armNextLandReverb(transition_config_.intro_land_reverb_preset);
std::cout << "[ROOM] spawn cinematic P" << (slot_index + 1) << ": airborne entry\n";
Log::info("ROOM", "spawn cinematic P{}: airborne entry", slot_index + 1);
}
void GameScene::dispatchPlayerKilled(int player_id) {
std::cout << "[PLAYER KILLED] player_id=" << player_id << '\n';
Log::info("PLAYER KILLED", "player_id={}", player_id);
const auto IT = std::ranges::find_if(players_, [player_id](const auto& p) {
return p && p->getId() == player_id;
});
@@ -713,7 +710,7 @@ void GameScene::spawnDuePlayers() {
void GameScene::setState(RoomFsm::State new_state) {
if (new_state == state_) { return; }
std::cout << "[ROOM] " << RoomFsm::stateName(state_) << " -> " << RoomFsm::stateName(new_state) << '\n';
Log::info("ROOM", "{} -> {}", RoomFsm::stateName(state_), RoomFsm::stateName(new_state));
state_ = new_state;
transition_ = {};
// Reset defensiu dels flags de cicle quan tornem a INTRO via aquest
@@ -742,7 +739,7 @@ void GameScene::setState(RoomFsm::State new_state) {
const int TEXT_TOTAL_MS = transition_config_.outro_text_enter_ms + transition_config_.outro_text_hold_ms + transition_config_.outro_text_exit_ms;
transition_config_.outro_music_tail_ms = 0;
if (audio == nullptr) {
std::cout << "[ROOM] OUTRO: Audio absent — sense música, timeline només contra text\n";
Log::warn("ROOM", "OUTRO: Audio absent — sense música, timeline només contra text");
} else {
audio->stopMusic();
audio->setOnMusicEnded({});
@@ -750,10 +747,9 @@ void GameScene::setState(RoomFsm::State new_state) {
if (Audio::getMusicState() == Audio::MusicState::PLAYING) {
const int DUR_MS = Audio::getMusicDurationMs("room_outro.ogg");
transition_config_.outro_music_tail_ms = std::max(0, DUR_MS - TEXT_TOTAL_MS);
std::cout << "[ROOM] OUTRO: música " << DUR_MS << " ms, music_tail "
<< transition_config_.outro_music_tail_ms << " ms\n";
Log::info("ROOM", "OUTRO: música {} ms, music_tail {} ms", DUR_MS, transition_config_.outro_music_tail_ms);
} else {
std::cout << "[ROOM] OUTRO: música no arrencada — timeline només contra text\n";
Log::warn("ROOM", "OUTRO: música no arrencada — timeline només contra text");
}
}
}
@@ -856,7 +852,7 @@ void GameScene::renderRoomText() {
// l'antic GameCanvas::CENTER_Y per descomptar la franja del marcador
// i tenir el rètol equilibrat sobre el playfield.
const int PLAY_CENTER_Y = (GameCanvas::HEIGHT + Map::hud_h) / 2;
// Colors del rètol via UiConfig (`data/ui.yaml > colors.room_text` i
// Colors del rètol via UiConfig (`data/config/ui.yaml > colors.room_text` i
// `room_text_outline`). Independents dels colors de notificació.
const Uint8 FILL = UiConfig::get().roomText();
const Uint8 OUTLINE = UiConfig::get().roomTextOutline();
@@ -1075,7 +1071,7 @@ void GameScene::handlePauseInput() {
pause_.curve = GOING_TO_PAUSE ? UiConfig::get().pauseEnterCurve() : UiConfig::get().pauseExitCurve();
pause_.state = GOING_TO_PAUSE ? PauseState::ENTERING : PauseState::EXITING;
std::cout << "[PAUSE] " << (GOING_TO_PAUSE ? "entering" : "exiting") << " from_scale=" << pause_.from_scale << " duration_ms=" << pause_.duration_ms << '\n';
Log::info("PAUSE", "{} from_scale={} duration_ms={}", GOING_TO_PAUSE ? "entering" : "exiting", pause_.from_scale, pause_.duration_ms);
}
}
@@ -1212,7 +1208,7 @@ void GameScene::update(int delta_ms) {
// (creuar → player_exited → level_complete → OUTRO) es manté.
// Mateixos gates que el salt entre sales.
if (pause_.state == PauseState::IDLE && !editor_.active() && detectOpenDoor()) {
std::cout << "[DEBUG] força obertura de la porta\n";
Log::info("DEBUG", "força obertura de la porta");
entity_manager_.forceOpenDoor();
}
#endif
+2 -2
View File
@@ -202,7 +202,7 @@ class GameScene : public Scene {
// Font del rètol de transició de sala (ROOM_INTRO i ROOM_OUTRO).
// Distinta de `pause_font_` perquè el rètol de fase té estètica
// pròpia (CaesarDressing per defecte, configurable via
// `data/ui.yaml > fonts.room_text`). La pausa segueix amb la
// `data/config/ui.yaml > fonts.room_text`). La pausa segueix amb la
// placeholder.
std::shared_ptr<Text> room_text_font_;
@@ -224,7 +224,7 @@ class GameScene : public Scene {
} pause_;
// Configuració de la FSM de sala (intro/outro). Carregada al ctor des
// de `data/scenes/game.yaml`; valors per defecte si manca el YAML.
// de `data/scenes/game/game.yaml`; valors per defecte si manca el YAML.
RoomFsm::Config transition_config_{};
// Estat actual de la FSM de sala. S'inicialitza a ROOM_INTRO al ctor
+2 -4
View File
@@ -4,7 +4,6 @@
#include <algorithm>
#include <cstdint>
#include <iostream>
#include <string>
#include <utility>
@@ -20,13 +19,12 @@
#include "game/scenes/scene_flow.hpp"
#include "utils/defaults.hpp"
#include "utils/defines.hpp"
#include "utils/log.hpp"
namespace {
constexpr int NAME_Y_OFFSET = -8;
constexpr int TIMER_Y_OFFSET = 8;
// TODO: migrar a un logger unificat quan es decideixi (veure INC-03 a
// docs/audit-core-rendering.md).
const char* const SCENE_NAME = "INTRO";
} // namespace
@@ -34,7 +32,7 @@ namespace {
IntroScene::IntroScene(std::shared_ptr<Text> font, DebugConfig::StartingPlayers starting_players)
: font_(std::move(font)),
starting_players_(starting_players) {
std::cout << "[scene] " << SCENE_NAME << " start (players=" << DebugConfig::toString(starting_players_) << ")\n";
Log::info("scene", "{} start (players={})", SCENE_NAME, DebugConfig::toString(starting_players_));
}
IntroScene::~IntroScene() = default;
+68 -43
View File
@@ -4,7 +4,6 @@
#include <algorithm>
#include <cstdint>
#include <iostream>
#include <utility>
#include "core/audio/audio.hpp"
@@ -19,25 +18,38 @@
#include "game/scenes/placeholder_input.hpp"
#include "game/scenes/scene_flow.hpp"
#include "utils/defines.hpp"
#include "utils/log.hpp"
namespace {
// TODO: migrar a un logger unificat quan es decideixi (veure INC-03 a
// docs/audit-core-rendering.md). De moment std::cout per rastrejar el
// flux d'escenes als smoke runs.
const char* const SCENE_NAME = "LOGO";
} // namespace
LogoScene::LogoScene(std::shared_ptr<Surface> logo, bool reset)
: logo_(std::move(logo)) {
std::cout << "[scene] " << SCENE_NAME << (reset ? " start (reset)\n" : " start\n");
Log::info("scene", "{} start{}", SCENE_NAME, reset ? " (reset)" : "");
if (reset) {
if (auto* audio = Audio::get(); audio != nullptr) {
audio->stopMusic();
audio->stopAllSounds();
}
}
// Arrenquem en BLACK_INTRO: ni so ni efecte. El glitch sound i el
// begin() de scan_ es disparen quan entrem a SCAN.
}
LogoScene::~LogoScene() = default;
auto LogoScene::wantsSkip() -> bool {
return std::ranges::any_of(EventBuffer::events, [](const SDL_Event& ev) {
return ev.type == SDL_EVENT_KEY_DOWN && !Placeholder::isReservedScancode(ev.key.scancode);
});
}
void LogoScene::enterScan() {
phase_ = Phase::SCAN;
phase_elapsed_ms_ = 0;
// So del scanline reveal: una sola pulsació al començament de la
// fase SCAN, en grup INTERFACE perquè no es vegi afectat pel volum
// de sons del joc i quedi sempre audible.
@@ -60,26 +72,36 @@ LogoScene::LogoScene(std::shared_ptr<Surface> logo, bool reset)
.vhs_burst_height_px = s.vhs_burst_height_px,
.vhs_burst_amplitude_px = s.vhs_burst_amplitude_px,
.flash_ms = s.flash_ms,
.tail_glitch_ms = s.tail_glitch_ms,
});
}
LogoScene::~LogoScene() = default;
auto LogoScene::wantsSkip() -> bool {
return std::ranges::any_of(EventBuffer::events, [](const SDL_Event& ev) {
return ev.type == SDL_EVENT_KEY_DOWN && !Placeholder::isReservedScancode(ev.key.scancode);
});
}
void LogoScene::enterMiddle() {
phase_ = Phase::MIDDLE;
void LogoScene::enterHoldSilent() {
phase_ = Phase::HOLD_SILENT;
phase_elapsed_ms_ = 0;
}
void LogoScene::enterHoldMusic() {
phase_ = Phase::HOLD_MUSIC;
phase_elapsed_ms_ = 0;
// Música del títol al començament d'aquesta fase. La crida és
// idempotent (Audio detecta same-track + same-loop i no reinicia),
// així que TitleScene podrà fer el seu propi playMusic sense
// interrompre la pista durant el FADE_OUT i el BLACK_OUTRO.
if (auto* audio = Audio::get(); audio != nullptr) {
audio->playMusic("title.ogg", -1);
}
}
void LogoScene::enterFadeOut() {
phase_ = Phase::FADE_OUT;
phase_elapsed_ms_ = 0;
fade_.begin(FADE_OUT_MS, Transitions::FadeToBlack::Direction::FADE_OUT);
fade_.begin(UiConfig::get().logoTimeline().fade_out_ms, Transitions::FadeToBlack::Direction::FADE_OUT);
}
void LogoScene::enterBlackOutro() {
phase_ = Phase::BLACK_OUTRO;
phase_elapsed_ms_ = 0;
}
void LogoScene::enterDone() {
@@ -103,32 +125,32 @@ void LogoScene::update(int delta_ms) {
if (phase_ != Phase::DONE) {
phase_elapsed_ms_ += delta_ms;
// Tick dels efectes actius. `scan_` només fa feina visible
// durant Phase::SCAN; `fade_` només durant Phase::FADE_OUT. Tots
// dos són no-op si el seu duration_ms intern està a 0.
// Tick dels efectes actius. Tots dos són no-op si el seu
// duration_ms intern està a 0; només cal cridar-los si la fase
// corresponent està viva o just acabada.
scan_.tick(delta_ms);
fade_.tick(delta_ms);
}
const auto& tl = UiConfig::get().logoTimeline();
switch (phase_) {
case Phase::SCAN:
if (scan_.isFinished()) { enterMiddle(); }
case Phase::BLACK_INTRO:
if (phase_elapsed_ms_ >= tl.black_intro_ms) { enterScan(); }
break;
case Phase::MIDDLE:
// Música del títol al cor de la fase central. La crida és
// idempotent (Audio detecta same-track + same-loop i no
// reinicia), així que TitleScene podrà fer el seu propi
// playMusic sense interrompre la pista.
if (!music_started_ && phase_elapsed_ms_ >= MUSIC_TRIGGER_MS) {
music_started_ = true;
if (auto* audio = Audio::get(); audio != nullptr) {
audio->playMusic("title.ogg", -1);
}
}
if (phase_elapsed_ms_ >= MIDDLE_MS) { enterFadeOut(); }
case Phase::SCAN:
if (scan_.isFinished()) { enterHoldSilent(); }
break;
case Phase::HOLD_SILENT:
if (phase_elapsed_ms_ >= tl.hold_silent_ms) { enterHoldMusic(); }
break;
case Phase::HOLD_MUSIC:
if (phase_elapsed_ms_ >= tl.hold_with_music_ms) { enterFadeOut(); }
break;
case Phase::FADE_OUT:
if (phase_elapsed_ms_ >= FADE_OUT_MS) { enterDone(); }
if (phase_elapsed_ms_ >= tl.fade_out_ms) { enterBlackOutro(); }
break;
case Phase::BLACK_OUTRO:
if (phase_elapsed_ms_ >= tl.black_outro_ms) { enterDone(); }
break;
case Phase::DONE:
break;
@@ -142,25 +164,28 @@ void LogoScene::render() {
constexpr std::uint32_t BLACK_ARGB = 0xFF000000U;
Screen::get().fillRect(0, 0, GameCanvas::WIDTH, GameCanvas::HEIGHT, BLACK_ARGB);
if (logo_) {
// BLACK_INTRO i BLACK_OUTRO no pinten logo: només el negre del
// fillRect d'aquí dalt. La resta de fases sí.
const bool SHOW_LOGO = phase_ != Phase::BLACK_INTRO && phase_ != Phase::BLACK_OUTRO;
if (logo_ && SHOW_LOGO) {
const int X = (GameCanvas::WIDTH - logo_->getWidth()) / 2;
const int Y = (GameCanvas::HEIGHT - logo_->getHeight()) / 2;
if (phase_ == Phase::SCAN) {
// Revelat CRT: la pròpia ScanReveal s'encarrega del blit
// fila a fila, de la banda lluminosa, dels glitches i del
// flash final.
// Revelat CRT + glitch tail: la pròpia ScanReveal s'encarrega
// del blit fila a fila, de la banda lluminosa, dels glitches,
// del flash final i dels bursts del tail.
scan_.render(*logo_, X, Y);
} else {
// MIDDLE i FADE_OUT: render normal, el logo ja està a
// pantalla complet i estable.
// HOLD_SILENT, HOLD_MUSIC, FADE_OUT: render normal, el logo
// ja està a pantalla complet i estable.
logo_->render(X, Y);
}
}
Notifier::get().render();
// Capa de fosa: només FADE_OUT pinta negre semitransparent (el
// FADE_IN s'ha substituït per `scan_`). Va per damunt del logo i
// del Notifier, però per sota de RenderInfo (overlay de debug).
// Capa de fosa: només pinta durant FADE_OUT (alpha creixent). En
// BLACK_OUTRO ja deixem la pantalla negra del fillRect, així que no
// cal ni que `fade_` segueixi viu.
fade_.render();
RenderInfo::get().render();
Screen::get().present();
+26 -23
View File
@@ -9,14 +9,22 @@
class Surface;
// Escena del logotip de l'editora. FSM amb tres fases visibles:
// `SCAN` — revelat tipus scanline CRT (durada YAML, ~1500 ms)
// `MIDDLE` — logo estable (`MIDDLE_MS`, 5 s)
// `FADE_OUT` — fade lineal a negre (`FADE_OUT_MS`, 500 ms)
// I una terminal `DONE` que dispara la transició cap a TITLE. A 2,5 s
// dins de `MIDDLE` arrenca la música del títol perquè entri al cor de
// l'escena i continue cap a TitleScene sense talls (Audio ja és
// idempotent quan es demana la mateixa pista que ja sona).
// Escena del logotip de l'editora. Timeline lineal en 7 fases visibles
// (durades configurables des de `logo.timeline.*` al YAML, excepte SCAN
// que la marca `logo.scan.duration_ms` + `tail_glitch_ms` + `flash_ms`):
//
// `BLACK_INTRO` — negre pur abans d'arrencar res.
// `SCAN` — revelat scanline CRT amb glitch tail. So
// `logoGlitchSound` es dispara just al començar
// aquesta fase (no al constructor).
// `HOLD_SILENT` — logo estable, en silenci, marge entre el final
// del glitch i l'arrencada de la música.
// `HOLD_MUSIC` — logo estable amb la música del títol sonant.
// `FADE_OUT` — fade lineal a negre.
// `BLACK_OUTRO` — negre amb la música encara sonant. TitleScene
// continuarà la pista de manera idempotent (Audio
// detecta same-track + same-loop i no reinicia).
// `DONE` — terminal; dispara la transició cap a TITLE.
//
// Skip: qualsevol tecla no reservada salta directament a la transició
// (sense fade-out) — el TitleScene té el seu propi `intro.fade_in_ms`,
@@ -27,14 +35,6 @@ class Surface;
// silenciós encara que veníssem del joc en marxa.
class LogoScene : public Scene {
public:
static constexpr int MIDDLE_MS = 5000;
static constexpr int FADE_OUT_MS = 500;
// Offset dins de MIDDLE en què comença a sonar la pista del títol;
// triat perquè quedi clarament al cor de la fase central (logo
// estable) i prou abans del fade-out per donar continuïtat sonora a
// TitleScene.
static constexpr int MUSIC_TRIGGER_MS = 2500;
LogoScene(std::shared_ptr<Surface> logo, bool reset);
~LogoScene() override;
@@ -42,22 +42,25 @@ class LogoScene : public Scene {
void render() override;
private:
enum class Phase : std::uint8_t { SCAN,
MIDDLE,
enum class Phase : std::uint8_t { BLACK_INTRO,
SCAN,
HOLD_SILENT,
HOLD_MUSIC,
FADE_OUT,
BLACK_OUTRO,
DONE };
void enterMiddle();
void enterScan();
void enterHoldSilent();
void enterHoldMusic();
void enterFadeOut();
void enterBlackOutro();
void enterDone();
[[nodiscard]] static auto wantsSkip() -> bool;
std::shared_ptr<Surface> logo_;
Phase phase_{Phase::SCAN};
Phase phase_{Phase::BLACK_INTRO};
int phase_elapsed_ms_{0};
bool music_started_{false};
// Capa de fosa per al FADE_OUT cap a TITLE. El FADE_IN inicial s'ha
// substituït per `scan_` (revelat CRT amb glitch).
Transitions::FadeToBlack fade_;
Effects::ScanReveal scan_;
};
+1 -1
View File
@@ -8,7 +8,7 @@
namespace Placeholder {
// Retorna true si el scancode coincideix amb alguna tecla declarada al
// KeyConfig::get() (les carregades de data/input/keys.yaml). Si KeyConfig
// KeyConfig::get() (les carregades de data/config/input.yaml). Si KeyConfig
// no s'ha carregat, totes les consultes retornaran SDL_SCANCODE_UNKNOWN i
// aquesta funció tornarà false, de manera que el test/smoke no depèn de
// l'estat global de la configuració.
+3 -5
View File
@@ -1,12 +1,12 @@
#include "game/scenes/room_fsm.hpp"
#include <array>
#include <iostream>
#include <string_view>
#include "core/locale/locale.hpp"
#include "core/resources/resource_helper.hpp"
#include "external/fkyaml_node.hpp"
#include "utils/log.hpp"
namespace {
@@ -28,7 +28,7 @@ namespace RoomFsm {
auto bytes = Resource::Helper::loadFile(yaml_path);
if (bytes.empty()) {
std::cout << "RoomFsm: " << yaml_path << " absent — uso fallbacks compilats\n";
Log::warn("RoomFsm", "{} absent — uso fallbacks compilats", yaml_path);
return cfg;
}
const auto* begin = reinterpret_cast<const char*>(bytes.data());
@@ -83,9 +83,7 @@ namespace RoomFsm {
cfg.text_panel_height_px = readChildInt(n, "height_px", cfg.text_panel_height_px);
}
std::cout << "RoomFsm: timings carregats des de " << yaml_path
<< " (intro fade " << cfg.intro_fade_in_ms
<< "ms, outro fade " << cfg.outro_fade_out_ms << "ms)\n";
Log::info("RoomFsm", "timings carregats des de {} (intro fade {}ms, outro fade {}ms)", yaml_path, cfg.intro_fade_in_ms, cfg.outro_fade_out_ms);
return cfg;
}
+2 -2
View File
@@ -35,7 +35,7 @@ namespace RoomFsm {
int elapsed_ms{0};
};
// Timings i corbes carregats de `data/scenes/game.yaml`. Els valors
// Timings i corbes carregats de `data/scenes/game/game.yaml`. Els valors
// per defecte coincideixen amb els del YAML versionat: si manca el
// fitxer, la FSM funciona igualment amb aquests fallbacks.
struct Config {
@@ -94,7 +94,7 @@ namespace RoomFsm {
UiConfig::CurveFn text_enter_curve{nullptr};
UiConfig::CurveFn text_exit_curve{nullptr};
// Preset de reverb (catàleg a `data/sounds.yaml`) que GameScene
// Preset de reverb (catàleg a `data/config/sounds.yaml`) que GameScene
// arma al jugador al spawn aeri d'INTRO. Es consumeix al primer
// aterratge cinemàtic; els salts posteriors del joc i els
// respawns post-mort cauen al so sec. Buit = sense reverb (la
+4 -4
View File
@@ -4,7 +4,6 @@
#include <algorithm>
#include <cstdint>
#include <iostream>
#include <string>
#include <utility>
@@ -26,6 +25,7 @@
#include "utils/defaults.hpp"
#include "utils/defines.hpp"
#include "utils/easing.hpp"
#include "utils/log.hpp"
namespace {
@@ -72,7 +72,7 @@ TitleScene::TitleScene(std::shared_ptr<Text> condensed_font, std::shared_ptr<Tex
game_logo_(std::move(game_logo)),
mini_logo_(std::move(mini_logo)),
timer_ms_(UiConfig::get().titleDurationMs()) {
std::cout << "[scene] " << SCENE_NAME << " start\n";
Log::info("scene", "{} start", SCENE_NAME);
const auto& hh = UiConfig::get().titleHeatHaze();
if (hh.enabled) {
heat_haze_.emplace(Effects::HeatHaze::Params{
@@ -190,7 +190,7 @@ void TitleScene::enterStarting(DebugConfig::StartingPlayers players) {
if (auto* audio = Audio::get(); audio != nullptr) {
audio->fadeOutMusic(UiConfig::get().titleMusicFadeOutMs());
}
std::cout << "[scene] " << SCENE_NAME << " STARTING (players=" << DebugConfig::toString(players) << ")\n";
Log::info("scene", "{} STARTING (players={})", SCENE_NAME, DebugConfig::toString(players));
}
void TitleScene::tickIdle(int delta_ms, bool p1_start, bool p2_start) {
@@ -255,7 +255,7 @@ void TitleScene::enterAttract() {
if (attract_fade_) {
attract_fade_->begin(UiConfig::get().titleAttractFadeToBlackMs());
}
std::cout << "[scene] " << SCENE_NAME << " ATTRACT_OUT\n";
Log::info("scene", "{} ATTRACT_OUT", SCENE_NAME);
}
void TitleScene::advanceToIntro(DebugConfig::StartingPlayers players) {
+1 -1
View File
@@ -17,7 +17,7 @@ class Text;
// Escena TITLE provisional. Pinta dos blocs de text sobre un fons gairebé
// negre i un prompt "press start" intermitent fins que apareix l'artwork
// real. Tota la cosmètica (colors, durada, ritme del parpadeig) viu a
// data/ui.yaml secció `title:` per poder afinar-la sense recompilar.
// data/config/ui.yaml secció `title:` per poder afinar-la sense recompilar.
class TitleScene : public Scene {
public:
// Fases de l'escena.
Loaded 100 of 114 files, more files were not shown because too many files have changed in this diff. Show more