Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3442b9b481 | ||
|
|
8746ee9ec9 | ||
|
|
ef7fbf4027 | ||
|
|
964c32dd0d | ||
|
|
970fe2b4db | ||
|
|
f816e75cc0 | ||
|
|
2e8df45b38 | ||
|
|
643e5442c3 | ||
|
|
82ccf65ce1 | ||
|
|
1ae6912be5 | ||
|
|
c003e6665e | ||
|
|
a254fdd669 | ||
|
|
c0362abf67 | ||
|
|
d13313503c | ||
|
|
2a78c78e77 | ||
|
|
10e16d7905 | ||
|
|
ecb4625465 | ||
|
|
b4e36944d7 | ||
|
|
9b09ccb765 | ||
|
|
94213f9fd6 | ||
|
|
f591aa82fd | ||
|
|
2ef5968e0d | ||
|
|
ca03a1307e | ||
|
|
fe916d263f | ||
|
|
de7233a012 | ||
|
|
d26ecec04f | ||
|
|
fddea0c449 | ||
|
|
2c1c1a442b | ||
|
|
d1948afe4b | ||
|
|
ac06b05332 | ||
|
|
7d11dde0b1 | ||
|
|
73add013dd | ||
|
|
4e2ba827e3 | ||
|
|
9ac0c0cb74 | ||
|
|
11a909b58d | ||
|
|
17cf6e0636 | ||
|
|
7d0aca506a | ||
|
|
7cd7b4dc4f | ||
|
|
6d4f606eeb | ||
|
|
1ccd444aad | ||
|
|
3bfca35f3a | ||
|
|
3e5217066f | ||
|
|
4da50571eb | ||
|
|
d4593d0686 | ||
|
|
a53cf4b797 | ||
|
|
bcbf990a5f | ||
|
|
45514ae2f4 | ||
|
|
0847e9c544 | ||
|
|
27cf2f032d | ||
|
|
6f6aa182f7 | ||
|
|
b65eeb7aaa | ||
|
|
37fdcb4751 | ||
|
|
4739354875 | ||
|
|
2879cbdb17 | ||
|
|
38070b33aa | ||
|
|
9f0a566ffb | ||
|
|
6397f9d4eb | ||
|
|
72384c3113 | ||
|
|
9dc7ff0da3 | ||
|
|
1d43a77800 | ||
|
|
39eda56c9d | ||
|
|
a351f920ba | ||
|
|
72642dc10e | ||
|
|
404db74889 | ||
|
|
91f664c4d5 | ||
|
|
ce55aeeadc | ||
|
|
8f66961c7b | ||
|
|
121558b1fd | ||
|
|
df60709e6d | ||
|
|
d208ba02df | ||
|
|
5db3bb0bd0 | ||
|
|
9ad21eb5bb | ||
|
|
4752eb8ad7 | ||
|
|
887b8f3aa3 | ||
|
|
2f3643f09c | ||
|
|
cc529afeb2 | ||
|
|
15a7c4e18a | ||
|
|
7d725f99f4 | ||
|
|
e9ef6565a7 | ||
|
|
47bb92144e | ||
|
|
d3698a1504 | ||
|
|
42062dca55 | ||
|
|
5502191725 | ||
|
|
8e2865efbd | ||
|
|
99eb42b319 | ||
|
|
8243e95ef1 | ||
|
|
0752df0f62 | ||
|
|
98303f7ce8 | ||
|
|
7982df922b | ||
|
|
8da18a6a86 | ||
|
|
3b05704e97 | ||
|
|
00594d9eb2 | ||
|
|
af25a31639 | ||
|
|
f52e7f4682 | ||
|
|
e2f6d326aa | ||
|
|
b18bd4d021 | ||
|
|
738cde12bb | ||
|
|
b147590a39 | ||
|
|
d52cc1c509 | ||
|
|
9d4f92c98b | ||
|
|
f51fe3d820 | ||
|
|
fe17c35c67 | ||
|
|
8fd06887d1 | ||
|
|
580c13e854 | ||
|
|
645db217fa | ||
|
|
cd961cf718 | ||
|
|
c0214b233b | ||
|
|
d054d1b246 | ||
|
|
cd5bb94223 | ||
|
|
178ce486c2 | ||
|
|
811c6c482d | ||
|
|
59cd9b524c | ||
|
|
28e55a0615 | ||
|
|
5c5cd1d5da | ||
|
|
377e5d0b97 | ||
|
|
648a5932f8 | ||
|
|
b54f3fc622 | ||
|
|
63bb41425a | ||
|
|
9a2c677153 | ||
|
|
4a9a037cd8 | ||
|
|
67b06c0a10 | ||
|
|
368f9d2d6a | ||
|
|
651db50a93 | ||
|
|
decf9f5d90 | ||
|
|
f921f6bf2b | ||
|
|
abf52be7e0 | ||
|
|
3e379ce129 | ||
|
|
c38c80753a | ||
|
|
4667e0abf9 | ||
|
|
7f78f556f3 | ||
|
|
e8407ce35e | ||
|
|
188f035333 | ||
|
|
3646e6a4aa | ||
|
|
f3f5fa5e92 | ||
|
|
3a750822f6 | ||
|
|
06ff11d563 | ||
|
|
f0a0515f28 | ||
|
|
4758d5d8a4 | ||
|
|
64390f8ad7 | ||
|
|
c4caa9021e | ||
|
|
a64f33fe43 | ||
|
|
cd50626b88 | ||
|
|
7b09a076b2 | ||
|
|
91614fc545 | ||
|
|
c77f90f6b6 | ||
|
|
4f1a3260a5 | ||
|
|
1af43ef052 | ||
|
|
d5a73a7dad | ||
|
|
578916bc58 | ||
|
|
ae8b9d8fe6 | ||
|
|
50aae790b1 | ||
|
|
ea0f6d98a9 | ||
|
|
291cbdc957 | ||
|
|
858e444842 | ||
|
|
511f8d5b09 | ||
|
|
e22f3240da | ||
|
|
1cc2e28b1b | ||
|
|
820b9f2d21 | ||
|
|
f3977170b7 | ||
|
|
996ff0053e | ||
|
|
e5b8d7731b | ||
|
|
a725b76684 | ||
|
|
4e9d5df872 | ||
|
|
74dad71cc2 | ||
|
|
5c70019eb5 | ||
|
|
8aa4ec3daf | ||
|
|
6c1c042637 | ||
|
|
dc8a7723e3 | ||
|
|
82b229222f | ||
|
|
67ecb704d2 | ||
|
|
936adf1405 | ||
|
|
0d914cc6dd | ||
|
|
1117264758 | ||
|
|
d0503ff8b1 | ||
|
|
e3cf2eb773 | ||
|
|
0f56ce7567 | ||
|
|
87b08a7c7c | ||
|
|
00eb696859 | ||
|
|
48ff1839bc | ||
|
|
01145464ed | ||
|
|
f30bb1fb0f | ||
|
|
05cdcfa660 | ||
|
|
a612a8255f | ||
|
|
329bf692e6 | ||
|
|
219f8033eb | ||
|
|
6d8423764d | ||
|
|
459d18b49c | ||
|
|
243ba06351 |
No files matched your search
@@ -8,12 +8,7 @@ Checks:
|
|||||||
- -bugprone-integer-division
|
- -bugprone-integer-division
|
||||||
- -bugprone-easily-swappable-parameters
|
- -bugprone-easily-swappable-parameters
|
||||||
- -bugprone-narrowing-conversions
|
- -bugprone-narrowing-conversions
|
||||||
- -modernize-avoid-c-arrays,-warnings-as-errors
|
- -modernize-avoid-c-arrays
|
||||||
# Fals positiu recurrent en codi C de tercers (source/external/stb_vorbis.c).
|
|
||||||
# HeaderFilterRegex no aplica a clang-analyzer-*, així que cal desactivar
|
|
||||||
# explícitament. El check inferència sobre variables locals de legacy C
|
|
||||||
# genera reports que no reflecteixen bugs reals.
|
|
||||||
- -clang-analyzer-core.UndefinedBinaryOperatorResult
|
|
||||||
|
|
||||||
WarningsAsErrors: '*'
|
WarningsAsErrors: '*'
|
||||||
# Headers nostres (excloem source/external/ que conté dependències de tercers no editables)
|
# Headers nostres (excloem source/external/ que conté dependències de tercers no editables)
|
||||||
@@ -24,61 +19,89 @@ CheckOptions:
|
|||||||
# bugprone-empty-catch: aceptar catches vacíos marcados con @INTENTIONAL en un comentario
|
# bugprone-empty-catch: aceptar catches vacíos marcados con @INTENTIONAL en un comentario
|
||||||
- { key: bugprone-empty-catch.IgnoreCatchWithKeywords, value: '@INTENTIONAL' }
|
- { key: bugprone-empty-catch.IgnoreCatchWithKeywords, value: '@INTENTIONAL' }
|
||||||
|
|
||||||
# Variables locales en snake_case
|
# =====================================================================
|
||||||
|
# CONSTANTES → UPPER_CASE (compile-time y runtime, en cualquier scope)
|
||||||
|
# =====================================================================
|
||||||
|
# Todo lo que sea const o constexpr se identifica visualmente en UPPER_CASE,
|
||||||
|
# sin importar si es global, local, miembro o static.
|
||||||
|
|
||||||
|
# constexpr en cualquier scope (globales y locales)
|
||||||
|
- { key: readability-identifier-naming.ConstexprVariableCase, value: UPPER_CASE }
|
||||||
|
|
||||||
|
# Constantes globales (const no-constexpr)
|
||||||
|
- { key: readability-identifier-naming.GlobalConstantCase, value: UPPER_CASE }
|
||||||
|
|
||||||
|
# Constantes locales (const en función)
|
||||||
|
- { key: readability-identifier-naming.LocalConstantCase, value: UPPER_CASE }
|
||||||
|
|
||||||
|
# Static const a nivel de archivo/namespace
|
||||||
|
- { key: readability-identifier-naming.StaticConstantCase, value: UPPER_CASE }
|
||||||
|
|
||||||
|
# Miembros static const/constexpr de clase (p.ej. static constexpr int MAX = 100;)
|
||||||
|
- { key: readability-identifier-naming.ClassConstantCase, value: UPPER_CASE }
|
||||||
|
|
||||||
|
# Miembros const no-static de clase (p.ej. const int limit;)
|
||||||
|
- { key: readability-identifier-naming.ConstantMemberCase, value: UPPER_CASE }
|
||||||
|
|
||||||
|
# Valores de enums
|
||||||
|
- { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE }
|
||||||
|
|
||||||
|
# NOTA: Los parámetros const NO se tratan como constantes aquí.
|
||||||
|
# Un parámetro sigue siendo un parámetro aunque sea const → hereda ParameterCase.
|
||||||
|
|
||||||
|
# =====================================================================
|
||||||
|
# VARIABLES NO-CONST
|
||||||
|
# =====================================================================
|
||||||
|
|
||||||
|
# Variables locales
|
||||||
- { key: readability-identifier-naming.VariableCase, value: lower_case }
|
- { key: readability-identifier-naming.VariableCase, value: lower_case }
|
||||||
|
- { key: readability-identifier-naming.LocalVariableCase, value: lower_case }
|
||||||
|
|
||||||
# Miembros privados en snake_case con sufijo _
|
# Parámetros de función
|
||||||
- { key: readability-identifier-naming.PrivateMemberCase, value: lower_case }
|
- { key: readability-identifier-naming.ParameterCase, value: lower_case }
|
||||||
- { key: readability-identifier-naming.PrivateMemberSuffix, value: _ }
|
|
||||||
|
|
||||||
# Miembros protegidos en snake_case con sufijo _
|
# Variables estáticas no-const (static locales, static file-scope,
|
||||||
- { key: readability-identifier-naming.ProtectedMemberCase, value: lower_case }
|
# y static members no-const de clase como el instance_ de un Singleton).
|
||||||
- { key: readability-identifier-naming.ProtectedMemberSuffix, value: _ }
|
# Sufijo _ para marcar que tienen storage estático.
|
||||||
|
|
||||||
# Miembros públicos en snake_case (sin sufijo)
|
|
||||||
- { key: readability-identifier-naming.PublicMemberCase, value: lower_case }
|
|
||||||
|
|
||||||
# Namespaces en CamelCase
|
|
||||||
- { key: readability-identifier-naming.NamespaceCase, value: CamelCase }
|
|
||||||
|
|
||||||
# Variables estáticas privadas como miembros privados
|
|
||||||
- { key: readability-identifier-naming.StaticVariableCase, value: lower_case }
|
- { key: readability-identifier-naming.StaticVariableCase, value: lower_case }
|
||||||
- { key: readability-identifier-naming.StaticVariableSuffix, value: _ }
|
- { key: readability-identifier-naming.StaticVariableSuffix, value: _ }
|
||||||
|
|
||||||
# Constantes estáticas sin sufijo
|
# =====================================================================
|
||||||
- { key: readability-identifier-naming.StaticConstantCase, value: UPPER_CASE }
|
# MIEMBROS DE CLASE NO-CONST
|
||||||
|
# =====================================================================
|
||||||
|
# Privados: snake_case con sufijo _
|
||||||
|
- { key: readability-identifier-naming.PrivateMemberCase, value: lower_case }
|
||||||
|
- { key: readability-identifier-naming.PrivateMemberSuffix, value: _ }
|
||||||
|
|
||||||
# Constantes globales en UPPER_CASE
|
# Protegidos: snake_case con sufijo _
|
||||||
- { key: readability-identifier-naming.GlobalConstantCase, value: UPPER_CASE }
|
- { key: readability-identifier-naming.ProtectedMemberCase, value: lower_case }
|
||||||
|
- { key: readability-identifier-naming.ProtectedMemberSuffix, value: _ }
|
||||||
|
|
||||||
# Variables constexpr globales en UPPER_CASE
|
# Públicos: snake_case sin sufijo
|
||||||
- { key: readability-identifier-naming.ConstexprVariableCase, value: UPPER_CASE }
|
- { key: readability-identifier-naming.PublicMemberCase, value: lower_case }
|
||||||
|
|
||||||
# Constantes locales en UPPER_CASE
|
# =====================================================================
|
||||||
- { key: readability-identifier-naming.LocalConstantCase, value: UPPER_CASE }
|
# TIPOS
|
||||||
|
# =====================================================================
|
||||||
# Constexpr miembros en UPPER_CASE (sin sufijo)
|
|
||||||
- { key: readability-identifier-naming.ConstexprMemberCase, value: UPPER_CASE }
|
|
||||||
|
|
||||||
# Constexpr miembros privados/protegidos con sufijo _
|
|
||||||
- { key: readability-identifier-naming.ConstexprMethodCase, value: UPPER_CASE }
|
|
||||||
|
|
||||||
# Clases, structs y enums en CamelCase
|
|
||||||
- { key: readability-identifier-naming.ClassCase, value: CamelCase }
|
- { key: readability-identifier-naming.ClassCase, value: CamelCase }
|
||||||
- { key: readability-identifier-naming.StructCase, value: CamelCase }
|
- { key: readability-identifier-naming.StructCase, value: CamelCase }
|
||||||
- { key: readability-identifier-naming.EnumCase, value: CamelCase }
|
- { key: readability-identifier-naming.EnumCase, value: CamelCase }
|
||||||
|
- { key: readability-identifier-naming.UnionCase, value: CamelCase }
|
||||||
|
- { key: readability-identifier-naming.TypeAliasCase, value: CamelCase }
|
||||||
|
- { key: readability-identifier-naming.TypedefCase, value: CamelCase }
|
||||||
|
- { key: readability-identifier-naming.TemplateParameterCase, value: CamelCase }
|
||||||
|
|
||||||
# Valores de enums en UPPER_CASE
|
# Namespaces
|
||||||
- { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE }
|
- { key: readability-identifier-naming.NamespaceCase, value: CamelCase }
|
||||||
|
|
||||||
# Métodos en camelBack (sin sufijos)
|
# =====================================================================
|
||||||
|
# FUNCIONES Y MÉTODOS (incluyendo constexpr)
|
||||||
|
# =====================================================================
|
||||||
|
# Un método/función constexpr es un invocable, no una constante → camelBack.
|
||||||
|
- { key: readability-identifier-naming.FunctionCase, value: camelBack }
|
||||||
|
- { key: readability-identifier-naming.ConstexprFunctionCase, value: camelBack }
|
||||||
- { key: readability-identifier-naming.MethodCase, value: camelBack }
|
- { key: readability-identifier-naming.MethodCase, value: camelBack }
|
||||||
- { key: readability-identifier-naming.PrivateMethodCase, value: camelBack }
|
- { key: readability-identifier-naming.PrivateMethodCase, value: camelBack }
|
||||||
- { key: readability-identifier-naming.ProtectedMethodCase, value: camelBack }
|
- { key: readability-identifier-naming.ProtectedMethodCase, value: camelBack }
|
||||||
- { key: readability-identifier-naming.PublicMethodCase, value: camelBack }
|
- { key: readability-identifier-naming.PublicMethodCase, value: camelBack }
|
||||||
|
- { key: readability-identifier-naming.ConstexprMethodCase, value: camelBack }
|
||||||
# Funciones en camelBack
|
|
||||||
- { key: readability-identifier-naming.FunctionCase, value: camelBack }
|
|
||||||
|
|
||||||
# Parámetros en lower_case
|
|
||||||
- { key: readability-identifier-naming.ParameterCase, value: lower_case }
|
|
||||||
@@ -33,7 +33,10 @@ configure_file(${CMAKE_SOURCE_DIR}/source/version.h.in ${CMAKE_BINARY_DIR}/versi
|
|||||||
# Sources comuns del sistema d'entitats: s'afegeixen tant a l'executable
|
# Sources comuns del sistema d'entitats: s'afegeixen tant a l'executable
|
||||||
# principal com al smoke test. Mantenir-los en variable separada evita
|
# principal com al smoke test. Mantenir-los en variable separada evita
|
||||||
# duplicació entre els dos targets.
|
# duplicació entre els dos targets.
|
||||||
set(ENTITY_SOURCES
|
# Entitats sense dependència directa amb Player.cpp: les enllacen tant
|
||||||
|
# l'executable principal com el test_entities_smoke (que evita arrossegar
|
||||||
|
# l'stack del jugador i les seues dependències pesades).
|
||||||
|
set(ENTITY_SOURCES_BASE
|
||||||
source/game/entities/entity.cpp
|
source/game/entities/entity.cpp
|
||||||
source/game/entities/animation_component.cpp
|
source/game/entities/animation_component.cpp
|
||||||
source/game/entities/event_bus.cpp
|
source/game/entities/event_bus.cpp
|
||||||
@@ -47,6 +50,15 @@ set(ENTITY_SOURCES
|
|||||||
source/game/entities/concrete/rolling_rock.cpp
|
source/game/entities/concrete/rolling_rock.cpp
|
||||||
source/game/entities/concrete/wall_trap.cpp
|
source/game/entities/concrete/wall_trap.cpp
|
||||||
source/game/entities/concrete/floor_trap.cpp
|
source/game/entities/concrete/floor_trap.cpp
|
||||||
|
source/game/entities/concrete/hole.cpp
|
||||||
|
source/game/entities/concrete/dust.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
# Entitats que criden directament símbols de Player (com WhipAttack via
|
||||||
|
# `enterHurt`/`getBaseBox`): només van a l'executable principal.
|
||||||
|
set(ENTITY_SOURCES
|
||||||
|
${ENTITY_SOURCES_BASE}
|
||||||
|
source/game/entities/concrete/whip_attack.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(APP_SOURCES
|
set(APP_SOURCES
|
||||||
@@ -54,23 +66,32 @@ set(APP_SOURCES
|
|||||||
source/core/system/director.cpp
|
source/core/system/director.cpp
|
||||||
source/core/audio/audio.cpp
|
source/core/audio/audio.cpp
|
||||||
source/core/audio/audio_adapter.cpp
|
source/core/audio/audio_adapter.cpp
|
||||||
|
source/core/audio/audio_effects.cpp
|
||||||
source/core/audio/jail_audio.cpp
|
source/core/audio/jail_audio.cpp
|
||||||
|
source/core/audio/sound_effects_config.cpp
|
||||||
source/core/locale/locale.cpp
|
source/core/locale/locale.cpp
|
||||||
|
source/core/ui/ui_config.cpp
|
||||||
source/core/input/key_config.cpp
|
source/core/input/key_config.cpp
|
||||||
source/core/input/global_inputs.cpp
|
source/core/input/global_inputs.cpp
|
||||||
source/core/input/input_state.cpp
|
source/core/input/input_state.cpp
|
||||||
source/core/input/gamepad_manager.cpp
|
source/core/input/gamepad_manager.cpp
|
||||||
|
source/core/input/gamepad_bindings.cpp
|
||||||
|
source/core/input/define_buttons.cpp
|
||||||
source/core/input/mouse.cpp
|
source/core/input/mouse.cpp
|
||||||
source/core/rendering/screen.cpp
|
source/core/rendering/screen.cpp
|
||||||
source/core/rendering/canvas.cpp
|
source/core/rendering/canvas.cpp
|
||||||
source/core/rendering/notifier.cpp
|
source/core/rendering/notifier.cpp
|
||||||
source/core/rendering/render_info.cpp
|
source/core/rendering/render_info.cpp
|
||||||
source/core/rendering/gif.cpp
|
source/core/rendering/gif.cpp
|
||||||
|
source/core/rendering/screenshot.cpp
|
||||||
|
source/external/stb_image_write_impl.cpp
|
||||||
|
source/external/stb_vorbis_impl.cpp
|
||||||
source/core/rendering/surface.cpp
|
source/core/rendering/surface.cpp
|
||||||
source/core/rendering/text.cpp
|
source/core/rendering/text.cpp
|
||||||
source/core/rendering/sprite/sprite.cpp
|
source/core/rendering/sprite/sprite.cpp
|
||||||
source/core/rendering/sprite/moving_sprite.cpp
|
source/core/rendering/sprite/moving_sprite.cpp
|
||||||
source/core/rendering/sprite/animated_sprite.cpp
|
source/core/rendering/sprite/animated_sprite.cpp
|
||||||
|
source/core/rendering/sprite/animated_sprite_parser.cpp
|
||||||
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp
|
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp
|
||||||
source/core/rendering/sdl3gpu/viewport.cpp
|
source/core/rendering/sdl3gpu/viewport.cpp
|
||||||
source/core/rendering/sdl3gpu/pipelines/crtpi_pipeline.cpp
|
source/core/rendering/sdl3gpu/pipelines/crtpi_pipeline.cpp
|
||||||
@@ -84,14 +105,30 @@ set(APP_SOURCES
|
|||||||
source/game/options.cpp
|
source/game/options.cpp
|
||||||
source/game/options_apply.cpp
|
source/game/options_apply.cpp
|
||||||
source/game/ui/service_menu.cpp
|
source/game/ui/service_menu.cpp
|
||||||
|
source/game/ui/hud.cpp
|
||||||
source/game/scenes/placeholder_input.cpp
|
source/game/scenes/placeholder_input.cpp
|
||||||
source/game/scenes/logo_scene.cpp
|
source/game/scenes/logo_scene.cpp
|
||||||
source/game/scenes/title_scene.cpp
|
source/game/scenes/title_scene.cpp
|
||||||
source/game/scenes/intro_scene.cpp
|
source/game/scenes/intro_scene.cpp
|
||||||
source/game/scenes/game_scene.cpp
|
source/game/scenes/game_scene.cpp
|
||||||
|
source/game/scenes/room_fsm.cpp
|
||||||
source/game/entities/player.cpp
|
source/game/entities/player.cpp
|
||||||
source/game/map/map.cpp
|
source/game/map/map.cpp
|
||||||
source/game/map/tilemap.cpp
|
source/game/map/tilemap.cpp
|
||||||
|
source/game/map/tilemap_writer.cpp
|
||||||
|
# Writer surgical de claus dins de `geometry:` (door_col), paral·lel
|
||||||
|
# al TilemapWriter. Només Debug.
|
||||||
|
source/game/map/map_writer.cpp
|
||||||
|
source/game/map/paint_layer.cpp
|
||||||
|
source/game/levels/level_manager.cpp
|
||||||
|
# Mòdul editor (només activa contingut en Debug via `#ifdef _DEBUG`).
|
||||||
|
# En Release els fitxers compilen a object buit i l'enllaçador no
|
||||||
|
# arrossega cap símbol del joc final.
|
||||||
|
source/game/editor/editor.cpp
|
||||||
|
source/game/editor/editor_config.cpp
|
||||||
|
# Writer del bloc `entities:` del YAML del nivell, paral·lel al
|
||||||
|
# TilemapWriter pero per a la persistència de l'editor d'entitats.
|
||||||
|
source/game/entities/entity_writer.cpp
|
||||||
${ENTITY_SOURCES}
|
${ENTITY_SOURCES}
|
||||||
# Fitxer només del binari principal: `loadFromLevel` depèn de
|
# Fitxer només del binari principal: `loadFromLevel` depèn de
|
||||||
# Resource::Cache (→ Surface → GIF → àudio). Separat per no arrossegar
|
# Resource::Cache (→ Surface → GIF → àudio). Separat per no arrossegar
|
||||||
@@ -150,6 +187,15 @@ endif()
|
|||||||
|
|
||||||
add_executable(${PROJECT_NAME} ${APP_SOURCES})
|
add_executable(${PROJECT_NAME} ${APP_SOURCES})
|
||||||
|
|
||||||
|
# Silencia warnings de codi de tercers (mateixa filosofia que el
|
||||||
|
# .clang-tidy de source/external/). Només afecta aquests TUs concrets;
|
||||||
|
# la resta del codi continua compilant amb -Wall -Wextra -Wpedantic.
|
||||||
|
set_source_files_properties(
|
||||||
|
source/external/stb_image_write_impl.cpp
|
||||||
|
source/external/stb_vorbis_impl.cpp
|
||||||
|
PROPERTIES COMPILE_OPTIONS "-Wno-missing-field-initializers;-Wno-deprecated-declarations"
|
||||||
|
)
|
||||||
|
|
||||||
if(NOT APPLE AND GLSLC_EXE)
|
if(NOT APPLE AND GLSLC_EXE)
|
||||||
add_dependencies(${PROJECT_NAME} shaders)
|
add_dependencies(${PROJECT_NAME} shaders)
|
||||||
endif()
|
endif()
|
||||||
@@ -192,6 +238,26 @@ add_custom_command(
|
|||||||
add_custom_target(resource_pack ALL DEPENDS ${RESOURCE_PACK})
|
add_custom_target(resource_pack ALL DEPENDS ${RESOURCE_PACK})
|
||||||
add_dependencies(${PROJECT_NAME} resource_pack)
|
add_dependencies(${PROJECT_NAME} resource_pack)
|
||||||
|
|
||||||
|
# --- CÒPIA DE gamecontrollerdb.txt AL COSTAT DEL BINARI ---
|
||||||
|
# SDL_AddGamepadMappingsFromFile només llegeix del filesystem real (no del
|
||||||
|
# pack), així que el fitxer ha de viure al directori del binari. Es copia
|
||||||
|
# només si existeix per no fallar la build d'algú que encara no ha fet
|
||||||
|
# `make controllerdb`.
|
||||||
|
if(EXISTS "${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt")
|
||||||
|
set(CONTROLLER_DB "${CMAKE_BINARY_DIR}/gamecontrollerdb.txt")
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${CONTROLLER_DB}
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt"
|
||||||
|
"${CONTROLLER_DB}"
|
||||||
|
DEPENDS "${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt"
|
||||||
|
COMMENT "Copiant gamecontrollerdb.txt → build/"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
add_custom_target(controller_db ALL DEPENDS ${CONTROLLER_DB})
|
||||||
|
add_dependencies(${PROJECT_NAME} controller_db)
|
||||||
|
endif()
|
||||||
|
|
||||||
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic)
|
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic)
|
||||||
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
||||||
$<$<CONFIG:Debug>:_DEBUG>
|
$<$<CONFIG:Debug>:_DEBUG>
|
||||||
@@ -290,10 +356,11 @@ option(BUILD_TESTS "Build smoke tests" ON)
|
|||||||
if(BUILD_TESTS)
|
if(BUILD_TESTS)
|
||||||
add_executable(test_entities_smoke
|
add_executable(test_entities_smoke
|
||||||
tests/test_entities_smoke.cpp
|
tests/test_entities_smoke.cpp
|
||||||
|
tests/hole_player_stubs.cpp
|
||||||
source/core/resources/resource_helper.cpp
|
source/core/resources/resource_helper.cpp
|
||||||
source/core/resources/resource_pack.cpp
|
source/core/resources/resource_pack.cpp
|
||||||
source/game/map/map.cpp
|
source/game/map/map.cpp
|
||||||
${ENTITY_SOURCES}
|
${ENTITY_SOURCES_BASE}
|
||||||
)
|
)
|
||||||
target_include_directories(test_entities_smoke PRIVATE
|
target_include_directories(test_entities_smoke PRIVATE
|
||||||
"${CMAKE_SOURCE_DIR}/source"
|
"${CMAKE_SOURCE_DIR}/source"
|
||||||
@@ -306,6 +373,30 @@ if(BUILD_TESTS)
|
|||||||
COMMAND test_entities_smoke
|
COMMAND test_entities_smoke
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||||
|
|
||||||
|
# Smoke del WhipAttack. Inclou tot l'stack d'entitats + whip_attack.cpp.
|
||||||
|
# `whip_attack_player_stubs.cpp` proveeix símbols mínims de Player per
|
||||||
|
# satisfer el linker sense arrossegar audio/gamepad/screen — els tests
|
||||||
|
# del whip s'enfoquen a hitbox + Mummy, no a friendly fire.
|
||||||
|
add_executable(test_whip_attack
|
||||||
|
tests/test_whip_attack.cpp
|
||||||
|
tests/whip_attack_player_stubs.cpp
|
||||||
|
tests/hole_player_stubs.cpp
|
||||||
|
source/core/resources/resource_helper.cpp
|
||||||
|
source/core/resources/resource_pack.cpp
|
||||||
|
source/game/map/map.cpp
|
||||||
|
${ENTITY_SOURCES_BASE}
|
||||||
|
source/game/entities/concrete/whip_attack.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(test_whip_attack PRIVATE
|
||||||
|
"${CMAKE_SOURCE_DIR}/source"
|
||||||
|
"${CMAKE_BINARY_DIR}"
|
||||||
|
)
|
||||||
|
target_link_libraries(test_whip_attack PRIVATE SDL3::SDL3)
|
||||||
|
target_compile_options(test_whip_attack PRIVATE -Wall -Wextra -Wpedantic)
|
||||||
|
add_test(NAME whip_attack
|
||||||
|
COMMAND test_whip_attack
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||||
|
|
||||||
# Smoke del flux d'escenes i del parser de debug.yaml. Stand-alone:
|
# Smoke del flux d'escenes i del parser de debug.yaml. Stand-alone:
|
||||||
# només enllaça els helpers purs (debug_config, placeholder_input,
|
# només enllaça els helpers purs (debug_config, placeholder_input,
|
||||||
# key_config) perquè la integració completa dels placeholders arrossegaria
|
# key_config) perquè la integració completa dels placeholders arrossegaria
|
||||||
@@ -327,4 +418,60 @@ if(BUILD_TESTS)
|
|||||||
add_test(NAME scene_flow
|
add_test(NAME scene_flow
|
||||||
COMMAND test_scene_flow
|
COMMAND test_scene_flow
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||||
|
|
||||||
|
# Smoke del writer surgical del tilemap (escriu el bloc `data:` sense
|
||||||
|
# tocar la resta del fitxer). Pura manipulació de text, zero
|
||||||
|
# dependències: no cal ni SDL ni el Resource::Cache.
|
||||||
|
add_executable(test_tilemap_writer
|
||||||
|
tests/test_tilemap_writer.cpp
|
||||||
|
source/game/map/tilemap_writer.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(test_tilemap_writer PRIVATE
|
||||||
|
"${CMAKE_SOURCE_DIR}/source"
|
||||||
|
)
|
||||||
|
target_compile_options(test_tilemap_writer PRIVATE -Wall -Wextra -Wpedantic)
|
||||||
|
add_test(NAME tilemap_writer
|
||||||
|
COMMAND test_tilemap_writer
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||||
|
|
||||||
|
# Smoke de la FSM de sala (RoomFsm). Cobreix loadConfig (timings i
|
||||||
|
# corbes des de YAML), advance (transicions entre estats), i
|
||||||
|
# formatRoomTitle (locale + substitució `{n}`). Stand-alone: no
|
||||||
|
# arrossega Audio/Screen/Player/EntityManager — només els helpers
|
||||||
|
# purs de la FSM, Locale i UiConfig.
|
||||||
|
add_executable(test_game_fsm
|
||||||
|
tests/test_game_fsm.cpp
|
||||||
|
source/game/scenes/room_fsm.cpp
|
||||||
|
source/core/locale/locale.cpp
|
||||||
|
source/core/ui/ui_config.cpp
|
||||||
|
source/core/resources/resource_helper.cpp
|
||||||
|
source/core/resources/resource_pack.cpp
|
||||||
|
source/utils/easing.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(test_game_fsm PRIVATE
|
||||||
|
"${CMAKE_SOURCE_DIR}/source"
|
||||||
|
"${CMAKE_BINARY_DIR}"
|
||||||
|
)
|
||||||
|
target_link_libraries(test_game_fsm PRIVATE SDL3::SDL3)
|
||||||
|
target_compile_options(test_game_fsm PRIVATE -Wall -Wextra -Wpedantic)
|
||||||
|
add_test(NAME game_fsm
|
||||||
|
COMMAND test_game_fsm
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||||
|
|
||||||
|
# Smoke del parser d'animacions amb suport de flip H/V. Stand-alone:
|
||||||
|
# només enllaça `animated_sprite_parser.cpp`, que és la part del motor
|
||||||
|
# que NO depèn de Resource::Cache ni de Surface — el parser és pure
|
||||||
|
# YAML-in / dades-out, així que no cal arrossegar tot l'stack.
|
||||||
|
add_executable(test_animated_sprite_flip
|
||||||
|
tests/test_animated_sprite_flip.cpp
|
||||||
|
source/core/rendering/sprite/animated_sprite_parser.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(test_animated_sprite_flip PRIVATE
|
||||||
|
"${CMAKE_SOURCE_DIR}/source"
|
||||||
|
)
|
||||||
|
target_link_libraries(test_animated_sprite_flip PRIVATE SDL3::SDL3)
|
||||||
|
target_compile_options(test_animated_sprite_flip PRIVATE -Wall -Wextra -Wpedantic)
|
||||||
|
add_test(NAME animated_sprite_flip
|
||||||
|
COMMAND test_animated_sprite_flip
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||||
endif()
|
endif()
|
||||||
@@ -1,20 +1,50 @@
|
|||||||
PROJECT := aee_2026
|
PROJECT := aee_2026
|
||||||
BUILDDIR := build
|
BUILDDIR := build
|
||||||
JOBS ?= $(shell nproc 2>/dev/null || echo 4)
|
|
||||||
|
|
||||||
# VERSION (única font de veritat: source/utils/defines.hpp)
|
# Detecció de plataforma. En Windows CMake defaulteja a "NMake Makefiles"
|
||||||
VERSION := v$(shell grep 'constexpr const char\* VERSION' source/utils/defines.hpp | sed -E 's/.*VERSION = "([^"]+)".*/\1/')
|
# (que requereix Visual Studio); forcem MinGW Makefiles per usar el g++ de
|
||||||
|
# MinGW. nproc tampoc existeix en cmd.exe → fem servir NUMBER_OF_PROCESSORS.
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
# Si Make corre dins MSYS2/Git Bash/MinGW, $(shell ...) usa sh.exe i
|
||||||
|
# "NUL" NO és dispositiu — un redirect "2>NUL" crearia un fitxer literal
|
||||||
|
# anomenat NUL al cwd (i en Windows es complica esborrar-lo). Detectem
|
||||||
|
# MSYSTEM per usar /dev/null en aquests entorns; només cmd.exe pur necessita NUL.
|
||||||
|
ifneq ($(MSYSTEM),)
|
||||||
|
NULDEV := /dev/null
|
||||||
|
else
|
||||||
|
NULDEV := NUL
|
||||||
|
endif
|
||||||
|
JOBS ?= $(NUMBER_OF_PROCESSORS)
|
||||||
|
# Si Ninja està al PATH, l'usem (més ràpid i paral·lelitza millor).
|
||||||
|
# Si no, caiem a MinGW Makefiles. Sense aquesta selecció CMake
|
||||||
|
# defaultejaria a "NMake Makefiles", que requereix Visual Studio.
|
||||||
|
HAS_NINJA := $(shell ninja --version 2>$(NULDEV))
|
||||||
|
ifneq ($(HAS_NINJA),)
|
||||||
|
CMAKE_GEN := -G "Ninja"
|
||||||
|
else
|
||||||
|
CMAKE_GEN := -G "MinGW Makefiles"
|
||||||
|
endif
|
||||||
|
else
|
||||||
|
NULDEV := /dev/null
|
||||||
|
JOBS ?= $(shell nproc 2>/dev/null || echo 4)
|
||||||
|
CMAKE_GEN :=
|
||||||
|
endif
|
||||||
|
|
||||||
|
# VERSION (única font de veritat: source/utils/defines.hpp).
|
||||||
|
# Lazy (=): només es calcula quan s'invoca un target que la usa
|
||||||
|
# (show-version/help). Evita executar grep/sed des de cmd.exe en `make debug`.
|
||||||
|
VERSION = v$(shell grep 'constexpr const char\* VERSION' source/utils/defines.hpp | sed -E 's/.*VERSION = "([^"]+)".*/\1/')
|
||||||
|
|
||||||
# GIT_HASH calculat al host i passat a CMake (evita problemes si CMake corre en
|
# GIT_HASH calculat al host i passat a CMake (evita problemes si CMake corre en
|
||||||
# entorns sense accés a git, p.ex. Docker/emscripten).
|
# entorns sense accés a git, p.ex. Docker/emscripten).
|
||||||
GIT_HASH := $(shell git rev-parse --short=7 HEAD 2>/dev/null)
|
GIT_HASH := $(shell git rev-parse --short=7 HEAD 2>$(NULDEV))
|
||||||
ifeq ($(GIT_HASH),)
|
ifeq ($(GIT_HASH),)
|
||||||
GIT_HASH := unknown
|
GIT_HASH := unknown
|
||||||
endif
|
endif
|
||||||
|
|
||||||
CMAKE_DEFS := -DGIT_HASH=$(GIT_HASH)
|
CMAKE_DEFS := -DGIT_HASH=$(GIT_HASH)
|
||||||
|
|
||||||
.PHONY: all debug release run run-debug clean rebuild show-version pack format format-check tidy tidy-fix cppcheck hooks-install help
|
.PHONY: all debug release run run-debug clean rebuild show-version pack controllerdb format format-check tidy tidy-fix cppcheck hooks-install help
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# COMPILACIÓ
|
# COMPILACIÓ
|
||||||
@@ -26,11 +56,11 @@ CMAKE_DEFS := -DGIT_HASH=$(GIT_HASH)
|
|||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
||||||
all:
|
all:
|
||||||
@cmake -S . -B $(BUILDDIR) -DCMAKE_BUILD_TYPE=Release $(CMAKE_DEFS)
|
@cmake -S . -B $(BUILDDIR) $(CMAKE_GEN) -DCMAKE_BUILD_TYPE=Release $(CMAKE_DEFS)
|
||||||
@cmake --build $(BUILDDIR) -j$(JOBS)
|
@cmake --build $(BUILDDIR) -j$(JOBS)
|
||||||
|
|
||||||
debug:
|
debug:
|
||||||
@cmake -S . -B $(BUILDDIR) -DCMAKE_BUILD_TYPE=Debug $(CMAKE_DEFS)
|
@cmake -S . -B $(BUILDDIR) $(CMAKE_GEN) -DCMAKE_BUILD_TYPE=Debug $(CMAKE_DEFS)
|
||||||
@cmake --build $(BUILDDIR) -j$(JOBS)
|
@cmake --build $(BUILDDIR) -j$(JOBS)
|
||||||
|
|
||||||
run: all
|
run: all
|
||||||
@@ -57,31 +87,40 @@ rebuild: clean all
|
|||||||
# del pack encara que res no hagi canviat dins data/. L'empaquetat també es fa
|
# del pack encara que res no hagi canviat dins data/. L'empaquetat també es fa
|
||||||
# automàticament a cada `make`/`make debug` via el target CMake `resource_pack`.
|
# automàticament a cada `make`/`make debug` via el target CMake `resource_pack`.
|
||||||
pack:
|
pack:
|
||||||
@cmake -S . -B $(BUILDDIR) -DCMAKE_BUILD_TYPE=Release $(CMAKE_DEFS)
|
@cmake -S . -B $(BUILDDIR) $(CMAKE_GEN) -DCMAKE_BUILD_TYPE=Release $(CMAKE_DEFS)
|
||||||
@cmake --build $(BUILDDIR) --target pack_resources
|
@cmake --build $(BUILDDIR) --target pack_resources
|
||||||
@./$(BUILDDIR)/pack_resources data $(BUILDDIR)/resources.pack
|
@./$(BUILDDIR)/pack_resources data $(BUILDDIR)/resources.pack
|
||||||
|
|
||||||
|
# Descàrrega de la base de dades de mappings de mandos. SDL només pot
|
||||||
|
# obrir-la des del filesystem (no del pack), per això viu a l'arrel del
|
||||||
|
# projecte i CMake la copia al costat del binari a cada build.
|
||||||
|
controllerdb:
|
||||||
|
@echo "Descarregant gamecontrollerdb.txt..."
|
||||||
|
@curl -fsSL https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/master/gamecontrollerdb.txt \
|
||||||
|
-o gamecontrollerdb.txt
|
||||||
|
@echo "gamecontrollerdb.txt actualitzat"
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# CODE QUALITY (delegados a cmake)
|
# CODE QUALITY (delegados a cmake)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
format:
|
format:
|
||||||
@cmake -S . -B $(BUILDDIR) -DCMAKE_BUILD_TYPE=Release $(CMAKE_DEFS)
|
@cmake -S . -B $(BUILDDIR) $(CMAKE_GEN) -DCMAKE_BUILD_TYPE=Release $(CMAKE_DEFS)
|
||||||
@cmake --build $(BUILDDIR) --target format
|
@cmake --build $(BUILDDIR) --target format
|
||||||
|
|
||||||
format-check:
|
format-check:
|
||||||
@cmake -S . -B $(BUILDDIR) -DCMAKE_BUILD_TYPE=Release $(CMAKE_DEFS)
|
@cmake -S . -B $(BUILDDIR) $(CMAKE_GEN) -DCMAKE_BUILD_TYPE=Release $(CMAKE_DEFS)
|
||||||
@cmake --build $(BUILDDIR) --target format-check
|
@cmake --build $(BUILDDIR) --target format-check
|
||||||
|
|
||||||
tidy:
|
tidy:
|
||||||
@cmake -S . -B $(BUILDDIR) -DCMAKE_BUILD_TYPE=Release $(CMAKE_DEFS)
|
@cmake -S . -B $(BUILDDIR) $(CMAKE_GEN) -DCMAKE_BUILD_TYPE=Release $(CMAKE_DEFS)
|
||||||
@cmake --build $(BUILDDIR) --target tidy
|
@cmake --build $(BUILDDIR) --target tidy
|
||||||
|
|
||||||
tidy-fix:
|
tidy-fix:
|
||||||
@cmake -S . -B $(BUILDDIR) -DCMAKE_BUILD_TYPE=Release $(CMAKE_DEFS)
|
@cmake -S . -B $(BUILDDIR) $(CMAKE_GEN) -DCMAKE_BUILD_TYPE=Release $(CMAKE_DEFS)
|
||||||
@cmake --build $(BUILDDIR) --target tidy-fix
|
@cmake --build $(BUILDDIR) --target tidy-fix
|
||||||
|
|
||||||
cppcheck:
|
cppcheck:
|
||||||
@cmake -S . -B $(BUILDDIR) -DCMAKE_BUILD_TYPE=Release $(CMAKE_DEFS)
|
@cmake -S . -B $(BUILDDIR) $(CMAKE_GEN) -DCMAKE_BUILD_TYPE=Release $(CMAKE_DEFS)
|
||||||
@cmake --build $(BUILDDIR) --target cppcheck
|
@cmake --build $(BUILDDIR) --target cppcheck
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
@@ -110,6 +149,7 @@ help:
|
|||||||
@echo ""
|
@echo ""
|
||||||
@echo " Recursos:"
|
@echo " Recursos:"
|
||||||
@echo " make pack - Empaquetar data/ a $(BUILDDIR)/resources.pack (format AE26)"
|
@echo " make pack - Empaquetar data/ a $(BUILDDIR)/resources.pack (format AE26)"
|
||||||
|
@echo " make controllerdb - Descarregar gamecontrollerdb.txt actualitzat (arrel)"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo " Qualitat de codi:"
|
@echo " Qualitat de codi:"
|
||||||
@echo " make format - Formatar codi amb clang-format"
|
@echo " make format - Formatar codi amb clang-format"
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Tema visual de l'editor de tilemap (només Debug). Carregat per
|
||||||
|
# EditorConfig a l'arrencada; les claus absents cauen als valors compilats
|
||||||
|
# a Defaults::Editor (veure source/utils/defaults.hpp).
|
||||||
|
|
||||||
|
picker:
|
||||||
|
margin: 8 # px entre canvas i caixa del picker
|
||||||
|
spacing: 1 # px de separació entre tiles dins del picker
|
||||||
|
|
||||||
|
# Colors ARGB en hex. Accepta enter (0xFF2244FF) o cadena ("#FF2244FF").
|
||||||
|
colors:
|
||||||
|
border: "0xFF2244FF" # vora 1 px que envolta canvas i picker
|
||||||
|
picker_bg: "0xFF101824" # fons del picker
|
||||||
|
picker_hover: "0xFFFFFFFF" # vora 1 px del tile sota el cursor
|
||||||
@@ -1,9 +1,22 @@
|
|||||||
name: door
|
name: door
|
||||||
type: interactable
|
type: interactable
|
||||||
|
editable: true
|
||||||
sprite:
|
sprite:
|
||||||
path: door.gif
|
path: door.gif
|
||||||
size: [48, 32]
|
size: [48, 32]
|
||||||
tile_size: [3, 2]
|
tile_size: [3, 2]
|
||||||
|
collision:
|
||||||
|
# Caixa de col·lisió: 16×24 centrada al sprite 48×32. Coincideix amb el
|
||||||
|
# tile central transitable (els 16 px del passadís de sortida) i es
|
||||||
|
# restringeix a la franja del mur trasser (24 px d'alçada). Així
|
||||||
|
# `checkPlayerCollisions` només dispara `onPlayerContact` quan el
|
||||||
|
# jugador entra al forat real, no als marcs flanquejants ni als 8 px
|
||||||
|
# superiors que queden tapats pel HUD.
|
||||||
|
size: [16, 24]
|
||||||
|
offset: [16, 8]
|
||||||
|
# Porta = mur infranquejable. Igual que el sarc, alçada efectiva
|
||||||
|
# infinita per impedir que un jugador la salte per damunt.
|
||||||
|
height: 999
|
||||||
flags:
|
flags:
|
||||||
collides: true
|
collides: true
|
||||||
interactable: true
|
interactable: true
|
||||||
|
|||||||
|
After Width: | Height: | Size: 306 B |
@@ -0,0 +1,22 @@
|
|||||||
|
name: dust
|
||||||
|
type: effect
|
||||||
|
sprite:
|
||||||
|
path: dust.gif
|
||||||
|
size: [40, 16]
|
||||||
|
|
||||||
|
# El polvet és purament visual: no col·lisiona, no interactua, no fa
|
||||||
|
# dany. Només viu mentre dura l'animació triada (`jump` o `land`) i
|
||||||
|
# desapareix per `markDead` quan acaba.
|
||||||
|
flags:
|
||||||
|
collides: false
|
||||||
|
interactable: false
|
||||||
|
|
||||||
|
# Dues animacions, no-loop: una per a cada moment del salt. El spawner
|
||||||
|
# (GameScene, en resposta a events del bus) instancia la variant
|
||||||
|
# apropiada via `setVariant` abans del primer tick.
|
||||||
|
# jump: frames 0..6 (7 frames) → reproduït al despegue.
|
||||||
|
# land: frames 7..12 (6 frames) → reproduït a l'aterratge.
|
||||||
|
# fps: 10 = 100 ms per frame.
|
||||||
|
animations:
|
||||||
|
jump: { frames: [0, 1, 2, 3, 4, 5, 6], fps: 15, loop: false }
|
||||||
|
land: { frames: [7, 8, 9, 10, 11, 12], fps: 15, loop: false }
|
||||||
@@ -4,6 +4,11 @@ sprite:
|
|||||||
path: floor_trap.gif
|
path: floor_trap.gif
|
||||||
size: [16, 16]
|
size: [16, 16]
|
||||||
tile_size: [1, 1]
|
tile_size: [1, 1]
|
||||||
|
collision:
|
||||||
|
# Trampa al sòl: alçada 0 → qualsevol salt l'esquiva. (Avui amb
|
||||||
|
# `collides: false` la trampa no entra al filtre de contacte, però
|
||||||
|
# declarem el camp per quan es cablege la detecció via àrea.)
|
||||||
|
height: 0
|
||||||
flags:
|
flags:
|
||||||
collides: false
|
collides: false
|
||||||
interactable: false
|
interactable: false
|
||||||
|
|||||||
|
After Width: | Height: | Size: 182 B |
@@ -0,0 +1,27 @@
|
|||||||
|
name: hole
|
||||||
|
type: static_hazard
|
||||||
|
editable: true
|
||||||
|
sprite:
|
||||||
|
path: hole.gif
|
||||||
|
size: [16, 16]
|
||||||
|
tile_size: [1, 1]
|
||||||
|
collision:
|
||||||
|
# Forat: ocupa el tile sencer (16×16). `height: 0` deixa que un salt
|
||||||
|
# AIRBORNE el creui sense disparar el contacte (el filtre d'altura de
|
||||||
|
# `checkPlayerCollisions` el descarta quan `jump_z_ > height`). El
|
||||||
|
# bucle de contacte admet l'entitat per `flags.triggers_on_overlap`,
|
||||||
|
# encara que `flags.collides` sigui false.
|
||||||
|
size: [16, 16]
|
||||||
|
height: 0
|
||||||
|
flags:
|
||||||
|
collides: false
|
||||||
|
interactable: false
|
||||||
|
# Avui el loader no llig el bloc `flags:` (és declaratiu); el flag
|
||||||
|
# real es fixa al constructor C++ de `Hole`. Mantenim el camp per
|
||||||
|
# coherència amb la resta de plantilles i per quan es cablege la
|
||||||
|
# lectura genèrica de flags.
|
||||||
|
triggers_on_overlap: true
|
||||||
|
animations:
|
||||||
|
idle: { frames: [0], fps: 1, loop: true }
|
||||||
|
stats:
|
||||||
|
damage: 0
|
||||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,35 +1,60 @@
|
|||||||
name: mummy
|
name: mummy
|
||||||
type: dynamic_hazard
|
type: dynamic_hazard
|
||||||
|
editable: true
|
||||||
sprite:
|
sprite:
|
||||||
path: mummy.gif
|
path: mummy.gif
|
||||||
size: [12, 24]
|
size: [16, 32]
|
||||||
tile_size: [1, 1]
|
tile_size: [1, 1]
|
||||||
# La momia usa falsa perspectiva: el sprite fa 12×24 (dos 12×12: cap i
|
# Variant 16×32 de la momia: el sprite fa 16×32 (dos 16×16: cap i peus)
|
||||||
# peus) però la caja de col·lisió amb el mapa és 16×16, concèntrica amb
|
# i la caja de col·lisió amb el mapa és 16×16, coincident amb el
|
||||||
# el rectangle-base 12×12 dels peus (igual que fa el jugador). Així la
|
# rectangle-base 16×16 dels peus. Com que `size` i `base_size` són
|
||||||
# caixa s'estén 2 px a cada costat i 2 px per sota dels peus visuals,
|
# iguals, el padding és zero i la caja s'alinea exactament amb la
|
||||||
# i amb `anchor: feet` s'alinea exactament amb el tile 16×16 on naix —
|
# silueta inferior del sprite; amb `anchor: feet` ocupa la cel·la
|
||||||
# la momia va pel centre del corredor i la caja ocupa la cel·la completa.
|
# 16×16 del corredor on naix.
|
||||||
anchor: feet
|
anchor: feet
|
||||||
collision:
|
collision:
|
||||||
size: [16, 16]
|
size: [16, 16]
|
||||||
base_size: [12, 12]
|
base_size: [16, 16]
|
||||||
|
# Alçada del cos en píxels per al filtre de col·lisió per salt: un
|
||||||
|
# jugador amb jump_z > height no col·lisiona amb la momia (la salta
|
||||||
|
# per damunt). Amb impulse=240 + gravity=600 el pic ronda els 48 px,
|
||||||
|
# així que 24 deixa marge per esquivar amb un salt no perfecte.
|
||||||
|
height: 12
|
||||||
flags:
|
flags:
|
||||||
collides: true
|
collides: true
|
||||||
interactable: false
|
interactable: false
|
||||||
# Strip de 12 frames en graella 12×1 (mateix esquema que el jugador):
|
# Strip de 12 frames en graella 4×3 (mateix esquema que el jugador):
|
||||||
# 0..3 → walk_down (cara avall)
|
# 0..3 → walk_south (cara avall)
|
||||||
# 4..7 → walk_up (cara amunt)
|
# 4..7 → walk_north (cara amunt)
|
||||||
# 8..11 → walk (lateral; flip horitzontal segons el sentit)
|
# 8..11 → walk_east (lateral cap a l'est)
|
||||||
|
# La variant `walk_west` reaprofita els frames de l'est amb `flip_h: true`:
|
||||||
|
# el motor d'animació aplica el flip al sprite, així el codi de la mòmia
|
||||||
|
# no decideix flip per direcció (només selecciona quina animació mostrar).
|
||||||
# La momia no té variants `stand_*` perquè mai no para.
|
# La momia no té variants `stand_*` perquè mai no para.
|
||||||
animations:
|
animations:
|
||||||
walk_down: { frames: [0, 1, 2, 3], fps: 8, loop: true }
|
walk_south: { frames: [0, 1, 2, 3], fps: 8, loop: true }
|
||||||
walk_up: { frames: [4, 5, 6, 7], fps: 8, loop: true }
|
walk_north: { frames: [4, 5, 6, 7], fps: 8, loop: true }
|
||||||
walk: { frames: [8, 9, 10, 11], fps: 8, loop: true }
|
walk_east: { frames: [8, 9, 10, 11], fps: 8, loop: true }
|
||||||
|
walk_west: { frames: [8, 9, 10, 11], fps: 8, loop: true, flip_h: true }
|
||||||
ai:
|
ai:
|
||||||
type: corridor_wanderer
|
type: corridor_wanderer
|
||||||
direction_choice: random
|
direction_choice: random
|
||||||
initial_direction: right
|
initial_direction: east
|
||||||
stats:
|
stats:
|
||||||
speed: 30
|
speed: 30
|
||||||
damage: 1
|
damage: 1
|
||||||
|
# Quan true, el contacte amb el jugador no aplica `hurt` ni knockback:
|
||||||
|
# mata d'un sol toc i dispara el cicle DYING → respawn. El path `hurt`
|
||||||
|
# del jugador segueix funcionant per a futurs hazards no-letals (avui
|
||||||
|
# no n'hi ha cap que el dispari).
|
||||||
|
kills_outright: true
|
||||||
|
|
||||||
|
# Estat HURT: post-fuetada, fugint en línia recta. Si la direcció
|
||||||
|
# preferida (la del whip) està bloquejada, prova perpendiculars; si totes
|
||||||
|
# ho estan, queda quieta tot el `duration_ms` i després torna a ROAMING.
|
||||||
|
# `speed` opcional: si no apareix, s'usa `stats.speed`. El `whippable`
|
||||||
|
# habilita la reacció al látigo (false ⇒ el whip la ignora).
|
||||||
|
hurt:
|
||||||
|
duration_ms: 600
|
||||||
|
speed: 60
|
||||||
|
whippable: true
|
||||||
@@ -4,6 +4,11 @@ sprite:
|
|||||||
path: rolling_rock.gif
|
path: rolling_rock.gif
|
||||||
size: [16, 16]
|
size: [16, 16]
|
||||||
tile_size: [1, 1]
|
tile_size: [1, 1]
|
||||||
|
collision:
|
||||||
|
# `size`/`base_size` no calen per a la roca (tot el sprite és cos),
|
||||||
|
# però sí declarem `height` per al filtre de salt: 16 px → fàcil
|
||||||
|
# d'esquivar amb un salt curt.
|
||||||
|
height: 16
|
||||||
flags:
|
flags:
|
||||||
collides: true
|
collides: true
|
||||||
interactable: false
|
interactable: false
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
name: sarcophagus
|
name: sarcophagus
|
||||||
type: interactable
|
type: interactable
|
||||||
|
editable: true
|
||||||
sprite:
|
sprite:
|
||||||
path: sarcophagus.gif
|
path: sarcophagus.gif
|
||||||
size: [48, 32]
|
size: [48, 32]
|
||||||
@@ -7,6 +8,13 @@ sprite:
|
|||||||
# són "alçada" en falsa perspectiva, sense col·lisió. Perímetre ortogonal
|
# són "alçada" en falsa perspectiva, sense col·lisió. Perímetre ortogonal
|
||||||
# de 8 tiles (3 N + 3 S + 1 O + 1 E).
|
# de 8 tiles (3 N + 3 S + 1 O + 1 E).
|
||||||
tile_size: [3, 1]
|
tile_size: [3, 1]
|
||||||
|
collision:
|
||||||
|
# Sarcòfag = obstacle massís: alçada efectiva infinita (999) per al
|
||||||
|
# filtre del salt. Un jugador no el pot saltar per damunt encara que
|
||||||
|
# el salt arribe al punt àlgid. La caixa de col·lisió real (logical
|
||||||
|
# 48×16) la calcula `placeSarcophagus` a partir de la geometria del
|
||||||
|
# mapa, no des d'aquest YAML.
|
||||||
|
height: 999
|
||||||
flags:
|
flags:
|
||||||
collides: true
|
collides: true
|
||||||
interactable: true
|
interactable: true
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
name: torch
|
name: torch
|
||||||
type: decoration
|
type: decoration
|
||||||
|
editable: true
|
||||||
sprite:
|
sprite:
|
||||||
path: torch.gif
|
path: torch.gif
|
||||||
size: [16, 16]
|
size: [16, 16]
|
||||||
tile_size: [1, 1]
|
tile_size: [1, 1]
|
||||||
|
collision:
|
||||||
|
# Decoratiu, no col·lisiona. Camp `height` declarat per coherència
|
||||||
|
# amb la resta d'entitats; no té efecte mentre `collides: false`.
|
||||||
|
height: 0
|
||||||
flags:
|
flags:
|
||||||
collides: false
|
collides: false
|
||||||
interactable: false
|
interactable: false
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ sprite:
|
|||||||
path: wall_trap.gif
|
path: wall_trap.gif
|
||||||
size: [16, 16]
|
size: [16, 16]
|
||||||
tile_size: [1, 1]
|
tile_size: [1, 1]
|
||||||
|
collision:
|
||||||
|
# Trampa de paret: ix d'una vora alta i no es pot saltar per damunt.
|
||||||
|
# 999 = infranquejable. Igual que floor_trap, el filtre només té efecte
|
||||||
|
# quan la detecció de contacte hi entre.
|
||||||
|
height: 999
|
||||||
flags:
|
flags:
|
||||||
collides: false
|
collides: false
|
||||||
interactable: false
|
interactable: false
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
name: whip_attack
|
||||||
|
|
||||||
|
# Quatre sprites distints, un per cardinal. El sistema d'animació actual
|
||||||
|
# només permet UNA mida de frame per surface, i els sprites del látigo
|
||||||
|
# tenen aspect ratios distints segons direcció. Per això són 4 fitxers
|
||||||
|
# en lloc d'una sola sheet compartida amb flips.
|
||||||
|
#
|
||||||
|
# Mides (= `draw_rect` del WhipAttack, que NO es retalla per
|
||||||
|
# bloquejadors):
|
||||||
|
# E/W → 80×32 px per frame: 16 px de wind-up darrere del jugador
|
||||||
|
# + 16 px de solapament + 48 px d'atac
|
||||||
|
# frontal. Cobreix tota l'altura del jugador.
|
||||||
|
# N/S → 16×96 px per frame: 48 px d'atac + 32 px solapant amb el jugador
|
||||||
|
# + 16 px de wind-up "darrere".
|
||||||
|
#
|
||||||
|
# La caja de DANY és independent i més petita (48×16 / 16×48), pegada
|
||||||
|
# al borde del jugador i retallada per murs/sarcs. La calcula
|
||||||
|
# `WhipAttack::configure` a partir del `owner_box` del payload.
|
||||||
|
sprites:
|
||||||
|
east: { path: whip_east.gif, size: [80, 32] }
|
||||||
|
west: { path: whip_west.gif, size: [80, 32] }
|
||||||
|
north: { path: whip_north.gif, size: [16, 96] }
|
||||||
|
south: { path: whip_south.gif, size: [16, 96] }
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
# - `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
|
||||||
|
# danyen — només els frames "actius" 1 i 2.
|
||||||
|
animations:
|
||||||
|
swing:
|
||||||
|
frames: [0, 1, 2, 3]
|
||||||
|
frame_ms: 50
|
||||||
|
loop: false
|
||||||
|
active_frames: [1, 2]
|
||||||
|
After Width: | Height: | Size: 313 B |
|
After Width: | Height: | Size: 378 B |
|
After Width: | Height: | Size: 369 B |
|
After Width: | Height: | Size: 313 B |
@@ -0,0 +1,132 @@
|
|||||||
|
# Font: CaesarDressing-Regular — generado desde CaesarDressing-Regular.ttf size 24
|
||||||
|
# Generado con font_gen.py
|
||||||
|
|
||||||
|
box_width 18
|
||||||
|
box_height 28
|
||||||
|
columns 15
|
||||||
|
|
||||||
|
# codepoint_decimal ancho_visual
|
||||||
|
32 8 # U+0020
|
||||||
|
33 5 # !
|
||||||
|
34 6 # "
|
||||||
|
35 14 # #
|
||||||
|
36 7 # $
|
||||||
|
37 17 # %
|
||||||
|
38 9 # &
|
||||||
|
39 3 # '
|
||||||
|
40 10 # (
|
||||||
|
41 8 # )
|
||||||
|
42 10 # *
|
||||||
|
43 11 # +
|
||||||
|
44 5 # ,
|
||||||
|
45 10 # -
|
||||||
|
46 3 # .
|
||||||
|
47 9 # /
|
||||||
|
48 12 # 0
|
||||||
|
49 6 # 1
|
||||||
|
50 10 # 2
|
||||||
|
51 9 # 3
|
||||||
|
52 12 # 4
|
||||||
|
53 11 # 5
|
||||||
|
54 13 # 6
|
||||||
|
55 11 # 7
|
||||||
|
56 13 # 8
|
||||||
|
57 13 # 9
|
||||||
|
58 5 # :
|
||||||
|
59 4 # ;
|
||||||
|
60 11 # <
|
||||||
|
61 11 # =
|
||||||
|
62 11 # >
|
||||||
|
63 11 # ?
|
||||||
|
64 14 # @
|
||||||
|
65 14 # A
|
||||||
|
66 12 # B
|
||||||
|
67 11 # C
|
||||||
|
68 12 # D
|
||||||
|
69 10 # E
|
||||||
|
70 10 # F
|
||||||
|
71 15 # G
|
||||||
|
72 12 # H
|
||||||
|
73 9 # I
|
||||||
|
74 9 # J
|
||||||
|
75 13 # K
|
||||||
|
76 10 # L
|
||||||
|
77 18 # M
|
||||||
|
78 12 # N
|
||||||
|
79 13 # O
|
||||||
|
80 12 # P
|
||||||
|
81 12 # Q
|
||||||
|
82 13 # R
|
||||||
|
83 9 # S
|
||||||
|
84 12 # T
|
||||||
|
85 11 # U
|
||||||
|
86 12 # V
|
||||||
|
87 17 # W
|
||||||
|
88 11 # X
|
||||||
|
89 12 # Y
|
||||||
|
90 10 # Z
|
||||||
|
91 8 # [
|
||||||
|
92 8 # \
|
||||||
|
93 7 # ]
|
||||||
|
94 11 # ^
|
||||||
|
95 13 # _
|
||||||
|
96 7 # `
|
||||||
|
97 13 # a
|
||||||
|
98 12 # b
|
||||||
|
99 11 # c
|
||||||
|
100 12 # d
|
||||||
|
101 10 # e
|
||||||
|
102 10 # f
|
||||||
|
103 15 # g
|
||||||
|
104 12 # h
|
||||||
|
105 5 # i
|
||||||
|
106 9 # j
|
||||||
|
107 13 # k
|
||||||
|
108 10 # l
|
||||||
|
109 18 # m
|
||||||
|
110 12 # n
|
||||||
|
111 13 # o
|
||||||
|
112 12 # p
|
||||||
|
113 12 # q
|
||||||
|
114 12 # r
|
||||||
|
115 10 # s
|
||||||
|
116 12 # t
|
||||||
|
117 11 # u
|
||||||
|
118 12 # v
|
||||||
|
119 17 # w
|
||||||
|
120 11 # x
|
||||||
|
121 12 # y
|
||||||
|
122 10 # z
|
||||||
|
123 9 # {
|
||||||
|
124 5 # |
|
||||||
|
125 9 # }
|
||||||
|
126 14 # ~
|
||||||
|
192 14 # À
|
||||||
|
193 14 # Á
|
||||||
|
200 10 # È
|
||||||
|
201 10 # É
|
||||||
|
204 9 # Ì
|
||||||
|
205 9 # Í
|
||||||
|
210 13 # Ò
|
||||||
|
211 13 # Ó
|
||||||
|
217 11 # Ù
|
||||||
|
218 11 # Ú
|
||||||
|
209 12 # Ñ
|
||||||
|
199 11 # Ç
|
||||||
|
224 13 # à
|
||||||
|
225 13 # á
|
||||||
|
232 10 # è
|
||||||
|
233 11 # é
|
||||||
|
236 7 # ì
|
||||||
|
237 8 # í
|
||||||
|
242 13 # ò
|
||||||
|
243 13 # ó
|
||||||
|
249 11 # ù
|
||||||
|
250 11 # ú
|
||||||
|
241 12 # ñ
|
||||||
|
231 11 # ç
|
||||||
|
161 5 # ¡
|
||||||
|
191 10 # ¿
|
||||||
|
171 11 # «
|
||||||
|
187 11 # »
|
||||||
|
183 3 # ·
|
||||||
|
After Width: | Height: | Size: 3.6 KiB |
@@ -29,15 +29,42 @@ keys:
|
|||||||
- id: toggle_vsync
|
- id: toggle_vsync
|
||||||
code: "F8"
|
code: "F8"
|
||||||
desc: "Activa / desactiva VSync"
|
desc: "Activa / desactiva VSync"
|
||||||
- id: menu_toggle
|
- id: screenshot
|
||||||
code: "F9"
|
code: "F9"
|
||||||
desc: "Obri / tanca el menú de servei"
|
desc: "Captura de pantalla (PNG)"
|
||||||
- id: toggle_render_info
|
- id: toggle_render_info
|
||||||
code: "F12"
|
code: "F10"
|
||||||
desc: "Cicle de l'overlay de debug: apagat / dalt / baix"
|
desc: "Cicle de l'overlay de debug: apagat / dalt / baix"
|
||||||
- id: toggle_render_time
|
- id: pause
|
||||||
code: "F11"
|
code: "F11"
|
||||||
desc: "Mostra o amaga el temps de joc dins de l'overlay"
|
desc: "Pausa el joc (o botó START del mando)"
|
||||||
|
- id: menu_toggle
|
||||||
|
code: "F12"
|
||||||
|
desc: "Obri / tanca el menú de servei"
|
||||||
|
- id: editor_toggle
|
||||||
|
code: "9"
|
||||||
|
desc: "Entra/surt del mode editor (només Debug)"
|
||||||
|
- id: editor_tile_picker
|
||||||
|
code: "T"
|
||||||
|
desc: "Obri/tanca el tilepicker dins de l'editor"
|
||||||
|
- id: editor_entity_picker
|
||||||
|
code: "E"
|
||||||
|
desc: "Obri/tanca l'entity picker dins de l'editor"
|
||||||
|
- id: editor_hand
|
||||||
|
code: "H"
|
||||||
|
desc: "Activa el mode mà de l'editor (selecciona/mou/borra entitats)"
|
||||||
|
- id: editor_delete
|
||||||
|
code: "Delete"
|
||||||
|
desc: "Esborra l'entitat seleccionada en mode mà"
|
||||||
|
- id: prev_room
|
||||||
|
code: "["
|
||||||
|
desc: "Salta a la sala anterior del levels.yaml (només Debug)"
|
||||||
|
- id: next_room
|
||||||
|
code: "]"
|
||||||
|
desc: "Salta a la sala següent del levels.yaml (només Debug)"
|
||||||
|
- id: open_door
|
||||||
|
code: "Keypad 1"
|
||||||
|
desc: "Força l'obertura de la porta de la sala (només Debug)"
|
||||||
- id: exit
|
- id: exit
|
||||||
code: "Escape"
|
code: "Escape"
|
||||||
desc: "Eixir (confirmar amb segona pulsació)"
|
desc: "Eixir (confirmar amb segona pulsació)"
|
||||||
@@ -14,6 +14,7 @@ input:
|
|||||||
shader_off: "Shaders desactivats"
|
shader_off: "Shaders desactivats"
|
||||||
vsync_on: "VSync activat"
|
vsync_on: "VSync activat"
|
||||||
vsync_off: "VSync desactivat"
|
vsync_off: "VSync desactivat"
|
||||||
|
screenshot: "Captura"
|
||||||
shader: "Shader"
|
shader: "Shader"
|
||||||
preset: "Preset"
|
preset: "Preset"
|
||||||
scaling_disabled: "Escalat sense"
|
scaling_disabled: "Escalat sense"
|
||||||
@@ -22,6 +23,32 @@ input:
|
|||||||
scaling_overscan: "Escalat overscan"
|
scaling_overscan: "Escalat overscan"
|
||||||
scaling_integer: "Escalat enter"
|
scaling_integer: "Escalat enter"
|
||||||
press_again_exit: "Prem una altra volta per eixir"
|
press_again_exit: "Prem una altra volta per eixir"
|
||||||
|
paused: "Pausa"
|
||||||
|
editor_on: "Editor ON"
|
||||||
|
editor_off: "Editor OFF"
|
||||||
|
editor_save_error: "Editor: error guardant el mapa"
|
||||||
|
editor_keep_door: "Editor: cal almenys una porta"
|
||||||
|
editor_keep_sarc: "Editor: cal almenys un sarcòfag"
|
||||||
|
editor_invalid_level: "Editor: nivell incomplet (porta i sarcs)"
|
||||||
|
|
||||||
|
hud:
|
||||||
|
p1: "P1"
|
||||||
|
p2: "P2"
|
||||||
|
lives: "Vides"
|
||||||
|
treasures: "Tres"
|
||||||
|
pyramid: "Piramide"
|
||||||
|
room: "Sala"
|
||||||
|
|
||||||
|
# Textos de la transició d'entrada/sortida de sala (FSM de GameScene).
|
||||||
|
# Tant l'INTRO com l'OUTRO es renderitzen com a dues línies separades
|
||||||
|
# (cadascuna amb la seua animació). L'INTRO accepta els placeholders
|
||||||
|
# `{p}` (piràmide) i `{r}` (sala); l'OUTRO no en té cap — són les
|
||||||
|
# dues paraules del rètol "fase completada".
|
||||||
|
game:
|
||||||
|
room_intro_pyramid: "Piramide {p}"
|
||||||
|
room_intro_room: "Sala {r}"
|
||||||
|
room_complete_line1: "Fase"
|
||||||
|
room_complete_line2: "Completada"
|
||||||
|
|
||||||
menu:
|
menu:
|
||||||
titles:
|
titles:
|
||||||
@@ -34,6 +61,7 @@ menu:
|
|||||||
system: "Sistema"
|
system: "Sistema"
|
||||||
labels:
|
labels:
|
||||||
player_1: "Jugador 1"
|
player_1: "Jugador 1"
|
||||||
|
player_2: "Jugador 2"
|
||||||
gamepad_unavailable: "No disponible"
|
gamepad_unavailable: "No disponible"
|
||||||
items:
|
items:
|
||||||
video_submenu: "Vídeo"
|
video_submenu: "Vídeo"
|
||||||
@@ -46,6 +74,13 @@ menu:
|
|||||||
move_down: "Avall"
|
move_down: "Avall"
|
||||||
move_left: "Esquerra"
|
move_left: "Esquerra"
|
||||||
move_right: "Dreta"
|
move_right: "Dreta"
|
||||||
|
jump: "Saltar"
|
||||||
|
attack: "Atacar"
|
||||||
|
service: "Opcions"
|
||||||
|
gamepad_p1: "Comandament P1"
|
||||||
|
gamepad_p2: "Comandament P2"
|
||||||
|
define_buttons_p1: "Definir botons P1"
|
||||||
|
define_buttons_p2: "Definir botons P2"
|
||||||
zoom: "Zoom"
|
zoom: "Zoom"
|
||||||
screen: "Pantalla completa"
|
screen: "Pantalla completa"
|
||||||
vsync: "VSync"
|
vsync: "VSync"
|
||||||
@@ -75,3 +110,9 @@ menu:
|
|||||||
render_info_off: "Apagat"
|
render_info_off: "Apagat"
|
||||||
render_info_top: "Dalt"
|
render_info_top: "Dalt"
|
||||||
render_info_bottom: "Baix"
|
render_info_bottom: "Baix"
|
||||||
|
gamepad_none: "Cap"
|
||||||
|
gamepad_disconnected: "Desconnectat"
|
||||||
|
press_key: "Polsa una tecla"
|
||||||
|
press_button: "Polsa un botó"
|
||||||
|
define_buttons_complete: "Configuració completa"
|
||||||
|
define_buttons_no_pad: "Cap mando assignat"
|
||||||
@@ -14,6 +14,7 @@ input:
|
|||||||
shader_off: "Shaders off"
|
shader_off: "Shaders off"
|
||||||
vsync_on: "VSync on"
|
vsync_on: "VSync on"
|
||||||
vsync_off: "VSync off"
|
vsync_off: "VSync off"
|
||||||
|
screenshot: "Screenshot"
|
||||||
shader: "Shader"
|
shader: "Shader"
|
||||||
preset: "Preset"
|
preset: "Preset"
|
||||||
scaling_disabled: "Scaling off"
|
scaling_disabled: "Scaling off"
|
||||||
@@ -22,6 +23,31 @@ input:
|
|||||||
scaling_overscan: "Scaling overscan"
|
scaling_overscan: "Scaling overscan"
|
||||||
scaling_integer: "Scaling integer"
|
scaling_integer: "Scaling integer"
|
||||||
press_again_exit: "Press again to exit"
|
press_again_exit: "Press again to exit"
|
||||||
|
paused: "Paused"
|
||||||
|
editor_on: "Editor ON"
|
||||||
|
editor_off: "Editor OFF"
|
||||||
|
editor_save_error: "Editor: map save failed"
|
||||||
|
editor_keep_door: "Editor: at least one door required"
|
||||||
|
editor_keep_sarc: "Editor: at least one sarcophagus required"
|
||||||
|
editor_invalid_level: "Editor: level incomplete (door and sarcs)"
|
||||||
|
|
||||||
|
hud:
|
||||||
|
p1: "P1"
|
||||||
|
p2: "P2"
|
||||||
|
lives: "Lives"
|
||||||
|
treasures: "Tre"
|
||||||
|
pyramid: "Pyramid"
|
||||||
|
room: "Room"
|
||||||
|
|
||||||
|
# Room transition texts (GameScene FSM intro/outro). Both INTRO and
|
||||||
|
# OUTRO render as two independent lines with their own animation.
|
||||||
|
# INTRO uses `{p}` (pyramid) and `{r}` (room) placeholders; OUTRO has
|
||||||
|
# no placeholders — it's the two words of the "stage complete" label.
|
||||||
|
game:
|
||||||
|
room_intro_pyramid: "Pyramid {p}"
|
||||||
|
room_intro_room: "Room {r}"
|
||||||
|
room_complete_line1: "Stage"
|
||||||
|
room_complete_line2: "Complete"
|
||||||
|
|
||||||
menu:
|
menu:
|
||||||
titles:
|
titles:
|
||||||
@@ -34,6 +60,7 @@ menu:
|
|||||||
system: "System"
|
system: "System"
|
||||||
labels:
|
labels:
|
||||||
player_1: "Player 1"
|
player_1: "Player 1"
|
||||||
|
player_2: "Player 2"
|
||||||
gamepad_unavailable: "Not available"
|
gamepad_unavailable: "Not available"
|
||||||
items:
|
items:
|
||||||
video_submenu: "Video"
|
video_submenu: "Video"
|
||||||
@@ -46,6 +73,13 @@ menu:
|
|||||||
move_down: "Down"
|
move_down: "Down"
|
||||||
move_left: "Left"
|
move_left: "Left"
|
||||||
move_right: "Right"
|
move_right: "Right"
|
||||||
|
jump: "Jump"
|
||||||
|
attack: "Attack"
|
||||||
|
service: "Options"
|
||||||
|
gamepad_p1: "Gamepad P1"
|
||||||
|
gamepad_p2: "Gamepad P2"
|
||||||
|
define_buttons_p1: "Define buttons P1"
|
||||||
|
define_buttons_p2: "Define buttons P2"
|
||||||
zoom: "Zoom"
|
zoom: "Zoom"
|
||||||
screen: "Fullscreen"
|
screen: "Fullscreen"
|
||||||
vsync: "VSync"
|
vsync: "VSync"
|
||||||
@@ -75,3 +109,9 @@ menu:
|
|||||||
render_info_off: "Off"
|
render_info_off: "Off"
|
||||||
render_info_top: "Top"
|
render_info_top: "Top"
|
||||||
render_info_bottom: "Bottom"
|
render_info_bottom: "Bottom"
|
||||||
|
gamepad_none: "None"
|
||||||
|
gamepad_disconnected: "Disconnected"
|
||||||
|
press_key: "Press a key"
|
||||||
|
press_button: "Press a button"
|
||||||
|
define_buttons_complete: "Configuration complete"
|
||||||
|
define_buttons_no_pad: "No gamepad assigned"
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Índex ordenat de les 30 sales del joc (5 piràmides × 6 sales). El
|
||||||
|
# LevelManager carrega aquesta llista a l'arrencada i la fa servir com a
|
||||||
|
# font de veritat per a `currentPath()`, `advance()`, `setIndex()` i la
|
||||||
|
# resta de consultes que li facin la GameScene, l'Editor i els salts de
|
||||||
|
# debug. Per reordenar la progressió, canvia l'ordre d'aquesta seqüència
|
||||||
|
# (no cal renombrar fitxers). Per afegir una sala, crea el YAML i posa
|
||||||
|
# una entrada nova; els camps `pyramid` i `room` són purament informatius
|
||||||
|
# (apareixen als logs i al text d'intro de sala) — l'únic camp que
|
||||||
|
# realment carrega l'engine és `file`.
|
||||||
|
#
|
||||||
|
# Convenció de nomenclatura del fitxer: `data/maps/p<N>/r<M>.yaml` per
|
||||||
|
# dins la piràmide N, sala M. La carpeta `p<N>/` agruparà més endavant
|
||||||
|
# les metadades comunes de la piràmide (tileset, paleta, música).
|
||||||
|
|
||||||
|
sequence:
|
||||||
|
- { pyramid: 1, room: 1, file: data/maps/p1/r1.yaml }
|
||||||
|
- { pyramid: 1, room: 2, file: data/maps/p1/r2.yaml }
|
||||||
|
- { pyramid: 1, room: 3, file: data/maps/p1/r3.yaml }
|
||||||
|
- { pyramid: 1, room: 4, file: data/maps/p1/r4.yaml }
|
||||||
|
- { pyramid: 1, room: 5, file: data/maps/p1/r5.yaml }
|
||||||
|
- { pyramid: 1, room: 6, file: data/maps/p1/r6.yaml }
|
||||||
|
- { pyramid: 2, room: 1, file: data/maps/p2/r1.yaml }
|
||||||
|
- { pyramid: 2, room: 2, file: data/maps/p2/r2.yaml }
|
||||||
|
- { pyramid: 2, room: 3, file: data/maps/p2/r3.yaml }
|
||||||
|
- { pyramid: 2, room: 4, file: data/maps/p2/r4.yaml }
|
||||||
|
- { pyramid: 2, room: 5, file: data/maps/p2/r5.yaml }
|
||||||
|
- { pyramid: 2, room: 6, file: data/maps/p2/r6.yaml }
|
||||||
|
- { pyramid: 3, room: 1, file: data/maps/p3/r1.yaml }
|
||||||
|
- { pyramid: 3, room: 2, file: data/maps/p3/r2.yaml }
|
||||||
|
- { pyramid: 3, room: 3, file: data/maps/p3/r3.yaml }
|
||||||
|
- { pyramid: 3, room: 4, file: data/maps/p3/r4.yaml }
|
||||||
|
- { pyramid: 3, room: 5, file: data/maps/p3/r5.yaml }
|
||||||
|
- { pyramid: 3, room: 6, file: data/maps/p3/r6.yaml }
|
||||||
|
- { pyramid: 4, room: 1, file: data/maps/p4/r1.yaml }
|
||||||
|
- { pyramid: 4, room: 2, file: data/maps/p4/r2.yaml }
|
||||||
|
- { pyramid: 4, room: 3, file: data/maps/p4/r3.yaml }
|
||||||
|
- { pyramid: 4, room: 4, file: data/maps/p4/r4.yaml }
|
||||||
|
- { pyramid: 4, room: 5, file: data/maps/p4/r5.yaml }
|
||||||
|
- { pyramid: 4, room: 6, file: data/maps/p4/r6.yaml }
|
||||||
|
- { pyramid: 5, room: 1, file: data/maps/p5/r1.yaml }
|
||||||
|
- { pyramid: 5, room: 2, file: data/maps/p5/r2.yaml }
|
||||||
|
- { pyramid: 5, room: 3, file: data/maps/p5/r3.yaml }
|
||||||
|
- { pyramid: 5, room: 4, file: data/maps/p5/r4.yaml }
|
||||||
|
- { pyramid: 5, room: 5, file: data/maps/p5/r5.yaml }
|
||||||
|
- { pyramid: 5, room: 6, file: data/maps/p5/r6.yaml }
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_1.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 1
|
||||||
|
room: 1
|
||||||
|
tileset: data/tilesets/pyramid_1.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 11
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: hole
|
||||||
|
position: [4, 7]
|
||||||
|
- type: hole
|
||||||
|
position: [8, 11]
|
||||||
|
- type: hole
|
||||||
|
position: [15, 11]
|
||||||
|
- type: hole
|
||||||
|
position: [15, 5]
|
||||||
|
- type: hole
|
||||||
|
position: [8, 5]
|
||||||
|
- type: door
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_1.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 1
|
||||||
|
room: 2
|
||||||
|
tileset: data/tilesets/pyramid_1.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_1.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 1
|
||||||
|
room: 3
|
||||||
|
tileset: data/tilesets/pyramid_1.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_1.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 1
|
||||||
|
room: 4
|
||||||
|
tileset: data/tilesets/pyramid_1.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_1.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 1
|
||||||
|
room: 5
|
||||||
|
tileset: data/tilesets/pyramid_1.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_1.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 1
|
||||||
|
room: 6
|
||||||
|
tileset: data/tilesets/pyramid_1.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_2.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 2
|
||||||
|
room: 1
|
||||||
|
tileset: data/tilesets/pyramid_2.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_2.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 2
|
||||||
|
room: 2
|
||||||
|
tileset: data/tilesets/pyramid_2.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_2.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 2
|
||||||
|
room: 3
|
||||||
|
tileset: data/tilesets/pyramid_2.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_2.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 2
|
||||||
|
room: 4
|
||||||
|
tileset: data/tilesets/pyramid_2.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_2.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 2
|
||||||
|
room: 5
|
||||||
|
tileset: data/tilesets/pyramid_2.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_2.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 2
|
||||||
|
room: 6
|
||||||
|
tileset: data/tilesets/pyramid_2.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_3.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 3
|
||||||
|
room: 1
|
||||||
|
tileset: data/tilesets/pyramid_3.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_3.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 3
|
||||||
|
room: 2
|
||||||
|
tileset: data/tilesets/pyramid_3.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_3.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 3
|
||||||
|
room: 3
|
||||||
|
tileset: data/tilesets/pyramid_3.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_3.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 3
|
||||||
|
room: 4
|
||||||
|
tileset: data/tilesets/pyramid_3.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_3.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 3
|
||||||
|
room: 5
|
||||||
|
tileset: data/tilesets/pyramid_3.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_3.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 3
|
||||||
|
room: 6
|
||||||
|
tileset: data/tilesets/pyramid_3.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_4.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 4
|
||||||
|
room: 1
|
||||||
|
tileset: data/tilesets/pyramid_4.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_4.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 4
|
||||||
|
room: 2
|
||||||
|
tileset: data/tilesets/pyramid_4.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_4.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 4
|
||||||
|
room: 3
|
||||||
|
tileset: data/tilesets/pyramid_4.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_4.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 4
|
||||||
|
room: 4
|
||||||
|
tileset: data/tilesets/pyramid_4.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_4.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 4
|
||||||
|
room: 5
|
||||||
|
tileset: data/tilesets/pyramid_4.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_4.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 4
|
||||||
|
room: 6
|
||||||
|
tileset: data/tilesets/pyramid_4.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_5.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 5
|
||||||
|
room: 1
|
||||||
|
tileset: data/tilesets/pyramid_5.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_5.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 5
|
||||||
|
room: 2
|
||||||
|
tileset: data/tilesets/pyramid_5.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_5.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 5
|
||||||
|
room: 3
|
||||||
|
tileset: data/tilesets/pyramid_5.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_5.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 5
|
||||||
|
room: 4
|
||||||
|
tileset: data/tilesets/pyramid_5.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_5.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 5
|
||||||
|
room: 5
|
||||||
|
tileset: data/tilesets/pyramid_5.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre pyramid_5.gif.
|
||||||
|
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
|
||||||
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
|
#
|
||||||
|
# Format del tilemap:
|
||||||
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
|
|
||||||
|
pyramid: 5
|
||||||
|
room: 6
|
||||||
|
tileset: data/tilesets/pyramid_5.gif
|
||||||
|
tile_size: 16
|
||||||
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
|
# vivien com a `static constexpr` a map.hpp; ara cada piràmide pot definir
|
||||||
|
# la seua pròpia forma sense recompilar.
|
||||||
|
geometry:
|
||||||
|
grid_cols: 4
|
||||||
|
grid_rows: 5
|
||||||
|
sarc_w: 48
|
||||||
|
sarc_h: 16
|
||||||
|
pitch_x: 64
|
||||||
|
pitch_y: 32
|
||||||
|
hud_h: 24
|
||||||
|
back_wall_h: 24
|
||||||
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
|
data:
|
||||||
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
|
- [11,38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,39,13]
|
||||||
|
|
||||||
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
# - sarcophagus → `sarc_position: [sarc_col, sarc_row]` en la graella de
|
||||||
|
# sarcòfags (4×5). EntityManager calcula el rect píxel via
|
||||||
|
# Map::getSarcRect i l'ancoratge de tile per al perímetre.
|
||||||
|
# - la resta (torch, door, mummy, rolling_rock, wall_trap, floor_trap)
|
||||||
|
# → `position: [tile_col, tile_row]` en coordenades del tilemap
|
||||||
|
# (origen `[-8, 0]` per defecte). Pixel = tile*16 + tilemap_origin.
|
||||||
|
#
|
||||||
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
|
entities:
|
||||||
|
- type: torch
|
||||||
|
position: [4, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [8, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [12, 2]
|
||||||
|
- type: torch
|
||||||
|
position: [16, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 0]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 1]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 2]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 3]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [0, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [1, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [2, 4]
|
||||||
|
- type: sarcophagus
|
||||||
|
sarc_position: [3, 4]
|
||||||
|
- type: door
|
||||||
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
|
- type: mummy
|
||||||
|
position: [10, 7]
|
||||||
|
- type: mummy
|
||||||
|
position: [7, 9]
|
||||||
@@ -3,18 +3,30 @@
|
|||||||
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
|
||||||
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
|
||||||
#
|
#
|
||||||
# Símbols del grid:
|
# Format del tilemap:
|
||||||
# W = paret (tile 99 del tileset, o (3,3) en 0-indexed)
|
# `data` és una llista de files; cada fila és una llista d'índexs del
|
||||||
# F = sòl (llista = un tile aleatori entre aquests per cada casella)
|
# tileset (lineal esq→dre, dalt→baix). Amb un tileset de 512×256 i tiles
|
||||||
# . = cap tile (els sarcòfags seran actors, ací deixem transparent)
|
# de 16×16 hi ha 32 tiles per fila. Un valor negatiu (p.ex. -1) deixa
|
||||||
#
|
# la cel·la buida (transparent), útil per caselles que ocuparà un actor.
|
||||||
# Índexs lineals: els tiles van d'esquerra a dreta, de dalt a baix. Amb un
|
|
||||||
# tileset de 512×256 i tiles de 16×16 hi ha 32 tiles per fila.
|
|
||||||
|
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/tomb.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
|
# per desactivar el pintat per a aquest jugador.
|
||||||
|
player1_paint_tile: 0
|
||||||
|
player2_paint_tile: 1
|
||||||
|
|
||||||
|
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
||||||
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
# ell mateix es ralentitza per mantenir el grup). En head-on, ambdós
|
||||||
|
# s'anul·len (sense push, sense moviment).
|
||||||
|
push_factor: 0.30
|
||||||
|
|
||||||
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
# Geometria lògica de la piràmide (separada del render visual del tileset).
|
||||||
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
# La graella de sarcòfags, el pitch entre ells i les amplades dels murs
|
||||||
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
# decideixen els límits del jugador i les seues caixes de col·lisió. Abans
|
||||||
@@ -30,39 +42,29 @@ geometry:
|
|||||||
hud_h: 24
|
hud_h: 24
|
||||||
back_wall_h: 24
|
back_wall_h: 24
|
||||||
side_wall_w: 24
|
side_wall_w: 24
|
||||||
|
# Columna central del tile transitable de la porta. La porta es renderitza
|
||||||
|
# com a sprite 48×32 (3 tiles d'amplada) centrat sobre aquesta columna; els
|
||||||
|
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
||||||
|
# on el jugador surt de la sala. Les 2 columnes flanquejants segueixen sent
|
||||||
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
|
door_col: 10
|
||||||
|
|
||||||
legend:
|
data:
|
||||||
W: 292
|
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
||||||
X: 169
|
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
||||||
Y: 1
|
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
||||||
Z: 202
|
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
||||||
T: 200
|
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
||||||
R: 262
|
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
||||||
G: 263
|
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
||||||
H: 230
|
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
||||||
I: 231
|
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
||||||
J: 233
|
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
||||||
A: 2
|
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
||||||
B: 34
|
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
||||||
C: 33
|
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
||||||
D: 32
|
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
||||||
|
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
||||||
data: |
|
|
||||||
YYYYYYYYYYYYYYYYYYYYY
|
|
||||||
YHJJJJJJJJJJJJJJJJJIY
|
|
||||||
YZWWWWWWWWWWWWWWWWWTY
|
|
||||||
YZDDADDBBDDADDDDBDDTY
|
|
||||||
YZDAAADAAADAAABAAADTY
|
|
||||||
YZDDDCCDDDDDADDDBDDTY
|
|
||||||
YZDAAADAAAAAAADAAADTY
|
|
||||||
YZDDCCDDDDDADDBDDDDTY
|
|
||||||
YZDAAADAAADAAADAAADTY
|
|
||||||
YZDDDDCCDDDDADDDDDDTY
|
|
||||||
YZDAAADAAADAAADAAADTY
|
|
||||||
YZDDDABDDDDADCDDBDDTY
|
|
||||||
YZDAAADAAADAAADAAADTY
|
|
||||||
YZDDBBDDADDDBCDDDDDTY
|
|
||||||
YRXXXXXXXXXXXXXXXXXGY
|
|
||||||
|
|
||||||
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
@@ -75,7 +77,7 @@ data: |
|
|||||||
#
|
#
|
||||||
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
# Dos sarcòfags en cantonades oposades → perímetres aïllats (cap tile en
|
||||||
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
# comú). La porta va al centre inferior, on el tilemap té els tiles
|
||||||
# d'obertura (R/G). La momia al corredor central. L'antorxa al costat
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
# superior esquerre per verificar render d'una entitat decorativa.
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
entities:
|
entities:
|
||||||
- type: torch
|
- type: torch
|
||||||
@@ -127,7 +129,9 @@ entities:
|
|||||||
- type: sarcophagus
|
- type: sarcophagus
|
||||||
sarc_position: [3, 4]
|
sarc_position: [3, 4]
|
||||||
- type: door
|
- type: door
|
||||||
position: [9, 1]
|
# Sense `position`: la porta sempre viu al mur trasser. La columna
|
||||||
|
# transitable la dicta `geometry.door_col`; el render i la caixa
|
||||||
|
# de col·lisió es deriven d'aquesta dada al loadFromLevel.
|
||||||
- type: mummy
|
- type: mummy
|
||||||
position: [10, 7]
|
position: [10, 7]
|
||||||
- type: mummy
|
- type: mummy
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
# per a poder carregar Map sense errors, però el seu propòsit real són
|
# per a poder carregar Map sense errors, però el seu propòsit real són
|
||||||
# els spawns de sota: l'smoke verifica que EntityLoader::readSpawns els
|
# els spawns de sota: l'smoke verifica que EntityLoader::readSpawns els
|
||||||
# retorna correctament i que EntityManager pot instanciar-los i wire-ar-los.
|
# retorna correctament i que EntityManager pot instanciar-los i wire-ar-los.
|
||||||
|
#
|
||||||
|
# Format del tilemap: vegeu pyramid.yaml.
|
||||||
|
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/tomb.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
@@ -18,18 +20,14 @@ geometry:
|
|||||||
back_wall_h: 24
|
back_wall_h: 24
|
||||||
side_wall_w: 24
|
side_wall_w: 24
|
||||||
|
|
||||||
legend:
|
data:
|
||||||
Y: 1
|
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
||||||
X: 169
|
- [ 1,169,169,169,169,169,169,169,169,169,169,169,169, 1]
|
||||||
|
- [ 1,169,169,169,169,169,169,169,169,169,169,169,169, 1]
|
||||||
data: |
|
- [ 1,169,169,169,169,169,169,169,169,169,169,169,169, 1]
|
||||||
YYYYYYYYYYYYYY
|
- [ 1,169,169,169,169,169,169,169,169,169,169,169,169, 1]
|
||||||
YXXXXXXXXXXXXY
|
- [ 1,169,169,169,169,169,169,169,169,169,169,169,169, 1]
|
||||||
YXXXXXXXXXXXXY
|
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
||||||
YXXXXXXXXXXXXY
|
|
||||||
YXXXXXXXXXXXXY
|
|
||||||
YXXXXXXXXXXXXY
|
|
||||||
YYYYYYYYYYYYYY
|
|
||||||
|
|
||||||
entities:
|
entities:
|
||||||
- type: torch
|
- type: torch
|
||||||
|
|||||||
@@ -0,0 +1,259 @@
|
|||||||
|
JASC-PAL
|
||||||
|
0100
|
||||||
|
256
|
||||||
|
0 0 0
|
||||||
|
36 0 0
|
||||||
|
72 0 0
|
||||||
|
109 0 0
|
||||||
|
145 0 0
|
||||||
|
182 0 0
|
||||||
|
218 0 0
|
||||||
|
255 0 0
|
||||||
|
0 36 0
|
||||||
|
36 36 0
|
||||||
|
72 36 0
|
||||||
|
109 36 0
|
||||||
|
145 36 0
|
||||||
|
182 36 0
|
||||||
|
218 36 0
|
||||||
|
255 36 0
|
||||||
|
0 72 0
|
||||||
|
36 72 0
|
||||||
|
72 72 0
|
||||||
|
109 72 0
|
||||||
|
145 72 0
|
||||||
|
182 72 0
|
||||||
|
218 72 0
|
||||||
|
255 72 0
|
||||||
|
0 109 0
|
||||||
|
36 109 0
|
||||||
|
72 109 0
|
||||||
|
109 109 0
|
||||||
|
145 109 0
|
||||||
|
182 109 0
|
||||||
|
218 109 0
|
||||||
|
255 109 0
|
||||||
|
0 145 0
|
||||||
|
36 145 0
|
||||||
|
72 145 0
|
||||||
|
109 145 0
|
||||||
|
145 145 0
|
||||||
|
182 145 0
|
||||||
|
218 145 0
|
||||||
|
255 145 0
|
||||||
|
0 182 0
|
||||||
|
36 182 0
|
||||||
|
72 182 0
|
||||||
|
109 182 0
|
||||||
|
145 182 0
|
||||||
|
182 182 0
|
||||||
|
218 182 0
|
||||||
|
255 182 0
|
||||||
|
0 218 0
|
||||||
|
36 218 0
|
||||||
|
72 218 0
|
||||||
|
109 218 0
|
||||||
|
145 218 0
|
||||||
|
182 218 0
|
||||||
|
218 218 0
|
||||||
|
255 218 0
|
||||||
|
0 255 0
|
||||||
|
36 255 0
|
||||||
|
72 255 0
|
||||||
|
109 255 0
|
||||||
|
145 255 0
|
||||||
|
182 255 0
|
||||||
|
218 255 0
|
||||||
|
255 255 0
|
||||||
|
0 0 85
|
||||||
|
36 0 85
|
||||||
|
72 0 85
|
||||||
|
109 0 85
|
||||||
|
145 0 85
|
||||||
|
182 0 85
|
||||||
|
218 0 85
|
||||||
|
255 0 85
|
||||||
|
0 36 85
|
||||||
|
36 36 85
|
||||||
|
72 36 85
|
||||||
|
109 36 85
|
||||||
|
145 36 85
|
||||||
|
182 36 85
|
||||||
|
218 36 85
|
||||||
|
255 36 85
|
||||||
|
0 72 85
|
||||||
|
36 72 85
|
||||||
|
72 72 85
|
||||||
|
109 72 85
|
||||||
|
145 72 85
|
||||||
|
182 72 85
|
||||||
|
218 72 85
|
||||||
|
255 72 85
|
||||||
|
0 109 85
|
||||||
|
36 109 85
|
||||||
|
72 109 85
|
||||||
|
109 109 85
|
||||||
|
145 109 85
|
||||||
|
182 109 85
|
||||||
|
218 109 85
|
||||||
|
255 109 85
|
||||||
|
0 145 85
|
||||||
|
36 145 85
|
||||||
|
72 145 85
|
||||||
|
109 145 85
|
||||||
|
145 145 85
|
||||||
|
182 145 85
|
||||||
|
218 145 85
|
||||||
|
255 145 85
|
||||||
|
0 182 85
|
||||||
|
36 182 85
|
||||||
|
72 182 85
|
||||||
|
109 182 85
|
||||||
|
145 182 85
|
||||||
|
182 182 85
|
||||||
|
218 182 85
|
||||||
|
255 182 85
|
||||||
|
0 218 85
|
||||||
|
36 218 85
|
||||||
|
72 218 85
|
||||||
|
109 218 85
|
||||||
|
145 218 85
|
||||||
|
182 218 85
|
||||||
|
218 218 85
|
||||||
|
255 218 85
|
||||||
|
0 255 85
|
||||||
|
36 255 85
|
||||||
|
72 255 85
|
||||||
|
109 255 85
|
||||||
|
145 255 85
|
||||||
|
182 255 85
|
||||||
|
218 255 85
|
||||||
|
255 255 85
|
||||||
|
0 0 170
|
||||||
|
36 0 170
|
||||||
|
72 0 170
|
||||||
|
109 0 170
|
||||||
|
145 0 170
|
||||||
|
182 0 170
|
||||||
|
218 0 170
|
||||||
|
255 0 170
|
||||||
|
0 36 170
|
||||||
|
36 36 170
|
||||||
|
72 36 170
|
||||||
|
109 36 170
|
||||||
|
145 36 170
|
||||||
|
182 36 170
|
||||||
|
218 36 170
|
||||||
|
255 36 170
|
||||||
|
0 72 170
|
||||||
|
36 72 170
|
||||||
|
72 72 170
|
||||||
|
109 72 170
|
||||||
|
145 72 170
|
||||||
|
182 72 170
|
||||||
|
218 72 170
|
||||||
|
255 72 170
|
||||||
|
0 109 170
|
||||||
|
36 109 170
|
||||||
|
72 109 170
|
||||||
|
109 109 170
|
||||||
|
145 109 170
|
||||||
|
182 109 170
|
||||||
|
218 109 170
|
||||||
|
255 109 170
|
||||||
|
0 145 170
|
||||||
|
36 145 170
|
||||||
|
72 145 170
|
||||||
|
109 145 170
|
||||||
|
145 145 170
|
||||||
|
182 145 170
|
||||||
|
218 145 170
|
||||||
|
255 145 170
|
||||||
|
0 182 170
|
||||||
|
36 182 170
|
||||||
|
72 182 170
|
||||||
|
109 182 170
|
||||||
|
145 182 170
|
||||||
|
182 182 170
|
||||||
|
218 182 170
|
||||||
|
255 182 170
|
||||||
|
0 218 170
|
||||||
|
36 218 170
|
||||||
|
72 218 170
|
||||||
|
109 218 170
|
||||||
|
145 218 170
|
||||||
|
182 218 170
|
||||||
|
218 218 170
|
||||||
|
255 218 170
|
||||||
|
0 255 170
|
||||||
|
36 255 170
|
||||||
|
72 255 170
|
||||||
|
109 255 170
|
||||||
|
145 255 170
|
||||||
|
182 255 170
|
||||||
|
218 255 170
|
||||||
|
255 255 170
|
||||||
|
0 0 255
|
||||||
|
36 0 255
|
||||||
|
72 0 255
|
||||||
|
109 0 255
|
||||||
|
145 0 255
|
||||||
|
182 0 255
|
||||||
|
218 0 255
|
||||||
|
255 0 255
|
||||||
|
0 36 255
|
||||||
|
36 36 255
|
||||||
|
72 36 255
|
||||||
|
109 36 255
|
||||||
|
145 36 255
|
||||||
|
182 36 255
|
||||||
|
218 36 255
|
||||||
|
255 36 255
|
||||||
|
0 72 255
|
||||||
|
36 72 255
|
||||||
|
72 72 255
|
||||||
|
109 72 255
|
||||||
|
145 72 255
|
||||||
|
182 72 255
|
||||||
|
218 72 255
|
||||||
|
255 72 255
|
||||||
|
0 109 255
|
||||||
|
36 109 255
|
||||||
|
72 109 255
|
||||||
|
109 109 255
|
||||||
|
145 109 255
|
||||||
|
182 109 255
|
||||||
|
218 109 255
|
||||||
|
255 109 255
|
||||||
|
0 145 255
|
||||||
|
36 145 255
|
||||||
|
72 145 255
|
||||||
|
109 145 255
|
||||||
|
145 145 255
|
||||||
|
182 145 255
|
||||||
|
218 145 255
|
||||||
|
255 145 255
|
||||||
|
0 182 255
|
||||||
|
36 182 255
|
||||||
|
72 182 255
|
||||||
|
109 182 255
|
||||||
|
145 182 255
|
||||||
|
182 182 255
|
||||||
|
218 182 255
|
||||||
|
255 182 255
|
||||||
|
0 218 255
|
||||||
|
36 218 255
|
||||||
|
72 218 255
|
||||||
|
109 218 255
|
||||||
|
145 218 255
|
||||||
|
182 218 255
|
||||||
|
218 218 255
|
||||||
|
255 218 255
|
||||||
|
0 255 255
|
||||||
|
36 255 255
|
||||||
|
72 255 255
|
||||||
|
109 255 255
|
||||||
|
145 255 255
|
||||||
|
182 255 255
|
||||||
|
218 255 255
|
||||||
|
255 255 255
|
||||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,133 @@
|
|||||||
|
sprite:
|
||||||
|
path: morcus.gif
|
||||||
|
size: [16, 32]
|
||||||
|
|
||||||
|
# Geometria de col·lisió. `size` és la caixa contra murs/sarcs;
|
||||||
|
# `hit_size`, la caixa contra altres actors (més petita); `height`,
|
||||||
|
# l'alçada que un salt ha de superar per passar per damunt.
|
||||||
|
collision:
|
||||||
|
size: 16
|
||||||
|
base_size: 16
|
||||||
|
hit_size: 8
|
||||||
|
height: 12
|
||||||
|
|
||||||
|
animations:
|
||||||
|
# Les variants `*_west` reaprofiten els frames de l'est i declaren
|
||||||
|
# `flip_h: true` perquè el motor d'animació apliqui el flip al sprite —
|
||||||
|
# el codi del joc no toca mai `flip_` directament.
|
||||||
|
stand_south: { frames: [0] }
|
||||||
|
stand_north: { frames: [4] }
|
||||||
|
stand_east: { frames: [8] }
|
||||||
|
stand_west: { frames: [8], flip_h: true }
|
||||||
|
|
||||||
|
jump_south: { frames: [12] }
|
||||||
|
jump_north: { frames: [13] }
|
||||||
|
jump_east: { frames: [14] }
|
||||||
|
jump_west: { frames: [14], flip_h: true }
|
||||||
|
|
||||||
|
stun_south: { frames: [16] }
|
||||||
|
stun_north: { frames: [17] }
|
||||||
|
stun_east: { frames: [18] }
|
||||||
|
stun_west: { frames: [18], flip_h: true }
|
||||||
|
|
||||||
|
walk_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
||||||
|
walk_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
||||||
|
walk_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
|
||||||
|
walk_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
|
||||||
|
|
||||||
|
hurt_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
||||||
|
hurt_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
||||||
|
hurt_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
|
||||||
|
hurt_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
|
||||||
|
|
||||||
|
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 }
|
||||||
|
|
||||||
|
recover_south: { frames: [23] }
|
||||||
|
recover_north: { frames: [27] }
|
||||||
|
recover_east: { frames: [31] }
|
||||||
|
recover_west: { frames: [31], flip_h: true }
|
||||||
|
|
||||||
|
# Animació de mort: 3 voltes horàries començant pel sud (south →
|
||||||
|
# west → north → east, repetit 3×). Reaprofita els frames stand_*; el
|
||||||
|
# west és l'east amb `flip_h: true` declarat per-frame (mapping form,
|
||||||
|
# no hereta del nivell d'anim). `frame_ms` per-frame creix linealment
|
||||||
|
# per donar la sensació de desaccelerar fins a aturar-se. `loop: false`
|
||||||
|
# → es queda al darrer frame i marca completed.
|
||||||
|
death_spinning:
|
||||||
|
frames:
|
||||||
|
- 0
|
||||||
|
- { index: 8, flip_h: true }
|
||||||
|
- 4
|
||||||
|
- 8
|
||||||
|
- 0
|
||||||
|
- { index: 8, flip_h: true }
|
||||||
|
- 4
|
||||||
|
- 8
|
||||||
|
- 0
|
||||||
|
- { index: 8, flip_h: true }
|
||||||
|
- 4
|
||||||
|
- 8
|
||||||
|
frame_ms: [60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280]
|
||||||
|
loop: false
|
||||||
|
|
||||||
|
# Mort per caiguda a un agujero (DeathCause::HOLE). `loop: false` perquè
|
||||||
|
# `animationIsCompleted()` dispari el respawn timer determinísticament.
|
||||||
|
death_hole:
|
||||||
|
frames: [15]
|
||||||
|
frame_ms: 60
|
||||||
|
loop: false
|
||||||
|
|
||||||
|
# Inèrcia de frenada en soltar la direcció. `threshold_ms` filtra
|
||||||
|
# tocs curts (passets d'un tile sense rellisquera).
|
||||||
|
inertia:
|
||||||
|
brake_time_ms: 150
|
||||||
|
threshold_ms: 300
|
||||||
|
|
||||||
|
# Gir de 180°: durant `duration_ms` el facing es fixa a la direcció
|
||||||
|
# intermèdia indicada (purament visual).
|
||||||
|
turn:
|
||||||
|
duration_ms: 80
|
||||||
|
transitions:
|
||||||
|
west_to_east: south
|
||||||
|
east_to_west: north
|
||||||
|
north_to_south: east
|
||||||
|
south_to_north: west
|
||||||
|
|
||||||
|
# Salt vertical (px de z, no toca la posició al món).
|
||||||
|
# `shadow_y_offset`: puja la sombra per compensar la franja
|
||||||
|
# transparent sota els peus del sprite.
|
||||||
|
jump:
|
||||||
|
impulse: 200
|
||||||
|
gravity: 600
|
||||||
|
landing_stun_ms: 400
|
||||||
|
shadow_y_offset: 3
|
||||||
|
sound: jump.wav
|
||||||
|
land_sound: land.wav
|
||||||
|
|
||||||
|
# Projecció post-impacte: surt cap a la direcció amb més recorregut
|
||||||
|
# lliure i frena fins a 0 en `brake_time_ms`.
|
||||||
|
hurt:
|
||||||
|
speed: 120
|
||||||
|
brake_time_ms: 500
|
||||||
|
sound: hurt.wav
|
||||||
|
|
||||||
|
# Atac amb làtigo. `range_tiles` és la longitud abans de retallar
|
||||||
|
# contra murs/sarcs; `cooldown_ms` allarga ATTACKING amb una
|
||||||
|
# recuperació en què la hitbox ja no fa dany.
|
||||||
|
whip:
|
||||||
|
range_tiles: 3
|
||||||
|
swing_ms: 200
|
||||||
|
cooldown_ms: 200
|
||||||
|
damage: 1
|
||||||
|
sound: whip.wav
|
||||||
|
|
||||||
|
# Mort + respawn. `respawn_delay_ms` és la pausa entre la fi de
|
||||||
|
# `death_spinning` i la reaparició al spawn. `lives` és el comptador
|
||||||
|
# inicial d'intents (estil Mario): cada respawn el decrementa en 1, pot
|
||||||
|
# anar a negatius (no hi ha game over).
|
||||||
|
death:
|
||||||
|
respawn_delay_ms: 500
|
||||||
|
lives: 3
|
||||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1,37 +0,0 @@
|
|||||||
# player animation
|
|
||||||
tileSetFile: player.gif
|
|
||||||
frameWidth: 12
|
|
||||||
frameHeight: 24
|
|
||||||
|
|
||||||
# Geometria de col·lisió del jugador (independent de l'animació):
|
|
||||||
# size → costat del quadrat de col·lisió (16×16)
|
|
||||||
# base_size → costat del quadrat visual inferior del sprite (els "peus");
|
|
||||||
# s'usa per centrar la caixa de col·lisió sobre ell.
|
|
||||||
collision:
|
|
||||||
size: 16
|
|
||||||
base_size: 12
|
|
||||||
|
|
||||||
animations:
|
|
||||||
- name: stand_down
|
|
||||||
frames: [0]
|
|
||||||
|
|
||||||
- name: stand_up
|
|
||||||
frames: [4]
|
|
||||||
|
|
||||||
- name: stand
|
|
||||||
frames: [8]
|
|
||||||
|
|
||||||
- name: walk_down
|
|
||||||
speed: 0.07
|
|
||||||
loopFrom: 0
|
|
||||||
frames: [0, 1, 2, 3]
|
|
||||||
|
|
||||||
- name: walk_up
|
|
||||||
speed: 0.07
|
|
||||||
loopFrom: 0
|
|
||||||
frames: [4, 5, 6, 7]
|
|
||||||
|
|
||||||
- name: walk
|
|
||||||
speed: 0.07
|
|
||||||
loopFrom: 0
|
|
||||||
frames: [8, 9, 10, 11]
|
|
||||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,133 @@
|
|||||||
|
sprite:
|
||||||
|
path: sam.gif
|
||||||
|
size: [16, 32]
|
||||||
|
|
||||||
|
# Geometria de col·lisió. `size` és la caixa contra murs/sarcs;
|
||||||
|
# `hit_size`, la caixa contra altres actors (més petita); `height`,
|
||||||
|
# l'alçada que un salt ha de superar per passar per damunt.
|
||||||
|
collision:
|
||||||
|
size: 16
|
||||||
|
base_size: 16
|
||||||
|
hit_size: 8
|
||||||
|
height: 12
|
||||||
|
|
||||||
|
animations:
|
||||||
|
# Les variants `*_west` reaprofiten els frames de l'est i declaren
|
||||||
|
# `flip_h: true` perquè el motor d'animació apliqui el flip al sprite —
|
||||||
|
# el codi del joc no toca mai `flip_` directament.
|
||||||
|
stand_south: { frames: [0] }
|
||||||
|
stand_north: { frames: [4] }
|
||||||
|
stand_east: { frames: [8] }
|
||||||
|
stand_west: { frames: [8], flip_h: true }
|
||||||
|
|
||||||
|
jump_south: { frames: [12] }
|
||||||
|
jump_north: { frames: [13] }
|
||||||
|
jump_east: { frames: [14] }
|
||||||
|
jump_west: { frames: [14], flip_h: true }
|
||||||
|
|
||||||
|
stun_south: { frames: [16] }
|
||||||
|
stun_north: { frames: [17] }
|
||||||
|
stun_east: { frames: [18] }
|
||||||
|
stun_west: { frames: [18], flip_h: true }
|
||||||
|
|
||||||
|
walk_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
||||||
|
walk_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
||||||
|
walk_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
|
||||||
|
walk_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
|
||||||
|
|
||||||
|
hurt_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
||||||
|
hurt_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
||||||
|
hurt_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
|
||||||
|
hurt_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
|
||||||
|
|
||||||
|
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 }
|
||||||
|
|
||||||
|
recover_south: { frames: [23] }
|
||||||
|
recover_north: { frames: [27] }
|
||||||
|
recover_east: { frames: [31] }
|
||||||
|
recover_west: { frames: [31], flip_h: true }
|
||||||
|
|
||||||
|
# Animació de mort: 3 voltes horàries començant pel sud (south →
|
||||||
|
# west → north → east, repetit 3×). Reaprofita els frames stand_*; el
|
||||||
|
# west és l'east amb `flip_h: true` declarat per-frame (mapping form,
|
||||||
|
# no hereta del nivell d'anim). `frame_ms` per-frame creix linealment
|
||||||
|
# per donar la sensació de desaccelerar fins a aturar-se. `loop: false`
|
||||||
|
# → es queda al darrer frame i marca completed.
|
||||||
|
death_spinning:
|
||||||
|
frames:
|
||||||
|
- 0
|
||||||
|
- { index: 8, flip_h: true }
|
||||||
|
- 4
|
||||||
|
- 8
|
||||||
|
- 0
|
||||||
|
- { index: 8, flip_h: true }
|
||||||
|
- 4
|
||||||
|
- 8
|
||||||
|
- 0
|
||||||
|
- { index: 8, flip_h: true }
|
||||||
|
- 4
|
||||||
|
- 8
|
||||||
|
frame_ms: [60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280]
|
||||||
|
loop: false
|
||||||
|
|
||||||
|
# Mort per caiguda a un agujero (DeathCause::HOLE). `loop: false` perquè
|
||||||
|
# `animationIsCompleted()` dispari el respawn timer determinísticament.
|
||||||
|
death_hole:
|
||||||
|
frames: [15]
|
||||||
|
frame_ms: 60
|
||||||
|
loop: false
|
||||||
|
|
||||||
|
# Inèrcia de frenada en soltar la direcció. `threshold_ms` filtra
|
||||||
|
# tocs curts (passets d'un tile sense rellisquera).
|
||||||
|
inertia:
|
||||||
|
brake_time_ms: 150
|
||||||
|
threshold_ms: 300
|
||||||
|
|
||||||
|
# Gir de 180°: durant `duration_ms` el facing es fixa a la direcció
|
||||||
|
# intermèdia indicada (purament visual).
|
||||||
|
turn:
|
||||||
|
duration_ms: 80
|
||||||
|
transitions:
|
||||||
|
west_to_east: south
|
||||||
|
east_to_west: north
|
||||||
|
north_to_south: east
|
||||||
|
south_to_north: west
|
||||||
|
|
||||||
|
# Salt vertical (px de z, no toca la posició al món).
|
||||||
|
# `shadow_y_offset`: puja la sombra per compensar la franja
|
||||||
|
# transparent sota els peus del sprite.
|
||||||
|
jump:
|
||||||
|
impulse: 200
|
||||||
|
gravity: 600
|
||||||
|
landing_stun_ms: 400
|
||||||
|
shadow_y_offset: 3
|
||||||
|
sound: jump.wav
|
||||||
|
land_sound: land.wav
|
||||||
|
|
||||||
|
# Projecció post-impacte: surt cap a la direcció amb més recorregut
|
||||||
|
# lliure i frena fins a 0 en `brake_time_ms`.
|
||||||
|
hurt:
|
||||||
|
speed: 120
|
||||||
|
brake_time_ms: 500
|
||||||
|
sound: hurt.wav
|
||||||
|
|
||||||
|
# Atac amb làtigo. `range_tiles` és la longitud abans de retallar
|
||||||
|
# contra murs/sarcs; `cooldown_ms` allarga ATTACKING amb una
|
||||||
|
# recuperació en què la hitbox ja no fa dany.
|
||||||
|
whip:
|
||||||
|
range_tiles: 3
|
||||||
|
swing_ms: 200
|
||||||
|
cooldown_ms: 200
|
||||||
|
damage: 1
|
||||||
|
sound: whip.wav
|
||||||
|
|
||||||
|
# Mort + respawn. `respawn_delay_ms` és la pausa entre la fi de
|
||||||
|
# `death_spinning` i la reaparició al spawn. `lives` és el comptador
|
||||||
|
# inicial d'intents (estil Mario): cada respawn el decrementa en 1, pot
|
||||||
|
# anar a negatius (no hi ha game over).
|
||||||
|
death:
|
||||||
|
respawn_delay_ms: 500
|
||||||
|
lives: 3
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Configuració de la GameScene: timings i corbes de la FSM de sala
|
||||||
|
# (ROOM_INTRO i ROOM_OUTRO). Tots els valors són ms i tota la geometria
|
||||||
|
# i font del text es reutilitzen del tema UI (data/ui.yaml > pause +
|
||||||
|
# notification). Si manca el fitxer o alguna clau, GameScene cau als
|
||||||
|
# fallbacks compilats; veure `readSceneConfig` a game_scene.cpp.
|
||||||
|
|
||||||
|
room_intro:
|
||||||
|
fade_in_ms: 500 # negre opac → món visible
|
||||||
|
text_delay_ms: 500 # espera amb el món net abans de sortir el text
|
||||||
|
text_enter_ms: 400 # text esquerra → centre
|
||||||
|
text_hold_ms: 2000 # text quiet al centre
|
||||||
|
text_exit_ms: 400 # text centre → fora per l'esquerra
|
||||||
|
line_gap_px: -4 # px extra entre les dues línies (piràmide / sala)
|
||||||
|
# Delays d'spawn aeri **mesurats des de la fi de la música d'INTRO**
|
||||||
|
# (callback `Audio::setOnMusicEnded`). 0 = cau al moment exacte que
|
||||||
|
# `room_intro.ogg` acaba; valors > 0 escalonen els jugadors. En mode
|
||||||
|
# single-player s'usa només `player_spawn_delay_ms`.
|
||||||
|
player_spawn_delay_ms: 0
|
||||||
|
player2_spawn_delay_ms: 300
|
||||||
|
# Preset de reverb (catàleg a `data/sounds.yaml`) aplicat al primer
|
||||||
|
# LAND de cada jugador durant la cinemàtica d'entrada. La sala dona
|
||||||
|
# "més detall" sonor a l'INTRO; els salts del joc i els respawns
|
||||||
|
# post-mort cauen al so sec. Buit o absent = sense reverb.
|
||||||
|
land_reverb: stone_hall
|
||||||
|
|
||||||
|
room_outro:
|
||||||
|
text_enter_ms: 400
|
||||||
|
text_hold_ms: 2000
|
||||||
|
text_exit_ms: 400
|
||||||
|
fade_out_ms: 500 # món visible → negre opac
|
||||||
|
line_gap_px: -4 # px extra entre les dues línies (fase / completada)
|
||||||
|
black_hold_ms: 500 # pausa en negre abans d'entrar a la pròxima sala
|
||||||
|
|
||||||
|
text:
|
||||||
|
# Noms d'easing tal com els resol UiConfig::curveByName (vegeu
|
||||||
|
# `data/ui.yaml`). El text entra suau i surt accelerant.
|
||||||
|
enter_curve: outCubic
|
||||||
|
exit_curve: inCubic
|
||||||
|
|
||||||
|
# Panel translúcid pintat sota el rètol de transició (INTRO i OUTRO).
|
||||||
|
# Va centrat verticalment al centre del playfield i ocupa tota
|
||||||
|
# l'amplada del canvas. Creix d'altura amb la mateixa enter_curve del
|
||||||
|
# text i decreix amb l'exit_curve. `color` són tres canals RGB (0..255);
|
||||||
|
# `alpha` és la transparència del blend (0 = invisible, 255 = opac).
|
||||||
|
text_panel:
|
||||||
|
color: [36, 182, 170]
|
||||||
|
alpha: 128
|
||||||
|
height_px: 32
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Presets d'efectes per a sons puntuals. Les crides
|
||||||
|
# Audio::playSoundWithEcho(name, preset, group) i
|
||||||
|
# Audio::playSoundWithReverb(name, preset, group) referencien aquests
|
||||||
|
# presets pel seu nom (camp `preset`). Si el nom no existeix, el so es
|
||||||
|
# reprodueix sec amb un avís a stderr.
|
||||||
|
#
|
||||||
|
# Camps:
|
||||||
|
# echo:
|
||||||
|
# delay_ms → temps fins al primer rebot, en mil·lisegons.
|
||||||
|
# feedback → reinjecció (0..0.95). >0.7 sona "metàl·lic".
|
||||||
|
# wet → mescla del senyal humit (0..1). 1 = només eco.
|
||||||
|
# reverb:
|
||||||
|
# room_size → mida percebuda de la sala (0..1).
|
||||||
|
# damping → atenuació de freqüències altes en cada rebot (0..1).
|
||||||
|
# wet → mescla humida (0..1).
|
||||||
|
#
|
||||||
|
# Els processadors limiten la cua a 800 ms (echo) i 1500 ms (reverb).
|
||||||
|
|
||||||
|
echo:
|
||||||
|
cave:
|
||||||
|
delay_ms: 220
|
||||||
|
feedback: 0.45
|
||||||
|
wet: 0.35
|
||||||
|
short:
|
||||||
|
delay_ms: 80
|
||||||
|
feedback: 0.30
|
||||||
|
wet: 0.25
|
||||||
|
|
||||||
|
reverb:
|
||||||
|
stone_hall:
|
||||||
|
room_size: 0.78
|
||||||
|
damping: 0.50
|
||||||
|
wet: 0.40
|
||||||
|
small_room:
|
||||||
|
room_size: 0.40
|
||||||
|
damping: 0.70
|
||||||
|
wet: 0.30
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 5.1 KiB |
@@ -0,0 +1,100 @@
|
|||||||
|
# Tema de la UI: índexs de paleta, timings, fonts i flags visuals. Tota
|
||||||
|
# clau absent cau al valor compilat a Defaults::UI (veure defaults.hpp).
|
||||||
|
|
||||||
|
colors:
|
||||||
|
render_info_text: 58
|
||||||
|
render_info_shadow: 16
|
||||||
|
canvas_bg: 72
|
||||||
|
notification_text: 232
|
||||||
|
notification_bg: 64
|
||||||
|
exit_text: 71
|
||||||
|
exit_bg: 2
|
||||||
|
menu_bg: 2
|
||||||
|
menu_border: 232
|
||||||
|
menu_title: 237
|
||||||
|
menu_label: 58
|
||||||
|
menu_value: 232
|
||||||
|
menu_cursor: 71
|
||||||
|
menu_disabled: 16
|
||||||
|
hud_bg: 9
|
||||||
|
hud_label: 58
|
||||||
|
hud_value: 187
|
||||||
|
hud_flash: 237
|
||||||
|
# Rètol de transició de sala (ROOM_INTRO i ROOM_OUTRO). Independents
|
||||||
|
# de notification_* perquè la fase i les notificacions són dos canals
|
||||||
|
# visuals distints: ací pots canviar el color del rètol sense tocar
|
||||||
|
# els missatges del HUD.
|
||||||
|
room_text: 160
|
||||||
|
room_text_outline: 253
|
||||||
|
|
||||||
|
timings:
|
||||||
|
notification_ms: 4000
|
||||||
|
exit_ms: 4000
|
||||||
|
|
||||||
|
fonts:
|
||||||
|
hud:
|
||||||
|
name: "8bithud"
|
||||||
|
gif: "data/fonts/8bithud.gif"
|
||||||
|
fnt: "data/fonts/8bithud.fnt"
|
||||||
|
kerning: 1
|
||||||
|
placeholder:
|
||||||
|
name: "ari-w9500-condensed-display"
|
||||||
|
gif: "data/fonts/ari-w9500-condensed-display.gif"
|
||||||
|
fnt: "data/fonts/ari-w9500-condensed-display.fnt"
|
||||||
|
kerning: 1
|
||||||
|
# Font del rètol de transició de sala (ROOM_INTRO i ROOM_OUTRO).
|
||||||
|
room_text:
|
||||||
|
name: "CaesarDressing-Regular"
|
||||||
|
gif: "data/fonts/CaesarDressing-Regular.gif"
|
||||||
|
fnt: "data/fonts/CaesarDressing-Regular.fnt"
|
||||||
|
kerning: 1
|
||||||
|
menu_title:
|
||||||
|
name: "ari-w9500-display"
|
||||||
|
gif: "data/fonts/ari-w9500-display.gif"
|
||||||
|
fnt: "data/fonts/ari-w9500-display.fnt"
|
||||||
|
kerning: 0
|
||||||
|
menu_items:
|
||||||
|
name: "ari-w9500-condensed-display"
|
||||||
|
gif: "data/fonts/ari-w9500-condensed-display.gif"
|
||||||
|
fnt: "data/fonts/ari-w9500-condensed-display.fnt"
|
||||||
|
kerning: 0
|
||||||
|
|
||||||
|
hud:
|
||||||
|
bg_enabled: false
|
||||||
|
entry_ms: 800 # durada de l'animació d'entrada dels tres blocs
|
||||||
|
flash_ms: 250 # durada del flash quan un jugador rep un hit
|
||||||
|
shake_ms: 300 # durada del shake que acompanya el flash
|
||||||
|
|
||||||
|
# Geometria del popup superior (Notifier). En píxels del canvas, llevat de
|
||||||
|
# slide_ms (durada en ms de l'animació d'entrada i d'eixida; constant
|
||||||
|
# independentment de la mida de la caixa). El padding vertical és asimètric a
|
||||||
|
# propòsit: padding_top > padding_bottom puja una mica el text dins la caixa.
|
||||||
|
# Corbes disponibles: linear, outQuad, inQuad, inOutQuad, outCubic, inCubic.
|
||||||
|
notifier:
|
||||||
|
padding_h: 6
|
||||||
|
padding_top: 4
|
||||||
|
padding_bottom: 3
|
||||||
|
line_gap: 1
|
||||||
|
margin_top: 3
|
||||||
|
border_px: 1
|
||||||
|
slide_ms: 300
|
||||||
|
slide_in_curve: outCubic
|
||||||
|
slide_out_curve: inCubic
|
||||||
|
|
||||||
|
# Pausa graduada de la GameScene: el món no para d'un colp, s'atura amb
|
||||||
|
# easing i torna a arrancar amb una altra corba. Els timings es poden
|
||||||
|
# afinar independentment, i les corbes permeten estils diferents entrant
|
||||||
|
# i sortint (p.ex. frenar suau i arrancar seca). Corbes disponibles:
|
||||||
|
# linear, outQuad, inQuad, inOutQuad, outCubic, inCubic.
|
||||||
|
pause:
|
||||||
|
enter_ms: 400
|
||||||
|
exit_ms: 250
|
||||||
|
enter_curve: outCubic
|
||||||
|
exit_curve: inCubic
|
||||||
|
|
||||||
|
# Sons del menú de servei. Els fitxers viuen a data/sounds/. SELECT
|
||||||
|
# acompanya navegació (amunt/avall) i back; ACCEPT acompanya modificació
|
||||||
|
# d'opció (esquerra/dreta/retorn) o entrada a un submenú.
|
||||||
|
sounds:
|
||||||
|
menu_select: "menu_select.wav"
|
||||||
|
menu_accept: "menu_accept.wav"
|
||||||
@@ -276,10 +276,74 @@ Canvi a [CMakeLists.txt](../CMakeLists.txt): la còpia de `data/` al build era u
|
|||||||
|
|
||||||
- `Mummy::step` partit: `snapToPlayfieldEdge`, `collectDirectionPools`, `applyChoice` — per mantenir la complexitat cognitiva sota el llindar de clang-tidy (< 25).
|
- `Mummy::step` partit: `snapToPlayfieldEdge`, `collectDirectionPools`, `applyChoice` — per mantenir la complexitat cognitiva sota el llindar de clang-tidy (< 25).
|
||||||
|
|
||||||
## 8. Estat global
|
## 8. Fase 6 — Pintat de tiles (DONE)
|
||||||
|
|
||||||
- **Andamiatge, integració, render animat, falsa perspectiva, moviment, animació direccional de momia**: DONE. El `pyramid.yaml` actual (4 torches, 20 sarcs, 1 door, 2 mummies) es carrega, anima, col·loca amb l'anchor correcte, col·lisiona segons caixa lògica, el z-order interleava per peus, i la momia gira visualment a cada intersecció.
|
Introdueix la mecànica central: el jugador deixa rastre al terra en trepitjar-lo, i els sarcòfags compten tiles **pintats** (no tiles *trepitjats*). Font única de veritat del pintat: una nova capa `PaintLayer` escolta el moviment del Player, filtra pintabilitat i re-publica `tile_painted`, que és el que ara observen els sarcs.
|
||||||
- **Pendents majors**: ENT-HAZ-01 (sistema de vides), ENT-DOOR-01 (transició d'escena), ENT-IA-02 (bias IA), PER-01 (perímetre amb blocked_predicate quan els sarcs siguin veïns).
|
|
||||||
|
### Cadena d'events resultant
|
||||||
|
|
||||||
|
```
|
||||||
|
Player canvi de tile
|
||||||
|
→ bus: on_tile_entered { col, row, paint_tile_index }
|
||||||
|
→ PaintLayer: filtra Map::isPaintable + "primer pinta guanya"
|
||||||
|
→ buffer ARGB + bus: tile_painted { col, row }
|
||||||
|
→ EntityManager fan-out → Sarcophagus::notifyTilePainted
|
||||||
|
→ perímetre complet → OPENING → OPEN → sarcophagus_opened
|
||||||
|
→ tots oberts → Door::open
|
||||||
|
```
|
||||||
|
|
||||||
|
Dos events encadenats perquè el *pintat* no és la *presència*: es filtra per pintabilitat i per "ja pintat", i el re-publish garanteix que els sarcs no vegin falsos positius (p. ex. el jugador lliscant contra un mur, o un tile ja marcat per l'altre jugador).
|
||||||
|
|
||||||
|
### Canvis per fitxer
|
||||||
|
|
||||||
|
| Path | Canvi |
|
||||||
|
|---|---|
|
||||||
|
| [event_bus.hpp](../source/game/entities/event_bus.hpp) | Nou `struct PaintedTile { col, row, tile_index }` al `Payload` variant + overload `publish`. |
|
||||||
|
| [map.hpp](../source/game/map/map.hpp) / [.cpp](../source/game/map/map.cpp) | `Map::isPaintable(col, row)` → `true` si el tile cau dins del playfield i no s'encavalca cap sarc. |
|
||||||
|
| [map/paint_layer.hpp](../source/game/map/paint_layer.hpp) / [.cpp](../source/game/map/paint_layer.cpp) | Classe nova. Subscriu `on_tile_entered`, marca `painted_[row][col]`, blita el tile al buffer ARGB propi, publica `tile_painted`. `render()` anàleg a `Tilemap::render`. |
|
||||||
|
| [player.hpp](../source/game/entities/player.hpp) / [.cpp](../source/game/entities/player.cpp) | Camp `paint_tile_index_` al ctor (default -1). El Player publica `PaintedTile` amb el seu índex en lloc de `TilePos`. |
|
||||||
|
| [entity.hpp](../source/game/entities/entity.hpp) / [.cpp](../source/game/entities/entity.cpp) | Rename `notifyTileEntered` → `notifyTilePainted`. |
|
||||||
|
| [concrete/sarcophagus.hpp](../source/game/entities/concrete/sarcophagus.hpp) / [.cpp](../source/game/entities/concrete/sarcophagus.cpp) | Mateix rename. La lògica del `visited_` és idèntica — només canvia qui li truca el hook. |
|
||||||
|
| [entity_manager.hpp](../source/game/entities/entity_manager.hpp) / [.cpp](../source/game/entities/entity_manager.cpp) | La subscripció canvia d'`on_tile_entered` → `tile_painted`. Fan-out renombrat a `notifyPlayerTilePainted`. |
|
||||||
|
| [game_scene.hpp](../source/game/scenes/game_scene.hpp) / [.cpp](../source/game/scenes/game_scene.cpp) | Membre `std::unique_ptr<PaintLayer> paint_layer_` (declarat després d'`entity_manager_` per garantir ordre invers de destrucció). Helper local `readOptionalInt` llegeix `player1_paint_tile` del YAML del mapa i el passa al Player. `render()` crida `paint_layer_->render()` entre `tilemap_.render()` i els actors; s'amaga al mode editor. |
|
||||||
|
| [data/maps/pyramid.yaml](../data/maps/pyramid.yaml) | Camps nous `player1_paint_tile: 2` i `player2_paint_tile: 2` (índex provisional, canviable sense recompilar). |
|
||||||
|
| [tests/test_entities_smoke.cpp](../tests/test_entities_smoke.cpp) | T9 adaptat: publica `tile_painted` directament al bus per aïllar el fan-out d'EntityManager de la resta de la cadena. |
|
||||||
|
|
||||||
|
### Decisions de disseny
|
||||||
|
|
||||||
|
- **Centralitzat vs per-sarc**: l'estat "pintat" viu al `PaintLayer`, no dins cada sarc. Els sarcs només apunten els tiles del seu perímetre que han estat notificats pintats. Aquesta separació deixa preparat per a items-en-sarc (obrir quan `N` items trobats, no quan tots oberts) i per a altres productors de pintat futurs (enemics, trampes).
|
||||||
|
- **"Primer pinta guanya"**: `PaintLayer::tryPaint` retorna false si el tile ja està pintat. No se sobreescriu amb l'índex d'un altre jugador. Reduïm fonts de decisió inconsistent; el rastre visual és estable.
|
||||||
|
- **Paint tile per jugador**: l'índex l'aporta el YAML del mapa (`player1_paint_tile`, `player2_paint_tile`), no el YAML del jugador. Raó: depèn del tileset del mapa — cada nivell pot oferir rastres diferents; canviar de mapa canvia l'índex vàlid.
|
||||||
|
- **Render al mode editor**: desactivat. L'editor edita el tilemap base; la capa de pintat és d'estat de joc, no de dades del nivell. Quan `editor_.active()`, `SKIP_ACTORS=true` i el `paint_layer_->render()` se salta amb la resta d'actors.
|
||||||
|
- **Resolució**: 16×16 reutilitzant `Map::tile_size` i l'origen del tilemap. El `PaintLayer` emmagatzema `tile_size_` com a camp propi per facilitar una eventual migració a 8×8 (graella 2× més fina) sense tocar la interfície.
|
||||||
|
- **Persistència**: runtime only. No hi ha camp `painted:` al YAML. Entrar al `GameScene` reseteja el pintat.
|
||||||
|
- **No cal `is_blocked` al `sarc->place(...)`** per a aquest nivell: l'`outer_corridor` del `pyramid.yaml` és exactament 1 tile, cosa que garanteix que el perímetre de qualsevol sarc (fins i tot els de cantonades) cau íntegrament dins del playfield i és pintable. Quedaria rellevant si el disseny futur posa sarcs sense passadís al voltant — cobert per PER-01.
|
||||||
|
|
||||||
|
### Verificació
|
||||||
|
|
||||||
|
- `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.
|
||||||
|
|
||||||
|
### Pendent derivat
|
||||||
|
|
||||||
|
| ID | Descripció | Estat |
|
||||||
|
|---|---|---|
|
||||||
|
| PAINT-01 | **Tile de pintat definitiu** quan l'art estigui madur. Ara `2` provisional al `pyramid.yaml`. Canviar l'índex no demana recompilar. | DEFERRED |
|
||||||
|
| PAINT-02 | **Resolució 8×8** si es vol més granularitat. El `PaintLayer` porta el seu propi `tile_size_`; cal propagar-lo al ctor per a no lligar-lo a `Tilemap::getTileSize()`. També cal decidir com es mapen els perímetres dels sarcs a sub-caselles. | DEFERRED |
|
||||||
|
| PAINT-03 | **Smoke unit del `PaintLayer`**: "primer pinta guanya", filtre d'`isPaintable`, publicació de `tile_painted`. Ara la cadena està coberta end-to-end via T9 (fan-out del bus) i via l'smoke d'arrencada, però el filtre i la deduplicació no tenen test directe. | DEFERRED |
|
||||||
|
| PAINT-04 | **Player 2 cablejat al `GameScene`**: la lectura del YAML i el ctor ja suporten un segon jugador amb `paint_tile_index_` propi; només cal instanciar-lo amb Controls distints (p. ex. WASD) i afegir-lo al vector. | DEFERRED |
|
||||||
|
|
||||||
|
### Memòria durable afectada
|
||||||
|
|
||||||
|
INF-02 queda parcialment cobert: el `PaintedTile` ja porta `tile_index` (per jugador), que és una forma d'identificar qui pinta sense haver de propagar un `player_id` explícit. Si en el futur cal saber *qui* (p. ex. per puntuació individual), caldrà afegir el camp; el canvi és local al struct.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Estat global
|
||||||
|
|
||||||
|
- **Andamiatge, integració, render animat, falsa perspectiva, moviment, animació direccional de momia, pintat de tiles**: DONE. El `pyramid.yaml` actual (4 torches, 20 sarcs, 1 door, 2 mummies) es carrega, anima, col·loca amb l'anchor correcte, col·lisiona segons caixa lògica, el z-order interleava per peus, la momia gira visualment a cada intersecció, i el jugador deixa rastre al terra.
|
||||||
|
- **Pendents majors**: ENT-HAZ-01 (sistema de vides), ENT-DOOR-01 (transició d'escena), ENT-IA-02 (bias IA), PER-01 (perímetre amb blocked_predicate quan els sarcs siguin veïns), PAINT-03 (test unit del PaintLayer), PAINT-04 (Player 2 cablejat).
|
||||||
- **Smoke tests**: 14/14 passant; regla durable de cobrir amb smoke tota lògica no trivial respectada a totes les fases.
|
- **Smoke tests**: 14/14 passant; regla durable de cobrir amb smoke tota lògica no trivial respectada a totes les fases.
|
||||||
|
|
||||||
El document és viu: cada tasca nova hauria d'afegir-s'hi (entrada amb ID, descripció, estat) al fer-la o ajornar-la.
|
El document és viu: cada tasca nova hauria d'afegir-s'hi (entrada amb ID, descripció, estat) al fer-la o ajornar-la.
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "core/audio/audio_adapter.hpp" // Per a AudioResource::getMusic/getSound
|
#include "core/audio/audio_adapter.hpp" // Per a AudioResource::getMusic/getSound
|
||||||
#include "core/audio/jail_audio.hpp" // Per a Ja::* (motor jailgames)
|
#include "core/audio/jail_audio.hpp" // Per a Ja::* (motor jailgames)
|
||||||
|
#include "core/audio/sound_effects_config.hpp" // Per a SoundEffectsConfig
|
||||||
#include "utils/defaults.hpp" // Per a Defaults::Audio::FREQUENCY
|
#include "utils/defaults.hpp" // Per a Defaults::Audio::FREQUENCY
|
||||||
|
|
||||||
// Invariant compile-time: tots els valors d'Audio::Group han de cabre als slots
|
// Invariant compile-time: tots els valors d'Audio::Group han de cabre als slots
|
||||||
@@ -128,6 +129,47 @@ void Audio::playSound(Ja::Sound* sound, Group group) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reprodueix un so processat per un eco definit a sounds.yaml. Si el preset no
|
||||||
|
// existeix o l'engine retorna -1 (cap de canals d'efecte plé), cau a playSound
|
||||||
|
// sec — l'usuari sent el so encara que la cua no s'apliqui.
|
||||||
|
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_
|
||||||
|
void Audio::playSoundWithEcho(const std::string& name, const std::string& preset_name, Group group) {
|
||||||
|
if (!sound_enabled_) { return; }
|
||||||
|
auto* sound = AudioResource::getSound(name);
|
||||||
|
if (sound == nullptr) { return; }
|
||||||
|
|
||||||
|
const auto* params = SoundEffectsConfig::get().findEcho(preset_name);
|
||||||
|
if (params == nullptr) {
|
||||||
|
std::fprintf(stderr, "Audio: preset d'eco '%s' desconegut — so '%s' es reprodueix sec\n", preset_name.c_str(), name.c_str());
|
||||||
|
engine_->playSound(sound, 0, static_cast<int>(group));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (engine_->playSoundWithEcho(sound, *params, static_cast<int>(group)) < 0) {
|
||||||
|
engine_->playSound(sound, 0, static_cast<int>(group));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reprodueix un so processat per un reverb definit a sounds.yaml. Mateix
|
||||||
|
// fallback que playSoundWithEcho.
|
||||||
|
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_
|
||||||
|
void Audio::playSoundWithReverb(const std::string& name, const std::string& preset_name, Group group) {
|
||||||
|
if (!sound_enabled_) { return; }
|
||||||
|
auto* sound = AudioResource::getSound(name);
|
||||||
|
if (sound == nullptr) { return; }
|
||||||
|
|
||||||
|
const auto* params = SoundEffectsConfig::get().findReverb(preset_name);
|
||||||
|
if (params == nullptr) {
|
||||||
|
std::fprintf(stderr, "Audio: preset de reverb '%s' desconegut — so '%s' es reprodueix sec\n", preset_name.c_str(), name.c_str());
|
||||||
|
engine_->playSound(sound, 0, static_cast<int>(group));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (engine_->playSoundWithReverb(sound, *params, static_cast<int>(group)) < 0) {
|
||||||
|
engine_->playSound(sound, 0, static_cast<int>(group));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Atura tots els sons
|
// Atura tots els sons
|
||||||
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_; unique_ptr::operator-> no propaga const
|
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_; unique_ptr::operator-> no propaga const
|
||||||
void Audio::stopAllSounds() {
|
void Audio::stopAllSounds() {
|
||||||
|
|||||||
@@ -80,6 +80,12 @@ class Audio {
|
|||||||
// --- Control de sons ---
|
// --- Control de sons ---
|
||||||
void playSound(const std::string& name, Group group = Group::GAME); // Reproduir so puntual per nom (muta globals de Ja)
|
void playSound(const std::string& name, Group group = Group::GAME); // Reproduir so puntual per nom (muta globals de Ja)
|
||||||
void playSound(Ja::Sound* sound, Group group = Group::GAME); // Reproduir so puntual per punter (muta globals de Ja)
|
void playSound(Ja::Sound* sound, Group group = Group::GAME); // Reproduir so puntual per punter (muta globals de Ja)
|
||||||
|
// Reprodueix un so processat per un efecte definit a data/sounds.yaml
|
||||||
|
// (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.
|
||||||
|
void playSoundWithEcho(const std::string& name, const std::string& preset_name, Group group = Group::GAME);
|
||||||
|
void playSoundWithReverb(const std::string& name, const std::string& preset_name, Group group = Group::GAME);
|
||||||
void stopAllSounds(); // Aturar tots els sons (muta globals de Ja)
|
void stopAllSounds(); // Aturar tots els sons (muta globals de Ja)
|
||||||
|
|
||||||
// --- Control de volum (API interna: float 0.0..1.0) ---
|
// --- Control de volum (API interna: float 0.0..1.0) ---
|
||||||
|
|||||||
@@ -0,0 +1,250 @@
|
|||||||
|
#include "core/audio/audio_effects.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "core/audio/jail_audio.hpp"
|
||||||
|
|
||||||
|
namespace AudioEffects {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// --- Caps de cua ---
|
||||||
|
constexpr float ECHO_TAIL_MS = 800.0F;
|
||||||
|
constexpr float REVERB_TAIL_MS = 1500.0F;
|
||||||
|
|
||||||
|
// --- Constants Freeverb ---
|
||||||
|
// Delays de comb i allpass tunats per a 44.1 kHz; els reescalem per
|
||||||
|
// freqüència real de la font.
|
||||||
|
constexpr int COMB_REFERENCE_RATE = 44100;
|
||||||
|
constexpr std::array<int, 8> COMB_DELAYS_L = {1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617};
|
||||||
|
constexpr std::array<int, 4> ALLPASS_DELAYS_L = {556, 441, 341, 225};
|
||||||
|
constexpr int STEREO_SPREAD = 23;
|
||||||
|
|
||||||
|
// Mapeig de Schroeder/Dattorro/Freeverb estàndard.
|
||||||
|
constexpr float FIXED_GAIN = 0.015F;
|
||||||
|
constexpr float SCALE_ROOM = 0.28F;
|
||||||
|
constexpr float OFFSET_ROOM = 0.7F;
|
||||||
|
constexpr float SCALE_DAMP = 0.4F;
|
||||||
|
|
||||||
|
// --- Decodificació a float -1..1 ---
|
||||||
|
// Suporta U8/S16, mono/estèreo. Mono es duplica a L i R (la cadena
|
||||||
|
// d'efectes treballa sempre amb dos canals per simplicitat).
|
||||||
|
auto decodeToStereoFloat(const Ja::Sound& src, std::vector<float>& left, std::vector<float>& right) -> bool {
|
||||||
|
const auto& spec = src.spec;
|
||||||
|
const Uint8* buf = src.buffer.get();
|
||||||
|
if (buf == nullptr || src.length == 0) { return false; }
|
||||||
|
|
||||||
|
int bytes_per_sample = 0;
|
||||||
|
if (spec.format == SDL_AUDIO_S16) {
|
||||||
|
bytes_per_sample = 2;
|
||||||
|
} 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";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (spec.channels < 1 || spec.channels > 2) {
|
||||||
|
std::cerr << "AudioEffects: el so ha de ser mono o estèreo\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t TOTAL_FRAMES = src.length / static_cast<std::size_t>(bytes_per_sample * spec.channels);
|
||||||
|
left.resize(TOTAL_FRAMES);
|
||||||
|
right.resize(TOTAL_FRAMES);
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < TOTAL_FRAMES; ++i) {
|
||||||
|
float sample_l = 0.0F;
|
||||||
|
float sample_r = 0.0F;
|
||||||
|
if (spec.format == SDL_AUDIO_S16) {
|
||||||
|
const auto* p = reinterpret_cast<const std::int16_t*>(buf + (i * spec.channels * 2));
|
||||||
|
sample_l = static_cast<float>(p[0]) / 32768.0F;
|
||||||
|
sample_r = (spec.channels == 2) ? static_cast<float>(p[1]) / 32768.0F : sample_l;
|
||||||
|
} else { // U8
|
||||||
|
const Uint8* p = buf + (i * spec.channels);
|
||||||
|
sample_l = (static_cast<float>(p[0]) - 128.0F) / 128.0F;
|
||||||
|
sample_r = (spec.channels == 2) ? (static_cast<float>(p[1]) - 128.0F) / 128.0F : sample_l;
|
||||||
|
}
|
||||||
|
left[i] = sample_l;
|
||||||
|
right[i] = sample_r;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empaqueta dos canals float (-1..1) a S16 entrellaçat.
|
||||||
|
void encodeStereoS16(const std::vector<float>& left, const std::vector<float>& right, std::vector<std::uint8_t>& out) {
|
||||||
|
const std::size_t LEN = left.size();
|
||||||
|
out.resize(LEN * 2 * sizeof(std::int16_t));
|
||||||
|
auto* dst = reinterpret_cast<std::int16_t*>(out.data());
|
||||||
|
for (std::size_t i = 0; i < LEN; ++i) {
|
||||||
|
const float L = std::clamp(left[i], -1.0F, 1.0F);
|
||||||
|
const float R = std::clamp(right[i], -1.0F, 1.0F);
|
||||||
|
dst[(i * 2) + 0] = static_cast<std::int16_t>(std::lround(L * 32767.0F));
|
||||||
|
dst[(i * 2) + 1] = static_cast<std::int16_t>(std::lround(R * 32767.0F));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reescala un delay de la taula de Freeverb per a la freqüència real.
|
||||||
|
auto scaledDelay(int reference_delay, int rate) -> int {
|
||||||
|
const long SCALED = std::lround(static_cast<double>(reference_delay) * static_cast<double>(rate) / static_cast<double>(COMB_REFERENCE_RATE));
|
||||||
|
return std::max(1, static_cast<int>(SCALED));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Filtres bàsics ---
|
||||||
|
struct Comb {
|
||||||
|
std::vector<float> buf;
|
||||||
|
std::size_t idx{0};
|
||||||
|
float feedback{0.0F};
|
||||||
|
float damp1{0.0F};
|
||||||
|
float damp2{1.0F};
|
||||||
|
float store{0.0F};
|
||||||
|
|
||||||
|
void init(int delay, float fb, float damping) {
|
||||||
|
buf.assign(static_cast<std::size_t>(delay), 0.0F);
|
||||||
|
idx = 0;
|
||||||
|
feedback = fb;
|
||||||
|
damp1 = damping;
|
||||||
|
damp2 = 1.0F - damping;
|
||||||
|
store = 0.0F;
|
||||||
|
}
|
||||||
|
auto tick(float in) -> float {
|
||||||
|
const float OUT = buf[idx];
|
||||||
|
store = (OUT * damp2) + (store * damp1);
|
||||||
|
buf[idx] = in + (store * feedback);
|
||||||
|
idx = (idx + 1) % buf.size();
|
||||||
|
return OUT;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Allpass {
|
||||||
|
std::vector<float> buf;
|
||||||
|
std::size_t idx{0};
|
||||||
|
|
||||||
|
void init(int delay) {
|
||||||
|
buf.assign(static_cast<std::size_t>(delay), 0.0F);
|
||||||
|
idx = 0;
|
||||||
|
}
|
||||||
|
auto tick(float in) -> float {
|
||||||
|
const float BUFOUT = buf[idx];
|
||||||
|
const float OUT = -in + BUFOUT;
|
||||||
|
buf[idx] = in + (BUFOUT * 0.5F);
|
||||||
|
idx = (idx + 1) % buf.size();
|
||||||
|
return OUT;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
auto applyEcho(const Ja::Sound& src, const Ja::EchoParams& params) -> std::optional<ProcessedSound> {
|
||||||
|
std::vector<float> left;
|
||||||
|
std::vector<float> right;
|
||||||
|
if (!decodeToStereoFloat(src, left, right)) { return std::nullopt; }
|
||||||
|
|
||||||
|
const int RATE = src.spec.freq;
|
||||||
|
const int DELAY_SAMPLES = std::max(1, static_cast<int>(std::lround(params.delay_ms * 0.001F * static_cast<float>(RATE))));
|
||||||
|
const auto TAIL_SAMPLES = static_cast<std::size_t>(std::lround(ECHO_TAIL_MS * 0.001F * static_cast<float>(RATE)));
|
||||||
|
|
||||||
|
const float FEEDBACK = std::clamp(params.feedback, 0.0F, 0.95F);
|
||||||
|
const float WET = std::clamp(params.wet, 0.0F, 1.0F);
|
||||||
|
const float DRY = 1.0F - WET;
|
||||||
|
|
||||||
|
const std::size_t INPUT_LEN = left.size();
|
||||||
|
const std::size_t TOTAL_LEN = INPUT_LEN + TAIL_SAMPLES;
|
||||||
|
|
||||||
|
std::vector<float> ring_l(static_cast<std::size_t>(DELAY_SAMPLES), 0.0F);
|
||||||
|
std::vector<float> ring_r(static_cast<std::size_t>(DELAY_SAMPLES), 0.0F);
|
||||||
|
std::size_t cursor = 0;
|
||||||
|
|
||||||
|
std::vector<float> out_l(TOTAL_LEN);
|
||||||
|
std::vector<float> out_r(TOTAL_LEN);
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < TOTAL_LEN; ++i) {
|
||||||
|
const float IN_L = (i < INPUT_LEN) ? left[i] : 0.0F;
|
||||||
|
const float IN_R = (i < INPUT_LEN) ? right[i] : 0.0F;
|
||||||
|
|
||||||
|
const float DELAYED_L = ring_l[cursor];
|
||||||
|
const float DELAYED_R = ring_r[cursor];
|
||||||
|
|
||||||
|
out_l[i] = (DRY * IN_L) + (WET * DELAYED_L);
|
||||||
|
out_r[i] = (DRY * IN_R) + (WET * DELAYED_R);
|
||||||
|
|
||||||
|
ring_l[cursor] = IN_L + (DELAYED_L * FEEDBACK);
|
||||||
|
ring_r[cursor] = IN_R + (DELAYED_R * FEEDBACK);
|
||||||
|
cursor = (cursor + 1) % static_cast<std::size_t>(DELAY_SAMPLES);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessedSound result;
|
||||||
|
result.spec = SDL_AudioSpec{.format = SDL_AUDIO_S16, .channels = 2, .freq = RATE};
|
||||||
|
encodeStereoS16(out_l, out_r, result.bytes);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto applyReverb(const Ja::Sound& src, const Ja::ReverbParams& params) -> std::optional<ProcessedSound> {
|
||||||
|
std::vector<float> left;
|
||||||
|
std::vector<float> right;
|
||||||
|
if (!decodeToStereoFloat(src, left, right)) { return std::nullopt; }
|
||||||
|
|
||||||
|
const int RATE = src.spec.freq;
|
||||||
|
const auto TAIL_SAMPLES = static_cast<std::size_t>(std::lround(REVERB_TAIL_MS * 0.001F * static_cast<float>(RATE)));
|
||||||
|
|
||||||
|
const float ROOM_SIZE = std::clamp(params.room_size, 0.0F, 1.0F);
|
||||||
|
const float DAMPING = std::clamp(params.damping, 0.0F, 1.0F);
|
||||||
|
const float WET = std::clamp(params.wet, 0.0F, 1.0F);
|
||||||
|
const float DRY = 1.0F - WET;
|
||||||
|
|
||||||
|
const float FEEDBACK = (ROOM_SIZE * SCALE_ROOM) + OFFSET_ROOM; // 0.7..0.98
|
||||||
|
const float DAMP1 = DAMPING * SCALE_DAMP; // 0..0.4
|
||||||
|
|
||||||
|
// Inicialitza els 8 comb filters per cada canal i els 4 allpass.
|
||||||
|
std::array<Comb, 8> comb_l;
|
||||||
|
std::array<Comb, 8> comb_r;
|
||||||
|
for (std::size_t i = 0; i < COMB_DELAYS_L.size(); ++i) {
|
||||||
|
comb_l[i].init(scaledDelay(COMB_DELAYS_L[i], RATE), FEEDBACK, DAMP1);
|
||||||
|
comb_r[i].init(scaledDelay(COMB_DELAYS_L[i] + STEREO_SPREAD, RATE), FEEDBACK, DAMP1);
|
||||||
|
}
|
||||||
|
std::array<Allpass, 4> allpass_l;
|
||||||
|
std::array<Allpass, 4> allpass_r;
|
||||||
|
for (std::size_t i = 0; i < ALLPASS_DELAYS_L.size(); ++i) {
|
||||||
|
allpass_l[i].init(scaledDelay(ALLPASS_DELAYS_L[i], RATE));
|
||||||
|
allpass_r[i].init(scaledDelay(ALLPASS_DELAYS_L[i] + STEREO_SPREAD, RATE));
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t INPUT_LEN = left.size();
|
||||||
|
const std::size_t TOTAL_LEN = INPUT_LEN + TAIL_SAMPLES;
|
||||||
|
std::vector<float> out_l(TOTAL_LEN);
|
||||||
|
std::vector<float> out_r(TOTAL_LEN);
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < TOTAL_LEN; ++i) {
|
||||||
|
const float IN_L = (i < INPUT_LEN) ? left[i] : 0.0F;
|
||||||
|
const float IN_R = (i < INPUT_LEN) ? right[i] : 0.0F;
|
||||||
|
const float MONO_INPUT = (IN_L + IN_R) * FIXED_GAIN;
|
||||||
|
|
||||||
|
// 8 comb filters en paral·lel, sumats.
|
||||||
|
float wet_l = 0.0F;
|
||||||
|
float wet_r = 0.0F;
|
||||||
|
for (std::size_t k = 0; k < comb_l.size(); ++k) {
|
||||||
|
wet_l += comb_l[k].tick(MONO_INPUT);
|
||||||
|
wet_r += comb_r[k].tick(MONO_INPUT);
|
||||||
|
}
|
||||||
|
// 4 allpass en sèrie.
|
||||||
|
for (std::size_t k = 0; k < allpass_l.size(); ++k) {
|
||||||
|
wet_l = allpass_l[k].tick(wet_l);
|
||||||
|
wet_r = allpass_r[k].tick(wet_r);
|
||||||
|
}
|
||||||
|
|
||||||
|
out_l[i] = (DRY * IN_L) + (WET * wet_l);
|
||||||
|
out_r[i] = (DRY * IN_R) + (WET * wet_r);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessedSound result;
|
||||||
|
result.spec = SDL_AudioSpec{.format = SDL_AUDIO_S16, .channels = 2, .freq = RATE};
|
||||||
|
encodeStereoS16(out_l, out_r, result.bytes);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace AudioEffects
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// Forward-declaració per no incloure jail_audio.hpp (cicle d'inclusió: aquest
|
||||||
|
// header viu sota els params declarats a jail_audio.hpp, i alhora jail_audio
|
||||||
|
// usa applyEcho/applyReverb).
|
||||||
|
namespace Ja {
|
||||||
|
struct Sound;
|
||||||
|
struct EchoParams;
|
||||||
|
struct ReverbParams;
|
||||||
|
} // namespace Ja
|
||||||
|
|
||||||
|
// Processadors d'efectes per a sons puntuals. Reben un Ja::Sound (qualsevol
|
||||||
|
// format suportat pel decodificador WAV: U8/S16, mono o estèreo) i tornen un
|
||||||
|
// buffer PCM en S16 + el seu spec, llest per empenyer a un SDL_AudioStream.
|
||||||
|
//
|
||||||
|
// El buffer de sortida inclou la cua (decay) generada per l'efecte: per al
|
||||||
|
// reverb, fins a 1500 ms; per a l'eco, fins a 800 ms. Aquests caps eviten
|
||||||
|
// allargar indefinidament la reproducció quan els paràmetres reinjecten molt.
|
||||||
|
//
|
||||||
|
// Si el format del so d'origen no es pot processar, retornen std::nullopt
|
||||||
|
// (el caller ha de fer fallback a reproducció seca).
|
||||||
|
namespace AudioEffects {
|
||||||
|
|
||||||
|
struct ProcessedSound {
|
||||||
|
std::vector<std::uint8_t> bytes; // PCM S16 entrellaçat (LRLRLR... si stereo)
|
||||||
|
SDL_AudioSpec spec; // Format/canals/freqüència del buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] auto applyEcho(const Ja::Sound& src, const Ja::EchoParams& params) -> std::optional<ProcessedSound>;
|
||||||
|
[[nodiscard]] auto applyReverb(const Ja::Sound& src, const Ja::ReverbParams& params) -> std::optional<ProcessedSound>;
|
||||||
|
|
||||||
|
} // namespace AudioEffects
|
||||||
@@ -4,10 +4,15 @@
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
// Implementació completa de stb_vorbis en aquest TU. HEADER_ONLY es queda
|
#include "core/audio/audio_effects.hpp"
|
||||||
// indefinit aquí perquè el .c aporti les definicions. La resta del projecte
|
|
||||||
// només veu el forward-decl de jail_audio.hpp.
|
// Només declaracions de stb_vorbis: STB_VORBIS_HEADER_ONLY omet el bloc
|
||||||
|
// d'implementació. Les definicions les aporta source/external/stb_vorbis_impl.cpp
|
||||||
|
// (TU aïllat perquè clang-analyzer no dispari fals positius al nostre codi).
|
||||||
|
#define STB_VORBIS_HEADER_ONLY
|
||||||
// clang-format off
|
// clang-format off
|
||||||
// NOLINTNEXTLINE(bugprone-suspicious-include)
|
// NOLINTNEXTLINE(bugprone-suspicious-include)
|
||||||
#include "external/stb_vorbis.c"
|
#include "external/stb_vorbis.c"
|
||||||
@@ -396,13 +401,17 @@ namespace Ja {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Engine::stopChannel(const int channel) {
|
void Engine::stopChannel(const int channel) {
|
||||||
forEachTargetChannel(channel, [](Channel& ch) {
|
forEachTargetChannel(channel, [this](Channel& ch) {
|
||||||
if (ch.state != ChannelState::FREE) {
|
if (ch.state != ChannelState::FREE) {
|
||||||
if (ch.stream != nullptr) { SDL_DestroyAudioStream(ch.stream); }
|
if (ch.stream != nullptr) { SDL_DestroyAudioStream(ch.stream); }
|
||||||
ch.stream = nullptr;
|
ch.stream = nullptr;
|
||||||
ch.state = ChannelState::FREE;
|
ch.state = ChannelState::FREE;
|
||||||
ch.pos = 0;
|
ch.pos = 0;
|
||||||
ch.sound = nullptr;
|
ch.sound = nullptr;
|
||||||
|
if (ch.has_effect) {
|
||||||
|
ch.has_effect = false;
|
||||||
|
if (effect_channels_active_ > 0) { --effect_channels_active_; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -437,6 +446,55 @@ namespace Ja {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Engine::playProcessedOnFreeChannel(const std::vector<std::uint8_t>& bytes, const SDL_AudioSpec& spec, const int group) -> int {
|
||||||
|
// El cap de canals amb efecte es valida abans de reservar slot —
|
||||||
|
// així evitem crear i destruir un stream només per descartar el play.
|
||||||
|
if (effect_channels_active_ >= MAX_EFFECT_CHANNELS) { return -1; }
|
||||||
|
|
||||||
|
int channel = 0;
|
||||||
|
while (channel < MAX_SIMULTANEOUS_CHANNELS && channels_[channel].state != ChannelState::FREE) { ++channel; }
|
||||||
|
if (channel == MAX_SIMULTANEOUS_CHANNELS) { channel = 0; }
|
||||||
|
|
||||||
|
stopChannel(channel);
|
||||||
|
|
||||||
|
// El stream es crea contra l'spec del buffer processat (S16, ...)
|
||||||
|
// perquè SDL faci el resampling cap a audio_spec_ del device.
|
||||||
|
channels_[channel].stream = SDL_CreateAudioStream(&spec, &audio_spec_);
|
||||||
|
if (channels_[channel].stream == nullptr) {
|
||||||
|
std::fprintf(stderr, "Ja::Engine::playProcessedOnFreeChannel: Failed to create audio stream!\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
channels_[channel].sound = nullptr; // El buffer no és propietat de cap Ja::Sound.
|
||||||
|
channels_[channel].times = 0;
|
||||||
|
channels_[channel].pos = 0;
|
||||||
|
const int CLAMPED_GROUP = (group >= 0 && group < MAX_GROUPS) ? group : 0;
|
||||||
|
channels_[channel].group = CLAMPED_GROUP;
|
||||||
|
channels_[channel].state = ChannelState::PLAYING;
|
||||||
|
channels_[channel].has_effect = true;
|
||||||
|
++effect_channels_active_;
|
||||||
|
|
||||||
|
SDL_PutAudioStreamData(channels_[channel].stream, bytes.data(), static_cast<int>(bytes.size()));
|
||||||
|
SDL_SetAudioStreamGain(channels_[channel].stream, sound_volume_[CLAMPED_GROUP]);
|
||||||
|
SDL_BindAudioStream(sdl_audio_device_, channels_[channel].stream);
|
||||||
|
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Engine::playSoundWithEcho(const Sound* sound, const EchoParams& params, const int group) -> int {
|
||||||
|
if (sound == nullptr) { return -1; }
|
||||||
|
auto processed = AudioEffects::applyEcho(*sound, params);
|
||||||
|
if (!processed) { return -1; }
|
||||||
|
return playProcessedOnFreeChannel(processed->bytes, processed->spec, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Engine::playSoundWithReverb(const Sound* sound, const ReverbParams& params, const int group) -> int {
|
||||||
|
if (sound == nullptr) { return -1; }
|
||||||
|
auto processed = AudioEffects::applyReverb(*sound, params);
|
||||||
|
if (!processed) { return -1; }
|
||||||
|
return playProcessedOnFreeChannel(processed->bytes, processed->spec, group);
|
||||||
|
}
|
||||||
|
|
||||||
// --- Factories i destructors (permanents) ---
|
// --- Factories i destructors (permanents) ---
|
||||||
|
|
||||||
auto loadMusic(const Uint8* buffer, Uint32 length) -> Music* {
|
auto loadMusic(const Uint8* buffer, Uint32 length) -> Music* {
|
||||||
|
|||||||
@@ -48,6 +48,26 @@ namespace Ja {
|
|||||||
// --- Constants ---
|
// --- Constants ---
|
||||||
inline constexpr int MAX_SIMULTANEOUS_CHANNELS = 20;
|
inline constexpr int MAX_SIMULTANEOUS_CHANNELS = 20;
|
||||||
inline constexpr int MAX_GROUPS = 2;
|
inline constexpr int MAX_GROUPS = 2;
|
||||||
|
// Cap superior de canals que poden estar simultàniament reproduint un so
|
||||||
|
// amb efecte (eco/reverb). Si està al límit, les noves crides amb efecte
|
||||||
|
// cauen al camí sec — l'usuari sent el so igualment, sense la cua.
|
||||||
|
inline constexpr int MAX_EFFECT_CHANNELS = 4;
|
||||||
|
|
||||||
|
// --- Paràmetres d'efectes ---
|
||||||
|
// Els camps els fixa el caller (Audio) llegint sounds.yaml; el motor només
|
||||||
|
// els passa a AudioEffects::applyEcho/applyReverb. Els defaults són
|
||||||
|
// sensats però els presets els sobreescriuen.
|
||||||
|
struct EchoParams {
|
||||||
|
float delay_ms{220.0F}; // Temps fins al primer rebot.
|
||||||
|
float feedback{0.45F}; // Reinjecció (0..0.95).
|
||||||
|
float wet{0.35F}; // Mescla humida (0..1).
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ReverbParams {
|
||||||
|
float room_size{0.7F}; // Mida percebuda (0..1).
|
||||||
|
float damping{0.5F}; // Atenuació d'aguts per rebot (0..1).
|
||||||
|
float wet{0.4F}; // Mescla humida (0..1).
|
||||||
|
};
|
||||||
|
|
||||||
// Spec de fallback del dispositiu. S'aplica abans que l'Engine s'iniciï i
|
// Spec de fallback del dispositiu. S'aplica abans que l'Engine s'iniciï i
|
||||||
// com a valor inicial de Sound/Music. L'spec real d'ús l'imposa el ctor
|
// com a valor inicial de Sound/Music. L'spec real d'ús l'imposa el ctor
|
||||||
@@ -72,6 +92,10 @@ namespace Ja {
|
|||||||
int times{0};
|
int times{0};
|
||||||
int group{0};
|
int group{0};
|
||||||
ChannelState state{ChannelState::FREE};
|
ChannelState state{ChannelState::FREE};
|
||||||
|
// Marca si aquest canal va arrencar amb so processat per un efecte.
|
||||||
|
// El motor compta canals actius amb efecte per fer complir
|
||||||
|
// MAX_EFFECT_CHANNELS i alliberar el comptador en parar.
|
||||||
|
bool has_effect{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Music {
|
struct Music {
|
||||||
@@ -147,6 +171,12 @@ namespace Ja {
|
|||||||
// --- So ---
|
// --- So ---
|
||||||
auto playSound(Sound* sound, int loop = 0, int group = 0) -> int;
|
auto playSound(Sound* sound, int loop = 0, int group = 0) -> int;
|
||||||
auto playSoundOnChannel(Sound* sound, int channel, int loop = 0, int group = 0) -> int;
|
auto playSoundOnChannel(Sound* sound, int channel, int loop = 0, int group = 0) -> int;
|
||||||
|
// Reproducció amb so processat per un efecte. Retorna el canal
|
||||||
|
// assignat o -1 si no queden slots d'efecte (MAX_EFFECT_CHANNELS).
|
||||||
|
// El sound original només s'usa per consultar el spec/buffer; el
|
||||||
|
// canal manipula el buffer ja processat (no reapunta a `sound`).
|
||||||
|
auto playSoundWithEcho(const Sound* sound, const EchoParams& params, int group = 0) -> int;
|
||||||
|
auto playSoundWithReverb(const Sound* sound, const ReverbParams& params, int group = 0) -> int;
|
||||||
void pauseChannel(int channel);
|
void pauseChannel(int channel);
|
||||||
void resumeChannel(int channel);
|
void resumeChannel(int channel);
|
||||||
void stopChannel(int channel);
|
void stopChannel(int channel);
|
||||||
@@ -161,6 +191,10 @@ namespace Ja {
|
|||||||
void updateIncomingFade();
|
void updateIncomingFade();
|
||||||
void updateCurrentMusic();
|
void updateCurrentMusic();
|
||||||
void updateSoundChannels();
|
void updateSoundChannels();
|
||||||
|
// Empenta un buffer ja processat (S16) a un canal lliure i el deixa
|
||||||
|
// sonar sense bucle. Camí comú dels dos overloads playSoundWith*.
|
||||||
|
// Retorna el canal o -1 si no queden slots.
|
||||||
|
auto playProcessedOnFreeChannel(const std::vector<std::uint8_t>& bytes, const SDL_AudioSpec& spec, int group) -> int;
|
||||||
|
|
||||||
template <typename Fn>
|
template <typename Fn>
|
||||||
void forEachTargetChannel(int channel, Fn&& fn);
|
void forEachTargetChannel(int channel, Fn&& fn);
|
||||||
@@ -174,6 +208,9 @@ namespace Ja {
|
|||||||
OutgoingMusic outgoing_music_;
|
OutgoingMusic outgoing_music_;
|
||||||
FadeState incoming_fade_;
|
FadeState incoming_fade_;
|
||||||
std::function<void()> on_music_ended_;
|
std::function<void()> on_music_ended_;
|
||||||
|
// Comptador derivat de Channel::has_effect — evita haver-lo de
|
||||||
|
// recalcular cada vegada que algú demana un play amb efecte.
|
||||||
|
int effect_channels_active_{0};
|
||||||
|
|
||||||
// NOLINTNEXTLINE(readability-identifier-naming) — convenció projecte: private static amb sufix _
|
// NOLINTNEXTLINE(readability-identifier-naming) — convenció projecte: private static amb sufix _
|
||||||
static Engine* active_;
|
static Engine* active_;
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
#include "core/audio/sound_effects_config.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "core/resources/resource_helper.hpp"
|
||||||
|
#include "external/fkyaml_node.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Lector de camp amb fallback: deixa el destí intacte si la clau no
|
||||||
|
// existeix (els defaults dels Ja::*Params s'inicialitzen al ctor del
|
||||||
|
// struct, així que el comportament és "preset parcial = preset complet
|
||||||
|
// amb defaults per als camps que falten").
|
||||||
|
template <typename T>
|
||||||
|
void readField(const fkyaml::node& node, const char* key, T& dst) {
|
||||||
|
if (node.contains(key)) { dst = node[key].get_value<T>(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
auto SoundEffectsConfig::get() -> SoundEffectsConfig& {
|
||||||
|
static SoundEffectsConfig instance_;
|
||||||
|
return instance_;
|
||||||
|
}
|
||||||
|
|
||||||
|
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";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const auto* begin = reinterpret_cast<const char*>(bytes.data());
|
||||||
|
const auto* end = begin + bytes.size();
|
||||||
|
auto yaml = fkyaml::node::deserialize(begin, end);
|
||||||
|
|
||||||
|
if (yaml.contains("echo") && yaml["echo"].is_mapping()) {
|
||||||
|
for (auto it = yaml["echo"].begin(); it != yaml["echo"].end(); ++it) {
|
||||||
|
const auto NAME = it.key().get_value<std::string>();
|
||||||
|
const auto& node = it.value();
|
||||||
|
Ja::EchoParams params{};
|
||||||
|
readField(node, "delay_ms", params.delay_ms);
|
||||||
|
readField(node, "feedback", params.feedback);
|
||||||
|
readField(node, "wet", params.wet);
|
||||||
|
echoes_[NAME] = params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yaml.contains("reverb") && yaml["reverb"].is_mapping()) {
|
||||||
|
for (auto it = yaml["reverb"].begin(); it != yaml["reverb"].end(); ++it) {
|
||||||
|
const auto NAME = it.key().get_value<std::string>();
|
||||||
|
const auto& node = it.value();
|
||||||
|
Ja::ReverbParams params{};
|
||||||
|
readField(node, "room_size", params.room_size);
|
||||||
|
readField(node, "damping", params.damping);
|
||||||
|
readField(node, "wet", params.wet);
|
||||||
|
reverbs_[NAME] = params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "SoundEffectsConfig: " << echoes_.size() << " preset(s) d'eco i " << reverbs_.size() << " de reverb des de " << file_path << '\n';
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "SoundEffectsConfig: error parsejant " << file_path << ": " << e.what() << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto SoundEffectsConfig::findEcho(const std::string& name) const -> const Ja::EchoParams* {
|
||||||
|
const auto IT = echoes_.find(name);
|
||||||
|
return (IT == echoes_.end()) ? nullptr : &IT->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto SoundEffectsConfig::findReverb(const std::string& name) const -> const Ja::ReverbParams* {
|
||||||
|
const auto IT = reverbs_.find(name);
|
||||||
|
return (IT == reverbs_.end()) ? nullptr : &IT->second;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#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
|
||||||
|
// Audio (playSoundWithEcho/playSoundWithReverb) hi accedeix per nom: si el
|
||||||
|
// preset no existeix, el so es reprodueix sec amb un avís a stderr.
|
||||||
|
//
|
||||||
|
// Patró Meyers idèntic a UiConfig/Locale: un sol load() a l'arrencada, sense
|
||||||
|
// hot-reload. Si el fitxer no existeix, el catàleg queda buit (cap preset
|
||||||
|
// disponible) i tots els playSoundWith* es comporten com playSound dry.
|
||||||
|
class SoundEffectsConfig {
|
||||||
|
public:
|
||||||
|
static auto get() -> SoundEffectsConfig&;
|
||||||
|
|
||||||
|
SoundEffectsConfig(const SoundEffectsConfig&) = delete;
|
||||||
|
SoundEffectsConfig(SoundEffectsConfig&&) = delete;
|
||||||
|
auto operator=(const SoundEffectsConfig&) -> SoundEffectsConfig& = delete;
|
||||||
|
auto operator=(SoundEffectsConfig&&) -> SoundEffectsConfig& = delete;
|
||||||
|
|
||||||
|
void load(const std::string& file_path);
|
||||||
|
|
||||||
|
// Retorna nullptr si el preset no existeix.
|
||||||
|
[[nodiscard]] auto findEcho(const std::string& name) const -> const Ja::EchoParams*;
|
||||||
|
[[nodiscard]] auto findReverb(const std::string& name) const -> const Ja::ReverbParams*;
|
||||||
|
|
||||||
|
private:
|
||||||
|
SoundEffectsConfig() = default;
|
||||||
|
~SoundEffectsConfig() = default;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, Ja::EchoParams> echoes_;
|
||||||
|
std::unordered_map<std::string, Ja::ReverbParams> reverbs_;
|
||||||
|
};
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
#include "core/input/define_buttons.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "core/locale/locale.hpp"
|
||||||
|
#include "core/rendering/screen.hpp"
|
||||||
|
#include "core/rendering/text.hpp"
|
||||||
|
#include "core/ui/ui_config.hpp"
|
||||||
|
#include "utils/defines.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Threshold per als triggers L2/R2 — equivalent a CC (16384 ≈ 50%).
|
||||||
|
constexpr Sint16 TRIGGER_THRESHOLD = 16384;
|
||||||
|
|
||||||
|
// Geometria de l'overlay (canvas lògic 320×240).
|
||||||
|
constexpr int BOX_WIDTH = 240;
|
||||||
|
constexpr int PAD_Y = 8;
|
||||||
|
constexpr int LINE_GAP = 4;
|
||||||
|
constexpr Uint8 BG_ALPHA = 230;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void DefineButtons::init(std::shared_ptr<Text> title_font,
|
||||||
|
std::shared_ptr<Text> items_font,
|
||||||
|
const Palette& ui_palette) {
|
||||||
|
title_font_ = std::move(title_font);
|
||||||
|
items_font_ = std::move(items_font);
|
||||||
|
ui_palette_ = ui_palette;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto DefineButtons::enable(SDL_Gamepad* pad, std::string name, std::string path) -> bool {
|
||||||
|
if (pad == nullptr) { return false; }
|
||||||
|
pad_ = pad;
|
||||||
|
pad_name_ = std::move(name);
|
||||||
|
pad_path_ = std::move(path);
|
||||||
|
enabled_ = true;
|
||||||
|
finished_ = false;
|
||||||
|
closing_ = false;
|
||||||
|
message_shown_ = false;
|
||||||
|
message_timer_ms_ = 0;
|
||||||
|
l2_was_pressed_ = false;
|
||||||
|
r2_was_pressed_ = false;
|
||||||
|
resetSequence();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefineButtons::disable() {
|
||||||
|
enabled_ = false;
|
||||||
|
finished_ = false;
|
||||||
|
closing_ = false;
|
||||||
|
message_shown_ = false;
|
||||||
|
message_timer_ms_ = 0;
|
||||||
|
l2_was_pressed_ = false;
|
||||||
|
r2_was_pressed_ = false;
|
||||||
|
pad_ = nullptr;
|
||||||
|
pad_name_.clear();
|
||||||
|
pad_path_.clear();
|
||||||
|
sequence_.clear();
|
||||||
|
index_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefineButtons::resetSequence() {
|
||||||
|
// Ordre: ATTACK, JUMP, SERVICE — replica l'estructura de
|
||||||
|
// `clearButtons()` de coffee_crisis_arcade_edition (seqüència ordenada
|
||||||
|
// d'accions amb la SERVICE al final) adaptada al joc actual. La label
|
||||||
|
// de SERVICE va a una clau pròpia (`menu.items.service`) per no
|
||||||
|
// reutilitzar la del submenú dels mandos.
|
||||||
|
sequence_.clear();
|
||||||
|
sequence_.push_back({"menu.items.attack", GamepadBindings::Action::ATTACK, static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID)});
|
||||||
|
sequence_.push_back({"menu.items.jump", GamepadBindings::Action::JUMP, static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID)});
|
||||||
|
sequence_.push_back({"menu.items.service", GamepadBindings::Action::SERVICE, static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID)});
|
||||||
|
index_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto DefineButtons::isInUse(int button) const -> bool {
|
||||||
|
return std::ranges::any_of(sequence_, [button](const ButtonAssignment& a) {
|
||||||
|
return a.button == button;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto DefineButtons::isReadyToClose() const -> bool {
|
||||||
|
// Llest per tancar quan: la seqüència ha acabat, ja s'ha mostrat el
|
||||||
|
// missatge, està en mode tancament, i el timer ha expirat. Replica el
|
||||||
|
// patró de `coffee_crisis_arcade_edition::DefineButtons::isReadyToClose`,
|
||||||
|
// simplificat perquè ací no tenim animació de WindowMessage.
|
||||||
|
return finished_ && message_shown_ && closing_ && message_timer_ms_ <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefineButtons::update(int delta_ms) {
|
||||||
|
if (!enabled_) { return; }
|
||||||
|
if (finished_ && message_shown_) {
|
||||||
|
message_timer_ms_ -= delta_ms;
|
||||||
|
if (message_timer_ms_ <= 0 && !closing_) {
|
||||||
|
closing_ = true;
|
||||||
|
message_timer_ms_ = 0; // anchor a 0 per a `isReadyToClose`.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefineButtons::handleEvent(const SDL_Event& ev) {
|
||||||
|
if (!enabled_) { return; }
|
||||||
|
switch (ev.type) {
|
||||||
|
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
||||||
|
handleButtonDown(ev.gbutton);
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_GAMEPAD_BUTTON_UP:
|
||||||
|
onButtonUp();
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
||||||
|
handleAxisMotion(ev.gaxis);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefineButtons::assignAndAdvance(int button) {
|
||||||
|
if (finished_ || isInUse(button)) { return; }
|
||||||
|
sequence_.at(index_).button = button;
|
||||||
|
if (index_ + 1 < sequence_.size()) {
|
||||||
|
++index_;
|
||||||
|
} else {
|
||||||
|
finished_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefineButtons::processTrigger(int virtual_button, bool& was_pressed, bool now) {
|
||||||
|
if (now && !was_pressed) { assignAndAdvance(virtual_button); }
|
||||||
|
if (!now && was_pressed) { onButtonUp(); }
|
||||||
|
was_pressed = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefineButtons::handleButtonDown(const SDL_GamepadButtonEvent& ev) {
|
||||||
|
// Filtra: només acceptem events del mando que estem configurant.
|
||||||
|
// (Per l'instance_id no podem comparar directament; obtenim el handle
|
||||||
|
// i el comparem.)
|
||||||
|
const SDL_Gamepad* source = SDL_GetGamepadFromID(ev.which);
|
||||||
|
if (source != pad_) { return; }
|
||||||
|
assignAndAdvance(static_cast<int>(ev.button));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefineButtons::handleAxisMotion(const SDL_GamepadAxisEvent& ev) {
|
||||||
|
const SDL_Gamepad* source = SDL_GetGamepadFromID(ev.which);
|
||||||
|
if (source != pad_) { return; }
|
||||||
|
|
||||||
|
// Triggers L2/R2 com a "botons" amb edge-detect (transició inactiu →
|
||||||
|
// actiu). Replica `doControllerAxisMotion` de
|
||||||
|
// coffee_crisis_arcade_edition.
|
||||||
|
const bool NOW = ev.value > TRIGGER_THRESHOLD;
|
||||||
|
if (ev.axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER) {
|
||||||
|
processTrigger(GamepadBindings::TRIGGER_L2_AS_BUTTON, l2_was_pressed_, NOW);
|
||||||
|
} else if (ev.axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
|
||||||
|
processTrigger(GamepadBindings::TRIGGER_R2_AS_BUTTON, r2_was_pressed_, NOW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefineButtons::onButtonUp() {
|
||||||
|
if (!finished_ || message_shown_) { return; }
|
||||||
|
persistAndShowComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefineButtons::persistAndShowComplete() {
|
||||||
|
std::unordered_map<GamepadBindings::Action, int> mapping;
|
||||||
|
for (const auto& a : sequence_) {
|
||||||
|
mapping[a.action] = a.button;
|
||||||
|
}
|
||||||
|
GamepadBindings::get().setBindings(pad_name_, pad_path_, mapping);
|
||||||
|
message_shown_ = true;
|
||||||
|
message_timer_ms_ = MESSAGE_DISPLAY_DURATION_MS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefineButtons::render() const {
|
||||||
|
if (!enabled_ || !title_font_ || !items_font_) { return; }
|
||||||
|
|
||||||
|
const int TITLE_H = title_font_->getLineHeight();
|
||||||
|
const int ITEM_H = items_font_->getLineHeight();
|
||||||
|
const int TITLE_KERNING = title_font_->getDefaultKerning();
|
||||||
|
const int ITEM_KERNING = items_font_->getDefaultKerning();
|
||||||
|
|
||||||
|
// Calcular l'alçada total: títol + 2 línies (instrucció + label).
|
||||||
|
int box_h = (PAD_Y * 2) + TITLE_H + LINE_GAP + (ITEM_H * 2) + LINE_GAP;
|
||||||
|
int box_x = (GameCanvas::WIDTH - BOX_WIDTH) / 2;
|
||||||
|
int box_y = (GameCanvas::HEIGHT - box_h) / 2;
|
||||||
|
|
||||||
|
const auto BG_ARGB = ui_palette_[UiConfig::get().menuBg()];
|
||||||
|
const auto BORDER_ARGB = ui_palette_[UiConfig::get().menuBorder()];
|
||||||
|
Screen::get().blendRect(box_x, box_y, BOX_WIDTH, box_h, BG_ARGB, BG_ALPHA);
|
||||||
|
Screen::get().fillRect(box_x, box_y, BOX_WIDTH, 1, BORDER_ARGB);
|
||||||
|
Screen::get().fillRect(box_x, box_y + box_h - 1, BOX_WIDTH, 1, BORDER_ARGB);
|
||||||
|
Screen::get().fillRect(box_x, box_y, 1, box_h, BORDER_ARGB);
|
||||||
|
Screen::get().fillRect(box_x + BOX_WIDTH - 1, box_y, 1, box_h, BORDER_ARGB);
|
||||||
|
|
||||||
|
int cursor_y = box_y + PAD_Y;
|
||||||
|
const int CENTER_X = box_x + (BOX_WIDTH / 2);
|
||||||
|
|
||||||
|
// Títol: nom del mando.
|
||||||
|
const std::string TITLE = pad_name_;
|
||||||
|
title_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
|
||||||
|
CENTER_X,
|
||||||
|
cursor_y,
|
||||||
|
TITLE,
|
||||||
|
TITLE_KERNING,
|
||||||
|
UiConfig::get().menuTitle());
|
||||||
|
cursor_y += TITLE_H + LINE_GAP;
|
||||||
|
|
||||||
|
if (message_shown_) {
|
||||||
|
// Pantalla final: missatge de configuració completa.
|
||||||
|
items_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
|
||||||
|
CENTER_X,
|
||||||
|
cursor_y,
|
||||||
|
Locale::get().text("menu.values.define_buttons_complete"),
|
||||||
|
ITEM_KERNING,
|
||||||
|
UiConfig::get().menuValue());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instrucció + acció pendent. Replica visualment el WindowMessage de
|
||||||
|
// coffee_crisis_arcade_edition.
|
||||||
|
items_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
|
||||||
|
CENTER_X,
|
||||||
|
cursor_y,
|
||||||
|
Locale::get().text("menu.values.press_button"),
|
||||||
|
ITEM_KERNING,
|
||||||
|
UiConfig::get().menuLabel());
|
||||||
|
cursor_y += ITEM_H + LINE_GAP;
|
||||||
|
|
||||||
|
if (index_ < sequence_.size()) {
|
||||||
|
const auto& current = sequence_[index_];
|
||||||
|
items_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
|
||||||
|
CENTER_X,
|
||||||
|
cursor_y,
|
||||||
|
Locale::get().text(current.label_key),
|
||||||
|
ITEM_KERNING,
|
||||||
|
UiConfig::get().menuCursor());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "core/input/gamepad_bindings.hpp"
|
||||||
|
#include "core/rendering/surface.hpp" // Palette
|
||||||
|
|
||||||
|
class Text;
|
||||||
|
|
||||||
|
// Sub-mòdul de captura seqüencial de botons per a un mando concret.
|
||||||
|
// Replica `DefineButtons` de coffee_crisis_arcade_edition: l'usuari obre el
|
||||||
|
// menú de servei, escull "Definir botons P1" / "P2", i el sub-mòdul li
|
||||||
|
// demana un per un els botons per a cada acció (JUMP → ATTACK → SERVICE).
|
||||||
|
//
|
||||||
|
// Cicle de vida:
|
||||||
|
// 1. `enable(pad, name, path)`: arrenca la seqüència. `index_` = 0.
|
||||||
|
// 2. Cada SDL_EVENT_GAMEPAD_BUTTON_DOWN (o transició de L2/R2 via
|
||||||
|
// AXIS_MOTION) que NO duplique cap botó ja assignat dins la sessió
|
||||||
|
// avança `index_`. Si `index_` arriba a la mida de la seqüència,
|
||||||
|
// `finished_` es posa a true i, al pròxim BUTTON_UP, escrivim el mapeig
|
||||||
|
// complet a `GamepadBindings`, mostrem "Configuració completa" durant
|
||||||
|
// `MESSAGE_DISPLAY_DURATION_MS`, i posem `closing_`. Quan tot l'overlay
|
||||||
|
// ja no és visible, `isReadyToClose()` torna `true` i el caller ens
|
||||||
|
// fa `disable()`.
|
||||||
|
//
|
||||||
|
// El layer runtime de l'aplicació viu a `GamepadBindings`: aquesta classe
|
||||||
|
// només decideix què s'escriu i quan.
|
||||||
|
class DefineButtons {
|
||||||
|
public:
|
||||||
|
DefineButtons() = default;
|
||||||
|
~DefineButtons() = default;
|
||||||
|
DefineButtons(const DefineButtons&) = delete;
|
||||||
|
DefineButtons(DefineButtons&&) = delete;
|
||||||
|
auto operator=(const DefineButtons&) -> DefineButtons& = delete;
|
||||||
|
auto operator=(DefineButtons&&) -> DefineButtons& = delete;
|
||||||
|
|
||||||
|
// Injecta les fonts i la paleta UI; cridat des de `ServiceMenu::init`
|
||||||
|
// perquè comparteixin tipografia i colors de tema.
|
||||||
|
void init(std::shared_ptr<Text> title_font,
|
||||||
|
std::shared_ptr<Text> items_font,
|
||||||
|
const Palette& ui_palette);
|
||||||
|
|
||||||
|
// Arranca la sessió per al mando indicat. `pad` ha de ser un handle
|
||||||
|
// viu obtingut de `GamepadManager`. Retorna false si `pad` és nullptr.
|
||||||
|
auto enable(SDL_Gamepad* pad, std::string name, std::string path) -> bool;
|
||||||
|
|
||||||
|
// Atura la sessió (sense persistir) i neteja l'estat.
|
||||||
|
void disable();
|
||||||
|
|
||||||
|
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; }
|
||||||
|
[[nodiscard]] auto isReadyToClose() const -> bool;
|
||||||
|
|
||||||
|
// Avança el timer de tancament (ms) — replica el `update` de
|
||||||
|
// coffee_crisis_arcade_edition. No fa res si no està actiu.
|
||||||
|
void update(int delta_ms);
|
||||||
|
|
||||||
|
// Pinta l'overlay sobre el canvas actual (centrat). No fa res si no
|
||||||
|
// està actiu.
|
||||||
|
void render() const;
|
||||||
|
|
||||||
|
// Punt d'entrada d'events: BUTTON_DOWN / BUTTON_UP / AXIS_MOTION del
|
||||||
|
// mando objectiu. La resta s'ignoren.
|
||||||
|
void handleEvent(const SDL_Event& ev);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr int MESSAGE_DISPLAY_DURATION_MS = 2000;
|
||||||
|
|
||||||
|
struct ButtonAssignment {
|
||||||
|
std::string label_key;
|
||||||
|
GamepadBindings::Action action;
|
||||||
|
int button; // SDL_GamepadButton o trigger virtual; INVALID = sense assignar.
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reinicialitza la seqüència ordenada (JUMP → ATTACK → SERVICE) amb
|
||||||
|
// tots els botons sense assignar. Cridat a `enable`.
|
||||||
|
void resetSequence();
|
||||||
|
|
||||||
|
// Comprova si un botó ja ha estat assignat dins de la sessió actual.
|
||||||
|
// Replica `checkButtonNotInUse` / `checkTriggerNotInUse`.
|
||||||
|
[[nodiscard]] auto isInUse(int button) const -> bool;
|
||||||
|
|
||||||
|
void handleButtonDown(const SDL_GamepadButtonEvent& ev);
|
||||||
|
void handleAxisMotion(const SDL_GamepadAxisEvent& ev);
|
||||||
|
|
||||||
|
// Helper de la lògica comuna a button-down i als triggers L2/R2:
|
||||||
|
// assigna `button` a l'acció pendent i avança l'índex (o marca
|
||||||
|
// finished_ si era l'última). No fa res si `button` ja està en ús.
|
||||||
|
void assignAndAdvance(int button);
|
||||||
|
|
||||||
|
// Tracta una transició de trigger (L2 o R2) com a botó virtual amb
|
||||||
|
// edge-detect. `virtual_button` és `TRIGGER_L?_AS_BUTTON`,
|
||||||
|
// `was_pressed` és l'estat anterior (mutat per referència).
|
||||||
|
void processTrigger(int virtual_button, bool& was_pressed, bool now);
|
||||||
|
|
||||||
|
// Crida `onSequenceEnd()` quan l'usuari deixa anar el botó després
|
||||||
|
// d'haver completat la seqüència — replica la branca de
|
||||||
|
// `SDL_EVENT_GAMEPAD_BUTTON_UP` a `DefineButtons::handleEvents`.
|
||||||
|
void onButtonUp();
|
||||||
|
|
||||||
|
// Persistix les assignacions a `GamepadBindings` i posa `message_shown_`.
|
||||||
|
void persistAndShowComplete();
|
||||||
|
|
||||||
|
SDL_Gamepad* pad_{nullptr};
|
||||||
|
std::string pad_name_;
|
||||||
|
std::string pad_path_;
|
||||||
|
|
||||||
|
std::shared_ptr<Text> title_font_;
|
||||||
|
std::shared_ptr<Text> items_font_;
|
||||||
|
Palette ui_palette_{};
|
||||||
|
|
||||||
|
std::vector<ButtonAssignment> sequence_;
|
||||||
|
std::size_t index_{0};
|
||||||
|
int message_timer_ms_{0};
|
||||||
|
|
||||||
|
bool enabled_{false};
|
||||||
|
bool finished_{false};
|
||||||
|
bool closing_{false};
|
||||||
|
bool message_shown_{false};
|
||||||
|
bool l2_was_pressed_{false};
|
||||||
|
bool r2_was_pressed_{false};
|
||||||
|
};
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
#include "core/input/gamepad_bindings.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string_view>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "external/fkyaml_node.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Threshold per a considerar L2/R2 com a premuts (mateix valor que
|
||||||
|
// coffee_crisis_arcade_edition: 16384 ≈ 50% de ±32767).
|
||||||
|
constexpr Sint16 TRIGGER_THRESHOLD = 16384;
|
||||||
|
|
||||||
|
// Taules de mapejat string ↔ valor: replica BUTTON_TO_STRING /
|
||||||
|
// STRING_TO_BUTTON / ACTION_TO_STRING / STRING_TO_ACTION del projecte
|
||||||
|
// de referència. Els noms en lowercase per llegibilitat al YAML.
|
||||||
|
using ActionPair = std::pair<GamepadBindings::Action, std::string_view>;
|
||||||
|
constexpr std::array ACTION_NAMES = {
|
||||||
|
ActionPair{GamepadBindings::Action::JUMP, std::string_view{"jump"}},
|
||||||
|
ActionPair{GamepadBindings::Action::ATTACK, std::string_view{"attack"}},
|
||||||
|
ActionPair{GamepadBindings::Action::SERVICE, std::string_view{"service"}},
|
||||||
|
};
|
||||||
|
|
||||||
|
using ButtonPair = std::pair<int, std::string_view>;
|
||||||
|
constexpr std::array BUTTON_NAMES = {
|
||||||
|
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_SOUTH), std::string_view{"south"}},
|
||||||
|
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_EAST), std::string_view{"east"}},
|
||||||
|
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_WEST), std::string_view{"west"}},
|
||||||
|
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_NORTH), std::string_view{"north"}},
|
||||||
|
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_BACK), std::string_view{"back"}},
|
||||||
|
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_GUIDE), std::string_view{"guide"}},
|
||||||
|
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_START), std::string_view{"start"}},
|
||||||
|
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_LEFT_STICK), std::string_view{"left_stick"}},
|
||||||
|
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_RIGHT_STICK), std::string_view{"right_stick"}},
|
||||||
|
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), std::string_view{"l1"}},
|
||||||
|
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), std::string_view{"r1"}},
|
||||||
|
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_DPAD_UP), std::string_view{"dpad_up"}},
|
||||||
|
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_DPAD_DOWN), std::string_view{"dpad_down"}},
|
||||||
|
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_DPAD_LEFT), std::string_view{"dpad_left"}},
|
||||||
|
ButtonPair{static_cast<int>(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), std::string_view{"dpad_right"}},
|
||||||
|
ButtonPair{GamepadBindings::TRIGGER_L2_AS_BUTTON, std::string_view{"l2"}},
|
||||||
|
ButtonPair{GamepadBindings::TRIGGER_R2_AS_BUTTON, std::string_view{"r2"}},
|
||||||
|
};
|
||||||
|
|
||||||
|
auto actionToString(GamepadBindings::Action a) -> std::string_view {
|
||||||
|
for (const auto& [act, name] : ACTION_NAMES) {
|
||||||
|
if (act == a) { return name; }
|
||||||
|
}
|
||||||
|
return ACTION_NAMES.front().second;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto stringToAction(std::string_view s, GamepadBindings::Action& out) -> bool {
|
||||||
|
for (const auto& [act, name] : ACTION_NAMES) {
|
||||||
|
if (name == s) {
|
||||||
|
out = act;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto buttonToString(int btn) -> std::string_view {
|
||||||
|
for (const auto& [val, name] : BUTTON_NAMES) {
|
||||||
|
if (val == btn) { return name; }
|
||||||
|
}
|
||||||
|
return std::string_view{""};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto stringToButton(std::string_view s, int& out) -> bool {
|
||||||
|
for (const auto& [val, name] : BUTTON_NAMES) {
|
||||||
|
if (name == s) {
|
||||||
|
out = val;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
auto GamepadBindings::get() -> GamepadBindings& {
|
||||||
|
static GamepadBindings instance_;
|
||||||
|
return instance_;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto GamepadBindings::defaultButton(Action action) -> int {
|
||||||
|
// Defaults històrics de l'aee_2026 (vegeu Player::accumulateGamepadMovement
|
||||||
|
// i les consultes del Director per al menú).
|
||||||
|
switch (action) {
|
||||||
|
case Action::JUMP:
|
||||||
|
return static_cast<int>(SDL_GAMEPAD_BUTTON_SOUTH);
|
||||||
|
case Action::ATTACK:
|
||||||
|
return static_cast<int>(SDL_GAMEPAD_BUTTON_WEST);
|
||||||
|
case Action::SERVICE:
|
||||||
|
return static_cast<int>(SDL_GAMEPAD_BUTTON_BACK);
|
||||||
|
}
|
||||||
|
return static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto GamepadBindings::findConfig(const std::string& name, const std::string& path) const -> int {
|
||||||
|
if (!path.empty()) {
|
||||||
|
for (std::size_t i = 0; i < configs_.size(); ++i) {
|
||||||
|
if (configs_[i].path == path) { return static_cast<int>(i); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!name.empty()) {
|
||||||
|
for (std::size_t i = 0; i < configs_.size(); ++i) {
|
||||||
|
if (configs_[i].name == name) { return static_cast<int>(i); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto GamepadBindings::getButton(const std::string& name, const std::string& path, Action action) const -> int {
|
||||||
|
const int IDX = findConfig(name, path);
|
||||||
|
if (IDX < 0) { return defaultButton(action); }
|
||||||
|
const auto& m = configs_[static_cast<std::size_t>(IDX)].bindings;
|
||||||
|
auto it = m.find(action);
|
||||||
|
return (it != m.end()) ? it->second : defaultButton(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GamepadBindings::setBindings(const std::string& name,
|
||||||
|
const std::string& path,
|
||||||
|
const std::unordered_map<Action, int>& bindings) {
|
||||||
|
int idx = findConfig(name, path);
|
||||||
|
if (idx < 0) {
|
||||||
|
configs_.push_back(Config{.name = name, .path = path, .bindings = bindings});
|
||||||
|
} else {
|
||||||
|
auto& cfg = configs_[static_cast<std::size_t>(idx)];
|
||||||
|
cfg.name = name;
|
||||||
|
cfg.path = path;
|
||||||
|
cfg.bindings = bindings;
|
||||||
|
}
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto GamepadBindings::isPressed(SDL_Gamepad* pad, int button) -> bool {
|
||||||
|
if (pad == nullptr) { return false; }
|
||||||
|
if (button == TRIGGER_L2_AS_BUTTON) {
|
||||||
|
return SDL_GetGamepadAxis(pad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) > TRIGGER_THRESHOLD;
|
||||||
|
}
|
||||||
|
if (button == TRIGGER_R2_AS_BUTTON) {
|
||||||
|
return SDL_GetGamepadAxis(pad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) > TRIGGER_THRESHOLD;
|
||||||
|
}
|
||||||
|
return SDL_GetGamepadButton(pad, static_cast<SDL_GamepadButton>(button));
|
||||||
|
}
|
||||||
|
|
||||||
|
void GamepadBindings::load(const std::string& file_path) {
|
||||||
|
file_path_ = file_path;
|
||||||
|
configs_.clear();
|
||||||
|
|
||||||
|
std::ifstream file(file_path);
|
||||||
|
if (!file) { return; }
|
||||||
|
std::stringstream buf;
|
||||||
|
buf << file.rdbuf();
|
||||||
|
const std::string CONTENT = buf.str();
|
||||||
|
if (CONTENT.empty()) { return; }
|
||||||
|
|
||||||
|
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';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const auto& entry : yaml["gamepads"]) {
|
||||||
|
Config cfg;
|
||||||
|
if (entry.contains("name")) { cfg.name = entry["name"].get_value<std::string>(); }
|
||||||
|
if (entry.contains("path")) { cfg.path = entry["path"].get_value<std::string>(); }
|
||||||
|
if (entry.contains("bindings") && entry["bindings"].is_mapping()) {
|
||||||
|
for (const auto& kv : entry["bindings"].as_map()) {
|
||||||
|
const auto KEY = kv.first.get_value<std::string>();
|
||||||
|
const auto VAL = kv.second.get_value<std::string>();
|
||||||
|
Action action{};
|
||||||
|
int btn = 0;
|
||||||
|
if (stringToAction(KEY, action) && stringToButton(VAL, btn)) {
|
||||||
|
cfg.bindings[action] = btn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
configs_.push_back(std::move(cfg));
|
||||||
|
}
|
||||||
|
std::cout << "GamepadBindings: " << configs_.size() << " mandos carregats de "
|
||||||
|
<< file_path << '\n';
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "GamepadBindings: error parsejant " << file_path << ": "
|
||||||
|
<< e.what() << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto GamepadBindings::save() -> bool {
|
||||||
|
if (file_path_.empty()) { return false; }
|
||||||
|
|
||||||
|
std::ostringstream out;
|
||||||
|
out << "# AEE 2026 - 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_) {
|
||||||
|
out << " - name: \"" << cfg.name << "\"\n";
|
||||||
|
out << " path: \"" << cfg.path << "\"\n";
|
||||||
|
out << " bindings:\n";
|
||||||
|
for (const auto& [action, button] : cfg.bindings) {
|
||||||
|
out << " " << actionToString(action) << ": \""
|
||||||
|
<< buttonToString(button) << "\"\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream file(file_path_, std::ios::binary);
|
||||||
|
if (!file) {
|
||||||
|
std::cerr << "GamepadBindings: no es pot escriure " << file_path_ << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
file << out.str();
|
||||||
|
return file.good();
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// Vinculacions remapeables d'accions del joc a botons d'un mando concret,
|
||||||
|
// indexades per `(name, path)` del mando — replica el patró
|
||||||
|
// `GamepadConfig` + `GamepadConfigManager` de coffee_crisis_arcade_edition.
|
||||||
|
//
|
||||||
|
// Persisteix en un YAML aïllat al system folder (no a `config.yaml`) perquè
|
||||||
|
// és per dispositiu, no per usuari/perfil: dos mandos diferents poden
|
||||||
|
// tenir mappings diferents i cap d'ells va lligat a un slot de jugador.
|
||||||
|
//
|
||||||
|
// El layer runtime (consultat per Player/Director cada frame) és
|
||||||
|
// directament aquesta classe; no hi ha caché a `GamepadManager::Entry`. El
|
||||||
|
// nombre d'entries persistides és típicament <10, i les accions són 3
|
||||||
|
// (JUMP/ATTACK/SERVICE), així que les consultes són trivialment ràpides.
|
||||||
|
class GamepadBindings {
|
||||||
|
public:
|
||||||
|
enum class Action : std::uint8_t {
|
||||||
|
JUMP,
|
||||||
|
ATTACK,
|
||||||
|
SERVICE,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Triggers L2/R2 representats com a "botons virtuals" — replica la
|
||||||
|
// convenció de coffee_crisis_arcade_edition (`TRIGGER_L2_AS_BUTTON`,
|
||||||
|
// `TRIGGER_R2_AS_BUTTON`). El threshold viu a `isPressed`.
|
||||||
|
static constexpr int TRIGGER_L2_AS_BUTTON = 100;
|
||||||
|
static constexpr int TRIGGER_R2_AS_BUTTON = 101;
|
||||||
|
|
||||||
|
static auto get() -> GamepadBindings&;
|
||||||
|
|
||||||
|
GamepadBindings(const GamepadBindings&) = delete;
|
||||||
|
GamepadBindings(GamepadBindings&&) = delete;
|
||||||
|
auto operator=(const GamepadBindings&) -> GamepadBindings& = delete;
|
||||||
|
auto operator=(GamepadBindings&&) -> GamepadBindings& = delete;
|
||||||
|
|
||||||
|
// Fixa la ruta del fitxer persistent + el carrega. Si no existix
|
||||||
|
// encara, l'estat queda buit (les consultes retornaran defaults) i
|
||||||
|
// `save()` el crearà al primer `setBindings`.
|
||||||
|
void load(const std::string& file_path);
|
||||||
|
|
||||||
|
// Persistix l'estat actual (totes les Config conegudes). Cridat
|
||||||
|
// automàticament dins de `setBindings` — no cal cridar-la a mà.
|
||||||
|
auto save() -> bool;
|
||||||
|
|
||||||
|
// Retorna el botó (o trigger virtual 100/101) lligat a `action` per al
|
||||||
|
// mando identificat per (name, path). Si no hi ha entrada per aquest
|
||||||
|
// mando, retorna el default global. La cerca és primer per `path`
|
||||||
|
// (re-vincular el mateix mando al mateix port és prioritari) i, si no
|
||||||
|
// troba, fallback per `name` (mando connectat a un altre port USB).
|
||||||
|
[[nodiscard]] auto getButton(const std::string& name, const std::string& path, Action action) const -> int;
|
||||||
|
|
||||||
|
// Reescriu totes les vinculacions del mando (name, path). Si no hi
|
||||||
|
// havia config per aquest dispositiu, en crea una nova. Persisteix a
|
||||||
|
// disc immediatament. Replicació de `Input::saveGamepadConfigFromGamepad`.
|
||||||
|
void setBindings(const std::string& name,
|
||||||
|
const std::string& path,
|
||||||
|
const std::unordered_map<Action, int>& bindings);
|
||||||
|
|
||||||
|
// Wrapper de polling: torna `true` si el "botó" (o trigger virtual)
|
||||||
|
// està premut ara mateix al mando `pad`. Encapsula el threshold per
|
||||||
|
// a L2/R2 (~50% del rang).
|
||||||
|
[[nodiscard]] static auto isPressed(SDL_Gamepad* pad, int button) -> bool;
|
||||||
|
|
||||||
|
// Default de fàbrica per a una acció — usat tant pel fallback de
|
||||||
|
// `getButton` com per inicialitzar la seqüència del DefineButtons.
|
||||||
|
[[nodiscard]] static auto defaultButton(Action action) -> int;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Config {
|
||||||
|
std::string name;
|
||||||
|
std::string path;
|
||||||
|
std::unordered_map<Action, int> bindings;
|
||||||
|
};
|
||||||
|
|
||||||
|
GamepadBindings() = default;
|
||||||
|
~GamepadBindings() = default;
|
||||||
|
|
||||||
|
// Localitza la Config que coincideix amb (name, path); retorna -1 si
|
||||||
|
// no la troba. Match prioritari per path, fallback per nom.
|
||||||
|
[[nodiscard]] auto findConfig(const std::string& name, const std::string& path) const -> int;
|
||||||
|
|
||||||
|
std::vector<Config> configs_;
|
||||||
|
std::string file_path_;
|
||||||
|
};
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <iterator>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@@ -78,6 +79,17 @@ auto GamepadManager::uniqueName(const std::string& base) const -> std::string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GamepadManager::addGamepad(SDL_JoystickID jid, bool notify) {
|
void GamepadManager::addGamepad(SDL_JoystickID jid, bool notify) {
|
||||||
|
// Dedup per `instance_id`: SDL encua un SDL_EVENT_GAMEPAD_ADDED per
|
||||||
|
// cada mando ja connectat quan s'inicialitza el subsistema, a més
|
||||||
|
// dels que ja hem obert a `init()`. Sense aquesta guarda, el mateix
|
||||||
|
// mando s'afegiria dues vegades (silenciós + notificat amb sufix
|
||||||
|
// "#2") i qualsevol referència via `getPrimary`/`entries_` seria
|
||||||
|
// ambigua.
|
||||||
|
const bool ALREADY_KNOWN = std::ranges::any_of(
|
||||||
|
entries_,
|
||||||
|
[jid](const Entry& e) { return e.instance_id == jid; });
|
||||||
|
if (ALREADY_KNOWN) { return; }
|
||||||
|
|
||||||
installWebStandardMapping(jid);
|
installWebStandardMapping(jid);
|
||||||
if (!SDL_IsGamepad(jid)) { return; }
|
if (!SDL_IsGamepad(jid)) { return; }
|
||||||
SDL_Gamepad* pad = SDL_OpenGamepad(jid);
|
SDL_Gamepad* pad = SDL_OpenGamepad(jid);
|
||||||
@@ -87,8 +99,23 @@ void GamepadManager::addGamepad(SDL_JoystickID jid, bool notify) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::string name = uniqueName(prettyName(SDL_GetGamepadName(pad)));
|
std::string name = uniqueName(prettyName(SDL_GetGamepadName(pad)));
|
||||||
std::cout << "GamepadManager: connectat " << name << '\n';
|
// `SDL_GetGamepadPath` pot tornar null a backends que no exposen ruta
|
||||||
entries_.push_back(Entry{.pad = pad, .instance_id = jid, .pretty_name = name});
|
// física (Emscripten, alguns drivers). Buit com a fallback; la
|
||||||
|
// 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';
|
||||||
|
entries_.push_back(Entry{.pad = pad, .instance_id = jid, .pretty_name = name, .path = path});
|
||||||
|
|
||||||
|
// Hot-plug: si alguna assignació pendent encaixa amb aquest mando,
|
||||||
|
// la vinculem ara. Primer intentem per path exacte, després per nom.
|
||||||
|
for (auto& a : assignments_) {
|
||||||
|
if (a.pad != nullptr) { continue; }
|
||||||
|
if (a.name.empty() && a.path.empty()) { continue; }
|
||||||
|
rebindAssignment(a);
|
||||||
|
}
|
||||||
|
|
||||||
if (notify) {
|
if (notify) {
|
||||||
Notification::notify(name + ' ' + Locale::get().text("notification.gamepad_connected"));
|
Notification::notify(name + ' ' + Locale::get().text("notification.gamepad_connected"));
|
||||||
}
|
}
|
||||||
@@ -100,9 +127,18 @@ void GamepadManager::removeGamepad(SDL_JoystickID jid) {
|
|||||||
[jid](const Entry& e) { return e.instance_id == jid; });
|
[jid](const Entry& e) { return e.instance_id == jid; });
|
||||||
if (it == entries_.end()) { return; }
|
if (it == entries_.end()) { return; }
|
||||||
std::string name = std::move(it->pretty_name);
|
std::string name = std::move(it->pretty_name);
|
||||||
if (it->pad != nullptr) { SDL_CloseGamepad(it->pad); }
|
SDL_Gamepad* pad = it->pad;
|
||||||
|
if (pad != nullptr) { SDL_CloseGamepad(pad); }
|
||||||
std::cout << "GamepadManager: desconnectat " << name << '\n';
|
std::cout << "GamepadManager: desconnectat " << name << '\n';
|
||||||
entries_.erase(it);
|
entries_.erase(it);
|
||||||
|
|
||||||
|
// Qualsevol assignació que apuntava a aquest mando queda amb `pad`
|
||||||
|
// a null però conservem nom/ruta: si el mando es torna a connectar,
|
||||||
|
// el hot-plug el re-vincularà automàticament.
|
||||||
|
for (auto& a : assignments_) {
|
||||||
|
if (a.pad == pad) { a.pad = nullptr; }
|
||||||
|
}
|
||||||
|
|
||||||
Notification::notify(name + ' ' + Locale::get().text("notification.gamepad_disconnected"));
|
Notification::notify(name + ' ' + Locale::get().text("notification.gamepad_disconnected"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,11 +149,22 @@ void GamepadManager::init() {
|
|||||||
<< SDL_GetError() << '\n';
|
<< SDL_GetError() << '\n';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// `gamecontrollerdb.txt` és opcional. Si no existeix, SDL retorna -1 i
|
// `gamecontrollerdb.txt` és opcional. Viu a l'arrel del projecte
|
||||||
// continuem amb les definicions internes del sistema.
|
// (no a `data/`) perquè SDL_AddGamepadMappingsFromFile només sap
|
||||||
const int ADDED = SDL_AddGamepadMappingsFromFile("data/input/gamecontrollerdb.txt");
|
// llegir del filesystem real, no del pack. CMake el copia al costat
|
||||||
if (ADDED > 0) {
|
// del binari a cada build perquè SDL_GetBasePath el trobi tant en
|
||||||
std::cout << "GamepadManager: " << ADDED << " mappings de gamecontrollerdb.txt\n";
|
// dev com en release. Sempre informem del resultat per facilitar el
|
||||||
|
// diagnòstic de mandos no reconeguts.
|
||||||
|
std::string db_path = "gamecontrollerdb.txt";
|
||||||
|
if (const char* base = SDL_GetBasePath()) {
|
||||||
|
db_path = std::string(base) + "gamecontrollerdb.txt";
|
||||||
|
}
|
||||||
|
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';
|
||||||
|
} else {
|
||||||
|
std::cout << "GamepadManager: " << ADDED << " mappings carregats de " << db_path << '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
@@ -128,6 +175,7 @@ void GamepadManager::init() {
|
|||||||
}
|
}
|
||||||
SDL_free(ids);
|
SDL_free(ids);
|
||||||
}
|
}
|
||||||
|
std::cout << "GamepadManager: " << entries_.size() << " mando(s) detectat(s) a l'inici\n";
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,3 +209,108 @@ auto GamepadManager::getCount() const -> std::size_t {
|
|||||||
auto GamepadManager::getByPlayerIndex(std::size_t idx) const -> SDL_Gamepad* {
|
auto GamepadManager::getByPlayerIndex(std::size_t idx) const -> SDL_Gamepad* {
|
||||||
return idx < entries_.size() ? entries_[idx].pad : nullptr;
|
return idx < entries_.size() ? entries_[idx].pad : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto GamepadManager::getAll() const -> std::vector<Info> {
|
||||||
|
std::vector<Info> result;
|
||||||
|
result.reserve(entries_.size());
|
||||||
|
std::ranges::transform(entries_, std::back_inserter(result), [](const Entry& e) {
|
||||||
|
return Info{.pad = e.pad, .name = e.pretty_name, .path = e.path};
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto GamepadManager::getForPlayer(int player_id) const -> SDL_Gamepad* {
|
||||||
|
if (player_id < 0 || player_id >= MAX_PLAYERS) { return nullptr; }
|
||||||
|
return assignments_[static_cast<std::size_t>(player_id)].pad;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GamepadManager::rebindAssignment(Assignment& a) {
|
||||||
|
// Primera passada: coincidència exacta per path. És el cas estable
|
||||||
|
// (mateix mando, mateix port) i no canvia res de l'assignació.
|
||||||
|
if (!a.path.empty()) {
|
||||||
|
auto it = std::ranges::find_if(
|
||||||
|
entries_,
|
||||||
|
[&](const Entry& e) { return !e.path.empty() && e.path == a.path; });
|
||||||
|
if (it != entries_.end()) {
|
||||||
|
a.pad = it->pad;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Segona passada: coincidència per nom (mando canviat de port USB).
|
||||||
|
// Acceptem la primera entrada que no estigui ja vinculada a una altra
|
||||||
|
// assignació — així dos mandos amb el mateix nom no es pisen.
|
||||||
|
if (!a.name.empty()) {
|
||||||
|
for (const auto& e : entries_) {
|
||||||
|
if (e.pretty_name != a.name) { continue; }
|
||||||
|
const bool TAKEN = std::ranges::any_of(
|
||||||
|
assignments_,
|
||||||
|
[&](const Assignment& other) { return &other != &a && other.pad == e.pad; });
|
||||||
|
if (TAKEN) { continue; }
|
||||||
|
a.pad = e.pad;
|
||||||
|
a.path = e.path; // actualitzem ruta persistida
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.pad = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GamepadManager::assignToPlayer(int player_id, const std::string& name, const std::string& path) {
|
||||||
|
if (player_id < 0 || player_id >= MAX_PLAYERS) { return; }
|
||||||
|
auto& a = assignments_[static_cast<std::size_t>(player_id)];
|
||||||
|
a.name = name;
|
||||||
|
a.path = path;
|
||||||
|
a.pad = nullptr;
|
||||||
|
if (name.empty() && path.empty()) { return; }
|
||||||
|
rebindAssignment(a);
|
||||||
|
// Last-wins: si l'altre jugador tenia el mateix mando vinculat, el
|
||||||
|
// desvinculem (conservant el seu nom/path perquè el menú pugui
|
||||||
|
// mostrar l'estat "pendent" i oferir re-selecció).
|
||||||
|
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";
|
||||||
|
other.pad = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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';
|
||||||
|
} else {
|
||||||
|
std::cout << "GamepadManager: P" << (player_id + 1) << " pendent ("
|
||||||
|
<< (a.name.empty() ? a.path : a.name) << " no connectat)\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GamepadManager::unassignFromPlayer(int player_id) {
|
||||||
|
if (player_id < 0 || player_id >= MAX_PLAYERS) { return; }
|
||||||
|
assignments_[static_cast<std::size_t>(player_id)] = Assignment{};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto GamepadManager::autoAssignFreeSlots() -> std::vector<AutoAssigned> {
|
||||||
|
std::vector<AutoAssigned> assigned;
|
||||||
|
for (int pid = 0; pid < MAX_PLAYERS; ++pid) {
|
||||||
|
auto& slot = assignments_[static_cast<std::size_t>(pid)];
|
||||||
|
if (slot.pad != nullptr) { continue; }
|
||||||
|
// Primer mando que cap altre slot no haja reclamat. Així respectem
|
||||||
|
// qualsevol assignació persistida que ja haja vinculat un pad
|
||||||
|
// concret i no el reassignem a un jugador diferent.
|
||||||
|
for (const auto& e : entries_) {
|
||||||
|
const bool TAKEN = std::ranges::any_of(
|
||||||
|
assignments_,
|
||||||
|
[&](const Assignment& other) { return other.pad == e.pad; });
|
||||||
|
if (TAKEN) { continue; }
|
||||||
|
slot.pad = e.pad;
|
||||||
|
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';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return assigned;
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -41,22 +42,83 @@ class GamepadManager {
|
|||||||
void handleEvent(const SDL_Event& ev);
|
void handleEvent(const SDL_Event& ev);
|
||||||
|
|
||||||
// Primary = primer gamepad al vector. `nullptr` si no n'hi ha cap.
|
// Primary = primer gamepad al vector. `nullptr` si no n'hi ha cap.
|
||||||
// Ús típic: Player 1 i navegació del ServiceMenu.
|
// Ús típic: navegació del ServiceMenu. Per al gameplay els jugadors
|
||||||
|
// usen `getForPlayer(id)`.
|
||||||
[[nodiscard]] auto getPrimary() const -> SDL_Gamepad*;
|
[[nodiscard]] auto getPrimary() const -> SDL_Gamepad*;
|
||||||
|
|
||||||
[[nodiscard]] auto getCount() const -> std::size_t;
|
[[nodiscard]] auto getCount() const -> std::size_t;
|
||||||
|
|
||||||
// Retorna el mand del jugador `idx`-èsim (0-indexed) o `nullptr` si
|
// Retorna el mand del jugador `idx`-èsim (0-indexed) en l'ordre de
|
||||||
// encara no hi ha prou mandos connectats. Reservat per a multi-jugador.
|
// connexió. Per al gameplay, `getForPlayer` fa servir l'assignació
|
||||||
|
// persistida, no l'ordre de connexió.
|
||||||
[[nodiscard]] auto getByPlayerIndex(std::size_t idx) const -> SDL_Gamepad*;
|
[[nodiscard]] auto getByPlayerIndex(std::size_t idx) const -> SDL_Gamepad*;
|
||||||
|
|
||||||
|
// Informació exposada al menú de servei (llista de mandos
|
||||||
|
// disponibles). `path` pot estar buit si SDL no l'exposa (p. ex. a
|
||||||
|
// alguns backends web).
|
||||||
|
struct Info {
|
||||||
|
SDL_Gamepad* pad{nullptr};
|
||||||
|
std::string name;
|
||||||
|
std::string path;
|
||||||
|
};
|
||||||
|
[[nodiscard]] auto getAll() const -> std::vector<Info>;
|
||||||
|
|
||||||
|
// Retorna el mand assignat al jugador `player_id` (0 = P1, 1 = P2)
|
||||||
|
// o `nullptr` si no n'hi ha cap. L'assignació la fixa
|
||||||
|
// `assignToPlayer` (cridat per Options en carregar `config.yaml` o
|
||||||
|
// des del ServiceMenu quan l'usuari tria un mando).
|
||||||
|
[[nodiscard]] auto getForPlayer(int player_id) const -> SDL_Gamepad*;
|
||||||
|
|
||||||
|
// Assigna un mando per nom + ruta física. Si ja hi ha un mando
|
||||||
|
// connectat que coincideix per ruta (o per nom com a fallback), el
|
||||||
|
// vincula immediatament; si no, l'assignació queda pendent i
|
||||||
|
// s'aplicarà si el mando es connecta més tard (hot-plug).
|
||||||
|
// Política last-wins: si l'altre jugador tenia el mateix mando
|
||||||
|
// vinculat, se'l desvincula silenciosament.
|
||||||
|
void assignToPlayer(int player_id, const std::string& name, const std::string& path);
|
||||||
|
|
||||||
|
// Esborra l'assignació (per si l'usuari trie "Cap" al menú).
|
||||||
|
void unassignFromPlayer(int player_id);
|
||||||
|
|
||||||
|
// Per a cada slot buit, hi vincula el primer mando connectat que no
|
||||||
|
// estigui reclamat per un altre slot. Equivalent al patró de
|
||||||
|
// `coffee_crisis_arcade_edition`: les assignacions persistides tenen
|
||||||
|
// prioritat; els slots que queden buits després s'emplenen per
|
||||||
|
// ordre. Retorna la llista de parells (player_id, name, path) que
|
||||||
|
// s'han vinculat, per a que Options pugui persistir-los.
|
||||||
|
struct AutoAssigned {
|
||||||
|
int player_id;
|
||||||
|
std::string name;
|
||||||
|
std::string path;
|
||||||
|
};
|
||||||
|
auto autoAssignFreeSlots() -> std::vector<AutoAssigned>;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Entry {
|
struct Entry {
|
||||||
SDL_Gamepad* pad{nullptr};
|
SDL_Gamepad* pad{nullptr};
|
||||||
SDL_JoystickID instance_id{0};
|
SDL_JoystickID instance_id{0};
|
||||||
std::string pretty_name;
|
std::string pretty_name;
|
||||||
|
std::string path;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Assignació persistent per jugador. `name` i `path` venen d'Options
|
||||||
|
// (o del menú de servei) i poden referenciar un mando encara no
|
||||||
|
// connectat; en aquest cas `pad` és `nullptr` i el hot-plug el
|
||||||
|
// vincularà quan aparegui.
|
||||||
|
struct Assignment {
|
||||||
|
std::string name;
|
||||||
|
std::string path;
|
||||||
|
SDL_Gamepad* pad{nullptr};
|
||||||
|
};
|
||||||
|
static constexpr int MAX_PLAYERS = 2;
|
||||||
|
std::array<Assignment, MAX_PLAYERS> assignments_{};
|
||||||
|
|
||||||
|
// Busca a `entries_` el millor match per a una assignació (primer
|
||||||
|
// per path, després per nom). Retorna l'iterador o `entries_.end()`.
|
||||||
|
// També accepta assignar `path` nou si ha coincidit per nom
|
||||||
|
// (cas mando reenchufat a un altre port USB).
|
||||||
|
void rebindAssignment(Assignment& a);
|
||||||
|
|
||||||
GamepadManager() = default;
|
GamepadManager() = default;
|
||||||
~GamepadManager() = default;
|
~GamepadManager() = default;
|
||||||
|
|
||||||
|
|||||||